JPA 与 JTA:持久化实体并合并级联子实体

2022-09-02 04:46:10

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

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

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

当然,所有必需的级联选项都是在客户端设置的。但是,如果在引用现有产品订单时首次保留新创建的客户端,则它不起作用,如以下场景所示:

  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
}

产品订购:

@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 Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLIntegrityConstraintViolationException: state-ment 被中止,因为它会导致在 'PRODUCTORDER' 上定义的 'SQL1205131333540930' 标识的唯一或主键约束或唯一索引中出现重复的键值。错误代码: -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”所标识的唯一或主键应变或唯一索引中出现重复的键值。

我不明白这个例外。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、名称、ADDRESS_ID)值(?, ?, ?)绑定 = > [3 个参数绑定]

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

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

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

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

结果:

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

精细:插入到客户端(名称、ADDRESS_ID)值(?, ?)绑定 = > [2 个参数绑定]

精细:值 IDENTITY_VAL_LOCAL()

精细:插入到产品订单(订单日期,CLIENT_ID)值(?, ?)绑定 = > [2 个参数绑定]

精细:值 IDENTITY_VAL_LOCAL()

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

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

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

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


答案 1

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

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

我正在尝试插入一个新对象(考试),该对象具有对另一个对象(Person)的引用,同时我想更新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);.这将负责持久化新考试,并且还将向该人级联合并调用。

谢谢,里克


我希望这可以帮助你。


答案 2

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

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

您的问题来自您分离对象。如果未分离对象,则不会遇到相同的问题。通常在JPA中,您将创建一个EntityManager,查找/查询对象,编辑/保留它们,调用提交。无需合并。


推荐