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

Spu*_*ick 18 java entity-relationship jpa jta jpa-2.0

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

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

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

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

  1. 产品订单"1"已创建并保留.工作良好.
  2. 创建客户'2'并将产品订单'1'添加到其产品订单列表中.然后它坚持下去.不行.

我尝试了几个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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

通用持久性外观:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

结果:

create()立即抛出此异常:

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

引起:javax.transaction.RollbackException:标记为回滚的事务....

引发者:异常[EclipseLink-4002](Eclipse Persistence Services - 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:语句被中止,因为它会在唯一或主键约束或由'SQL120513133540930'标识的唯一索引中导致重复键值生产方-DER".

我不明白这个例外.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);
}
Run Code Online (Sandbox Code Playgroud)

结果:

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

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

但产品订单表上没有"更新".因此,这种关系没有建立.同样,另一方面,edit()工作正常.

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

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

...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
Run Code Online (Sandbox Code Playgroud)

结果:

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

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

罚款:值IDENTITY_VAL_LOCAL()

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

罚款:值IDENTITY_VAL_LOCAL()

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

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

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

我的问题是:有没有办法实现上述情景?如何才能成就?我想继续使用JPA(没有特定于供应商的解决方案).

max*_*ito 7

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

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

我试图插入一个新对象(检查),该对象具有对另一个对象(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();       
   }

}
Run Code Online (Sandbox Code Playgroud)

我是否必须为每个子对象使用MERGE方法?

答案是这样的:

看起来您的问题是考试是新的,但是人员存在并且当级联持久操作时现有实体被忽略.我相信这是按预期工作的.

只要您的关系设置为CascadeType.ALL,您就可以随时更改您的em.persist(考试); 到em.merge(考试); 这将考虑坚持新的考试,它也会将合并呼叫级联到该人.

谢谢,里克


我希望这可以帮到你.


Sai*_*Aye 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
   }
Run Code Online (Sandbox Code Playgroud)