【问题标题】:JPA with JTA: Persist entity and merge cascaded child entitiesJPA 与 JTA:持久化实体并合并级联子实体
【发布时间】:2012-06-17 06:13:54
【问题描述】:

我与以下实体类具有双向一对多关系:

0 或 1 个客户 0 或更多产品订单

在持久化客户端实体时,我希望关联的产品订单实体也可以持久化(因为它们对“父”客户端的外键可能已更新)。

当然,所有必需的 CASCADE 选项都是在客户端设置的。但是如果新创建的客户端在引用现有产品订单时第一次被持久化,则它不起作用,如在这种情况下:

  1. 产品订单“1”已创建并保留。工作正常。
  2. 客户端“2”已创建,产品订单“1”已添加到其产品订单列表中。然后它被持久化。不起作用。

我尝试了几种方法,但都没有显示出预期的结果。请参阅下面的结果。我在这里阅读了所有相关问题,但它们对我没有帮助。我在 GlassFish 3.1.2 上的 Apache Derby (JavaDB) 内存数据库中使用 EclipseLink 2.3.0、纯 JPA 2.0 注释和 JTA 作为事务类型。实体关系由 JSF GUI 管理。对象级关系管理有效(除了持久化),我用 JUnit 测试对其进行了测试。

方法 1)“默认”(基于 NetBeans 类模板)

客户:

@Entity
public class Client implements Serializable, ParentEntity {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToMany(mappedBy = "client", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH},
            fetch= FetchType.LAZY)
    private List<ProductOrder> orders = new ArrayList<>();

    // other fields, getters and setters
}

产品订单:

@Entity
public class ProductOrder implements Serializable, ChildEntity {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne // owning side
    private Client client;

    // other fields, getters and setters
}

通用持久性外观:

// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
    getEntityManager().persist(entity);
}

// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
    getEntityManager().merge(entity);
}

结果:

create() 立即抛出此异常:

警告:调用 EJB 期间发生系统异常 ClientFacade 方法 public void javaee6test.beans.AbstractFacade.create(java.lang.Object) javax.ejb.EJBException:事务中止...

原因: javax.transaction.RollbackException:标记为回滚的事务。 ...

原因:异常 [EclipseLink-4002](Eclipse 持久性 服务 - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException 内部 异常:java.sql.SQLIntegrityConstraintViolationException: 语句被中止,因为它会导致重复键 标识的唯一或主键约束或唯一索引中的值 由在“PRODUCTORDER”上定义的“SQL120513133540930”。错误代码:-1 调用:INSERT INTO PRODUCTORDER (ID, CLIENT_ID) VALUES (?, ?) bind => 【2个参数绑定】查询: InsertObjectQuery(javaee6test.model.ProductOrder[ id=1 ]) ...

原因: java.sql.SQLIntegrityConstraintViolationException:该语句是 中止,因为它会导致唯一的键值重复 或主键约束或唯一索引 在“PRO-DUCTORDER”上定义的“SQL120513133540930”。 ...

原因: org.apache.derby.client.am.SqlException:语句被中止 因为它会导致唯一或 主键约束或唯一索引由 在“PRODUCTOR-DER”上定义的“SQL120513133540930”。

我不明白这个例外。编辑()工作正常。但是我想在创建时向客户添加产品订单,所以这还不够。

方法 2)仅合并()

对通用持久性外观的更改:

// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
    getEntityManager().merge(entity);
}

// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
    getEntityManager().merge(entity);
}

结果:

在 create() 时,EclipseLink 日志输出显示:

很好:插入客户(ID、NAME、ADDRESS_ID)值(?、?、?) bind => [绑定3个参数]

但产品订单表上没有“更新”。因此,关系不成立。另一方面,edit() 同样可以正常工作。

方法 3) 两种实体类型的 Id GenerationType.IDENTITY

对客户和产品订单类的更改:

...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...

结果:

在 create() 时,EclipseLink 日志输出显示:

很好:INSERT INTO CLIENT (NAME, ADDRESS_ID) VALUES (?, ?) bind => [2 参数绑定]

很好:值 IDENTITY_VAL_LOCAL()

很好:插入 PRODUCTORDER (ORDERDATE, CLIENT_ID) VALUES (?, ?) bind => [2 参数绑定]

很好:值 IDENTITY_VAL_LOCAL()

因此,不是建立与添加到客户列表中的产品订单的关系,而是创建并保留一个新的产品订单实体(!),并建立与该实体的关系。同样在这里,edit() 工作正常。

方法 4) 方法 (2) 和 (3) 结合

结果:与方法(2)相同。

我的问题是:有没有办法实现上述场景?如何归档?我想继续使用 JPA(没有特定于供应商的解决方案)。

