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

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

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

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

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

  1. 产品订单“1”已创建并保留。 工作良好。
  2. 创建客户’2’并将产品订单’1’添加到其产品订单列表中。 然后它坚持下去。 不起作用。

我尝试了几个apporaches,但没有一个显示预期的结果。 请参阅下面的结果。 我在这里阅读了所有相关问题,但他们没有帮助我。 我在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 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()立即抛出此exception:

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

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

引发者:exception[EclipseLink-4002](Eclipse Persistence Services – 2.3.0.v20110604-r9504):org.eclipse.persistence.exceptions.DatabaseException内部exception: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”。

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

方法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)VALUES(?,?,?)bind => [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 => [2个参数绑定]

罚款:值IDENTITY_VAL_LOCAL()

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

罚款:值IDENTITY_VAL_LOCAL()

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

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

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

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

嗨,我今天遇到了同样的问题,我通过这封电子邮件询问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 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(考试); 这将考虑坚持新的考试,它也会将合并电话级联到该人。

谢谢,里克


我希望这可以帮到你。

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

您有客户端实体 – @ 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 order = new ArrayList<>(); 

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

//为productOrder设置客户端

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

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

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

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

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

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

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