为啥单向@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;

在您的客户实体中,您应该将 ma​​ppedBy=customer 作为订单字段。

之后,您可以为特定订单向客户下达订单,而不是为客户下订单。在我看来,它将实现更好的关系映射;

order.setCustomer(customer);

我希望我理解正确,这将解决您的问题。当您为订单提供客户详细信息时,您不需要需要为同一客户提供订单列表详细信息。只要其中一个就足够了。

【讨论】:

Erdem,感谢您的评论,但我已经解决了问题。请参阅问题的更新。问题是我对合并对象时 JPA 如何操作的理解。我认为如果将新实体(即 CustomerOrder)添加到现有客户实体中,JPA 足够聪明,可以确定新实体(CustomerOrder)还不是持久化上下文的一部分,它需要首先持久化这个新实体在将其与客户实体合并之前。通过手动添加CustomerOrder,然后与Customer合并,问题就解决了。 Erdem,你所建议的也是一个设计决策,它是有效的,也许是问题域的另一种解决方案。 嘿托尼。很高兴您通过手动添加订单解决了问题。但是为订单创建一个新列表并手动更新整个 OrdersList 对我来说听起来有点痛苦。如果您保留每个订单的客户信息,它会自动为您扩充列表,如果您问我,您将不需要一直处理客户的整个 OrderList。 :)。好吧,我宁愿一直处理一个订单而不是整个列表。 Erdem:我越想你的设计建议,我就越喜欢它,我可能会这样做,但是,目前,我正在为单向和双向映射概念而苦苦挣扎,我真的不'不知道何时使用其中一个。我也喜欢使用@JointColumn 来消除映射表,但我不能让它工作,所以一旦我对 JAP 有更好的处理,我会进行设计更改。再次感谢 通过双向映射,您可以访问两个方向。例如,您有一个从存储库中检索到的客户。如果客户和订单具有正确的双向关系,您可以仅使用 getter (customer.getOrders) 让客户订购。此外,如果您有一张票,您可以通过 getter ticket.getCustomer 访问票的客户。它提供了许多类似的有用技能。

以上是关于为啥单向@OneToMany 更新不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥java更新查询不起作用?

Symfony OneToMany 关联不起作用

为啥作曲家更新后php artisan命令不起作用

Symfony 3 - Doctrine 2 - 在 oneToMany 关系上的 orderBy 不起作用

为啥我的适配器更新不起作用

为啥我的 UPDATE sql 查询不起作用?不会更新表