为啥单向@OneToMany 更新不起作用
Posted
技术标签:
【中文标题】为啥单向@OneToMany 更新不起作用【英文标题】:Why unidirectional @OneToMany update is not working为什么单向@OneToMany 更新不起作用 【发布时间】:2021-02-22 18:10:21 【问题描述】:我对 JPA 和 ORM 概念完全陌生,所以我希望有人能清楚地解释我的代码可能存在什么问题。
@Entity
@Table(name = "PERSISTENCE_customer")
public class Customer implements Serializable
private static final long serialVersionUID = 1005220876458L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@OneToMany (cascade = CascadeType.ALL, orphanRemoval = true)
private List<CustomerOrder> orders;
@Entity
@Table(name = "PERSISTENCE_ORDER")
public class CustomerOrder implements Serializable
private static final long serialVersionUID = 199102142021L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotNull
String status;
@NotNull
@OneToMany (cascade = CascadeType.ALL, orphanRemoval = true)
private List<LineItem> lineItems = new ArrayList();
@NotNull
private String orderNumber;
................
................
@Entity
@Table(name = "PERSISTENCE_LINEITEM")
public class LineItem implements Serializable
private static final long serialVersionUID = 1991217202100959L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotNull
private Integer quantity;
@NotNull
private Part part;
最初,客户实体是通过用户界面创建并成功持久化的。后来,客户有订单,我用 CustomerOrder 更新客户如下:
private void UpdateCustomer(Customer customer)
FacesContext fc = FacesContext.getCurrentInstance();
List<ShoppingCartItem> shoppingCart = getShoppingCart();
CustomerOrder order = new CustomerOrder();
List<CustomerOrder> orders = customer.getOrders();
order.setLastUpdated(new Date());
order.setOrderNumber(getInvoiceNumber());
List<LineItem> lineItems = shoppingCart
.stream()
.map(e -> (new LineItem(e.getPart(), e.getQuantity())))
.collect(Collectors.toList());
order.setLineItems(lineItems);
order.setStatus("Pending Shipment");
order.setTotal(getTotal());
orders.add(order);
customer.setOrders(orders);
try
updateOrders(customer, orders);
fc.addMessage(null,
new FacesMessage("Customer order added successfuly"));
catch (ListServiceException e)
FacesMessage errMsg = new FacesMessage(FacesMessage.SEVERITY_FATAL,
"Error while adding customer order: ", e.getMessage());
fc.addMessage(null, errMsg);
private void updateOrders(Customer cust, List<CustomerOrder> orders) throws ListServiceException
try //em is the EntityManager injected as the field member
if (em != null)
if (em.isOpen())
Customer c = getCustomer(cust.getId());
c.setOrders(orders);
em.merge(c);
else
logger.severe("Entity manager is closed");
else
logger.severe("Entity manager is NULL");
catch (Exception e)
throw ThrowListServiceException.wrapException(e);
一旦 EntityManage 合并,我得到以下异常。我的印象是我不需要自己显式地保留 LineItem 和 CustomerOrder 实体。我认为 JPA 将保留对象图中的所有实体。为什么我会收到此异常? (我正在使用带有 EclipseLink JPA 的 GlassFish 5.1 服务器)
提前致谢。
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'ORDERS_ID' cannot accept a NULL value.
Error Code: 30000
Call: INSERT INTO PERSISTENCE_CUSTOMER_PERSISTENCE_ORDER (orders_ID, Customer_ID) VALUES (?, ?)
bind => [2 parameters bound]
Query: DataModifyQuery(name="orders" sql="INSERT INTO PERSISTENCE_USER_PERSISTENCE_ORDER (orders_ID, User_ID) VALUES (?, ?)")
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:331)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:905)
...............................
.................................
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'ORDERS_ID' cannot accept a NULL value.
更新
使用 IDE (Netbeans) 调试器,我逐步完成了代码,正如我所预测的,在实体合并期间,JPA 不会将作为对象图一部分的新实体添加到持久性上下文中。例如,在 updateOrders() 方法中,当我尝试使用新的 CustomerOrder 对象列表更新现有 Customer 对象时,JPA 并没有发现 List 中的元素还不是持久性上下文的一部分,它们需要要添加。结果,我不得不修改我的代码,首先将 List 添加到持久化上下文中,然后将 Customer 对象与新持久化的 List 合并。通过这样做,我不再得到异常。顺便说一下,目前所有的映射关系都是单向的,因为我没有看到任何使用双向映射的理由。但是,通过使这些映射双向进行,我会得到什么吗?
【问题讨论】:
您何时收到错误消息。你什么时候运行这个项目?你能再解释一下吗?谢谢。 你好优素福。好问题。当我使用调试器单步执行代码时,我看到 updateOrders() 方法完成时没有任何错误或异常。我什至更进一步,在将 Customer 合并到 updateOrders() 后立即检索它,并且对象图看起来不错且准确。该异常发生在 JSF 渲染阶段的某处,当时它使用 Customer 对象填充 UI 页面,但调试器无法显示该过程。 感谢您的精彩解释。我想这与@onetomany 无关。你确实习惯了。你添加的异常是给出一个空错误。我一直在检查代码 【参考方案1】:您的 OneToMany 映射缺少连接规范或 mappedBy 值
【讨论】:
Sir3nka,根据 JPA 规范,单向映射不需要 mappedBy,也不需要 JointColumn,尽管 JoinColum 消除了使用额外连接表的需要。难道是当我使用不属于持久性上下文的新 CustomerOrder 更新现有 Customer 对象时出现问题吗?当持久化一个新实体时,JPA 将持久化整个实体图,但是,从例外情况来看,合并实体时似乎并非如此。我觉得 JPA 希望我首先坚持 CustomerORder 并将其与 Customer 合并。这是正确的吗?【参考方案2】:我注意到了。 首先你应该向数据库提交新订单。然后你应该将它与用户链接。我不确定这是否解决了你的问题,但这是一个问题。你能检查一下吗?
【讨论】:
优素福,正如我之前指出的那样。我想了想问题解决了【参考方案3】:在我看来,如果您将客户信息保存在 Order 实体中,可能会解决此问题。
@ManyToOne ()
private Customer customer;
在您的客户实体中,您应该将 mappedBy=customer 作为订单字段。
之后,您可以为特定订单向客户下达订单,而不是为客户下订单。在我看来,它将实现更好的关系映射;
order.setCustomer(customer);
我希望我理解正确,这将解决您的问题。当您为订单提供客户详细信息时,您不需要需要为同一客户提供订单列表详细信息。只要其中一个就足够了。
【讨论】:
Erdem,感谢您的评论,但我已经解决了问题。请参阅问题的更新。问题是我对合并对象时 JPA 如何操作的理解。我认为如果将新实体(即 CustomerOrder)添加到现有客户实体中,JPA 足够聪明,可以确定新实体(CustomerOrder)还不是持久化上下文的一部分,它需要首先持久化这个新实体在将其与客户实体合并之前。通过手动添加CustomerOrder,然后与Customer合并,问题就解决了。 Erdem,你所建议的也是一个设计决策,它是有效的,也许是问题域的另一种解决方案。 嘿托尼。很高兴您通过手动添加订单解决了问题。但是为订单创建一个新列表并手动更新整个 OrdersList 对我来说听起来有点痛苦。如果您保留每个订单的客户信息,它会自动为您扩充列表,如果您问我,您将不需要一直处理客户的整个 OrderList。 :)。好吧,我宁愿一直处理一个订单而不是整个列表。 Erdem:我越想你的设计建议,我就越喜欢它,我可能会这样做,但是,目前,我正在为单向和双向映射概念而苦苦挣扎,我真的不'不知道何时使用其中一个。我也喜欢使用@JointColumn 来消除映射表,但我不能让它工作,所以一旦我对 JAP 有更好的处理,我会进行设计更改。再次感谢 通过双向映射,您可以访问两个方向。例如,您有一个从存储库中检索到的客户。如果客户和订单具有正确的双向关系,您可以仅使用 getter (customer.getOrders) 让客户订购。此外,如果您有一张票,您可以通过 getter ticket.getCustomer 访问票的客户。它提供了许多类似的有用技能。以上是关于为啥单向@OneToMany 更新不起作用的主要内容,如果未能解决你的问题,请参考以下文章