带有JTA的JPA:持久化实体并合并级联子实体

时间:2012-06-14 08:22:54

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

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

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

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

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

  1. 创建并保留产品订单“1”。工作正常。
  2. 创建客户'2',并将产品订单'1'添加到其产品订单列表中。然后它坚持下去。不起作用。
  3. 我尝试了几个apporaches,但没有一个显示出预期的结果。请参阅下面的结果。我在这里阅读了所有相关问题,但他们没有帮助我。我在GlassFish 3.1.2上的Apache Derby(JavaDB)内存数据库中使用EclipseLink 2.3.0,纯JPA 2.0 Annotations和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
    }
    

    ProductOrder:

    @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:The   状态被中止,因为它会导致重复密钥   标识的唯一或主键约束或唯一索引中的值   通过'PRODUCTORDER'上定义的'SQL120513133540930'。错误代码:-1   调用:INSERT INTO PRODUCTORDER(ID,CLIENT_ID)VALUES(?,?)bind =&gt;   [2个参数绑定]查询:   InsertObjectQuery(javaee6test.model.ProductOrder [id = 1])...

         

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

         

    引起:   org.apache.derby.client.am.SqlException:语句已中止   因为它会导致一个唯一的或重复的键值   主键约束或唯一索引   'PRODUCTOR-DER'上定义了'SQL120513133540930'。

    我不明白这个例外。 edit()工作正常。但我想在创建时向客户添加产品订单,因此这是不够的。

    方法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 Logging输出显示:

      

    好的:插入客户端(ID,NAME,ADDRESS_ID)值(?,?,?)   bind =&gt; [3个参数绑定]

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

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

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

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

    结果:

    在create()上,EclipseLink Logging输出显示:

      

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

         

    罚款:值IDENTITY_VAL_LOCAL()

         

    好的:INSERT INTO   PRODUCTORDER(ORDERDATE,CLIENT_ID)VALUES(?,?)bind =&gt; [2   参数绑定]

         

    罚款:值IDENTITY_VAL_LOCAL()

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

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

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

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

4 个答案:

答案 0 :(得分:7)

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

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

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

我不喜欢在Person对象中使用merge进行“手动更新”的想法,因为我不知道用户想要更新多少个对象(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(考试);到em.merge(考试);这会照顾 坚持新的考试,它也会将合并电话级联到 人

谢谢, 瑞克


我希望这可以帮到你。

答案 1 :(得分:1)

@SputNick,我通过下面描述的步骤解决了这个问题。我将使用你的代码片段来演示我做了什么,

您有客户实体 - @ OneToMany ProductOrder实体 - @ 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)

请注意,无论哪种方式都可以使用您的更改更新两个实体表。

虽然很晚,但希望它会帮助某人

答案 2 :(得分:0)

在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
   }

答案 3 :(得分:0)

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

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

您的问题来自您分离对象。如果您没有分离对象,则不会出现相同的问题。通常在JPA中,您将创建一个EntityManager,查找/查询您的对象,编辑/持久化它们,调用commit。不需要合并。