【问题讨论】:

    标签: java jpa jpa-2.0 entity-relationship jta


    【解决方案1】:

    在您的 ProductOrder 实体中使用 @Joincolumn 注释,请参见下文。

       @Entity
       public class ProductOrder implements Serializable, ChildEntity {
       private static final long serialVersionUID = 1L;
    
       @Id
       @GeneratedValue(strategy = GenerationType.AUTO)
       private Long id;
    
       @ManyToOne // owning side
       @JoinColumn(name = "PRODUCTORDER_ID", referencedColumnName = "ID")
       //write your database column names into name and referencedColumnName attributes.
       private Client client;
    
       // other fields, getters and setters
       }
    

    【讨论】:

    • 不,这不起作用。它实际上并没有改变任何东西。我用@JoinColumn(name = "CLIENT_ID", referencedColumnName = "ID") 试过了,但是没有用。
    【解决方案2】:

    确保设置了关系的双方,不能只将订单添加到客户端,还需要设置订单的客户端。您还需要合并您已更改的两个对象。如果您只是合并客户端,那么订单的客户端将不会被合并(尽管您的级联导致它被合并)。

    persist 不起作用,因为persist 要求被持久化的对象对于持久化上下文是正确的,即不引用分离的对象,它必须引用托管对象。

    您的问题来自您分离对象。如果您没有分离对象,您将不会遇到同样的问题。通常在 JPA 中,您将创建一个 EntityManager,查找/查询您的对象,编辑/保存它们,调用 commit。不需要合并。

    【讨论】:

    • 我找到了一个类似于您的建议的解决方法:我删除了所有cascade 选项,并且我手动持久化/合并了“依赖”实体,即通过维护“依赖”实体列表并在何时合并它们“父”被持久化/合并。但我不会接受这个答案,因为这可能永远不会是“官方 JPA”方法。此外,我没有明确分离任何实体,并且 EclipseLink 日志记录没有提及任何分离操作。我也不明白这些 cascade 选项的用途,因为它们显然不起作用。
    • 4 年后,我不得不做同样的事情。起初,我遵循使用仅 JPA 级联注释的按书本方法,但最终我不得不删除注释并持久化我的父实体,将它们添加回每个子实体,然后使用合并来持久化它们。
    • @DouglasDeRizzoMeneghetti 因为 JPA 和 Hibernate 一起糟透了!从事行业 20 年,但 CASCADE MERGE 问题没有直接的解决方案。这简直是​​一种耻辱。
    • 你的最后一段对我有用。我从数据库中检索了实体,然后与所有更改的成员合并。
    【解决方案3】:

    您好,我今天遇到了同样的问题,我通过这封电子邮件向 openJPA 邮件列表询问:

    嗨。我在同一实体中插入和更新引用时遇到问题。

    我试图插入一个引用另一个对象(Person)的新对象(Exam),同时我想更新 Person 对象的属性(birthDate)。尽管我将 CascadeType 设置为 ALL,但从未发生过更新。唯一可行的方法是进行持久化,然后进行合并操作。这是正常的吗?我需要改变什么吗??

    我不喜欢在 Person 对象中使用合并“手动更新”的想法,因为我不知道用户想要更新多少对象(Exam 的子对象)。

    实体:

    public class Exam{  
       @ManyToOne(cascade= CascadeType.ALL)
       @JoinColumn(name = "person_id")
       public Person person;
    ......
    }
    
    public class Person{
        private Date birthDate;
       @OneToMany(mappedBy = "person")
        private List<Exam> exams
    .......
    }
    
    public class SomeClass{
       public void someMethod(){
          exam = new Exam()
          person.setBirthDate(new Date());
          exam.setPerson(person); 
          someEJB.saveExam(exam);
       }
    }
    
    public class someEJB(){
    
       public void saveExam(Exam exam){
            ejbContext.getUserTransaction().begin();
            em.persist(exam);
            //THIS WORKS
            em.merge(exam.getPerson());
            ejbContext.getUserTransaction().commit();       
       }
    
    }
    

    我必须对每个子对象使用 MERGE 方法吗?

    答案是这样的:

    看起来你的问题是考试是新的,但人是 级联持久化时,现有的和现有的实体被忽略 手术。我相信这是按预期工作的。

    只要您的关系设置为 CascadeType.ALL,您就可以随时 改变你的 em.persist(exam);到 em.merge(exam);.那会照顾 坚持新的考试,它也会将合并调用级联到 人。

    谢谢, 瑞克


    希望对你有帮助。

    【讨论】:

      【解决方案4】:

      @SputNick,我通过下面描述的步骤解决了。我将使用你的代码 sn-p 来演示我做了什么,

      您有 Client 实体 -@OneToManyProductOrder 实体 -@ManyToOne。我将使用

      // Called when pressing "save" on the "edit..." 
      public void edit(T entity) {
       getEntityManager().merge(entity);}
      

      为了持久性,因为它可以让我灵活地保存和更新。

      要创建客户和相应的产品订单,请使用

      client.set..(some property to be saved);
      
      productOrder.set..(some property to be saved);
      

      //为客户端设置productOrder

      List<ProductOrder> order = new ArrayList<>();
      

      client.setOrders(order); //注意这里需要传递一个列表

      //为productOrder设置客户端

      productOrder.setClient(productOrder);
      
      .edit(client) or .edit(productOrder)
      

      请注意,任何一种方式都可以使用您的更改更新两个实体表。

      虽然晚了,希望对大家有所帮助

      【讨论】:

        猜你喜欢
        • 2013-10-26
        • 1970-01-01
        • 2015-11-02
        • 2010-12-30
        • 2015-04-15
        • 1970-01-01
        • 1970-01-01
        • 2016-05-17
        • 1970-01-01
        相关资源
        最近更新 更多