我有一个双向的一对多关系,涉及以下实体类:
0或1个客户端 <-> 0或多个产品订单
在持久化客户端实体时,我希望相关的产品订单实体也被持久化(因为它们与“父”客户端的外键可能已经更新)。
当然,所有必需的级联选项都在客户端方面设置。但是如果在引用现有产品订单的同时首次持久化新创建的客户端时,它将无法正常工作,如下所示:
- 创建并持久化产品订单“1”。可以正常工作。
- 创建客户端“2”并将产品订单“1”添加到其产品订单列表中。然后它被持久化。无法正常工作。
我尝试了几种方法,但没有一种能够显示出预期的结果。请参见下面的结果。我阅读了这里的所有相关问题,但它们没有帮助我。我使用 EclipseLink 2.3.0、纯 JPA 2.0 注释和 JTA 作为 GlassFish 3.1.2 上基于 Apache Derby (JavaDB) 的内存数据库上的事务类型。实体关系由 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: 事务标记为回滚...
原因: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLIntegrityConstraintViolationException: 这个语句将在 'PRODUCTORDER' 上引发一个唯一或主键约束条件的重复关键值。错误代码:-1 查询:INSERT INTO PRODUCTORDER (ID, CLIENT_ID) VALUES (?, ?) bind => [2 parameters bound] Query: InsertObjectQuery(javaee6test.model.ProductOrder[ id=1 ]) ...
原因: java.sql.SQLIntegrityConstraintViolationException: 这个语句将在唯一或主键约束条件或唯一索引上引发一个重复的关键值,该约束条件或唯一索引由 'SQL120513133540930' 定义在 'PRO-DUCTORDER' 上。...
原因: org.apache.derby.client.am.SqlException: 这个语句将在唯一或主键约束条件或唯一索引上引发一个重复的关键值,该约束条件或唯一索引由 'SQL120513133540930' 定义在 'PRODUCTOR-DER' 上。
我不理解这个异常。edit() 方法可以正常工作。但是我想在创建客户端时将产品订单添加到其中,因此这种方式是不够的。
方法2)仅使用 merge()
通用持久性 Facade 的更改:
// 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 日志输出如下:
Fine: INSERT INTO CLIENT (ID, NAME, ADDRESS_ID) VALUES (?, ?, ?) bind => [3 parameters bound]
但是没有对产品订单表进行 "UPDATE" 操作。因此,关系没有建立。另一方面,edit() 方法运行正常。
方法三)在两种实体类型上使用 GenerationType.IDENTITY
对客户和产品订单类的更改:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
结果:
在create()方法中,EclipseLink日志输出如下:
Fine: INSERT INTO CLIENT (NAME, ADDRESS_ID) VALUES (?, ?) bind => [2 parameters bound]
Fine: values IDENTITY_VAL_LOCAL()
Fine: INSERT INTO PRODUCTORDER (ORDERDATE, CLIENT_ID) VALUES (?, ?) bind => [2 parameters bound]
Fine: values IDENTITY_VAL_LOCAL()
因此,与添加到客户端列表的产品订单建立关系不同,会创建并持久化一个新的产品订单实体,并建立与该实体的关系。同样,在edit()方法中可以正常工作。
方法4:将方法2和3结合起来
结果:与方法2相同。
我的问题是:有没有办法实现上述描述的情况?如何实现?我想使用JPA(不使用特定于供应商的解决方案)。