插入具有复合 EmbeddedId(包含另一个 EmbeddedId 的 EmbeddedId)的 JPA 实体的顺序
Insert order of JPA entity with compound EmbeddedId (an EmbeddedId that contains another EmbeddedId)
我正在 WebSphere 8.5.5 (OpenJPA 2.2.3) 中开展一个项目,该项目需要通过大型 JPA 注释实体模型进行级联创建和合并。通过在 grand-parent 上调用 EntityManager.merge() 或在事务提交时触发刷新来合并 grand-children 时,我们遇到了一个非常具体的问题。详情如下:
实体映射的相关部分:
- EntityA 有一个 oneToMany 到 EntityB
- EntityB 与 EntityC 有一对多关系
- EntityC 与 EntityD 有一对多关系
都有双向映射。实体 A 和 B 具有单列主键。实体 C 有一个复合主键,其中包含实体 B 主键的外键。实体 D 有一个复合键,其中包含实体 C 的复合键。请参阅下面的映射。
@Entity
@Table(name="TableA")
public class EntityA extends BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
@SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
@Column(name="TABLE_A_ID")
private Integer id;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
private List<EntityB> entityBList;
...
}
@Entity
@Table(name="TableB")
public class EntityB extends BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
@SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
@Column(name="TABLE_B_ID")
private Integer id;
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="TABLE_A_ID")
private EntityA entityA;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
private List<EntityC> entityCList;
...
}
@Entity
@Table(name="TableC")
public class EntityC extends BaseEntity {
@EmbeddedId
private EntityC_PK id = new EntityC_PK();
@MapsId("entityB_Id")
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="TABLE_B_ID")
private EntityB entityB;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
private List<EntityD> entityDList;
...
}
@Embeddable
public class EntityC_PK implements BaseComponent {
@Column(name="TABLE_B_ID", nullable = false, updatable = false)
private Integer entityB_Id;
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
@SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
@Column(name="TABLE_C_ID")
private Integer entityC_Id;
...
}
@Entity
@Table(name="TABLE_D")
public class EntityD extends BaseEntity {
@EmbeddedId
private EntityD_PK id = new EntityD_PK();
@MapsId("entityC_Id")
@JoinColumns({
@JoinColumn(name = "TABLE_B_ID"),
@JoinColumn(name = "TABLE_C_ID")})
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private EntityC entityC;
...
}
@Embeddable
public class EntityD_PK implements BaseComponent {
@Embedded
private EntityC_PK entityC_Id;
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
@SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
@Column(name="TABLE_D_ID")
private Integer entity_id;
...
}
什么有效:
您可以在实体 A 上调用 EntityManager.persist()(附加所有 children),模型将正确地级联持久化。
什么不起作用:
如果您实例化实体 A 并调用 EntityManager.persist(entityA) 然后添加 children、grand-children 等,当您 EntityManager.merge(entityA)(或允许在提交事务时隐式合并)它将无法以正确的顺序执行 INSERT 语句。更令人困惑的是,INSERTS 的顺序在单元测试的重复执行中并不一致。尝试在实体 C 之前插入实体 D 失败。
问题:
我们如何更正 JPA 注释以在合并时强制执行正确的插入顺序(和 update/delete)?
编辑 1:
insert/delete 顺序很关键,因为数据库会强制执行具有约束的外键关系。
首先让我声明(对不起,我可能说的很明显)您应该针对您的场景查看 JPA 规范......可嵌入对象有时对它们有不同的规则。接下来,您声明 'EntityManager.create()',但我认为您的意思是 .persist?您稍后会谈到合并,所以也许您指的是 .merge?无论哪种方式,如果您想保留新实体而不是合并,我建议您坚持使用 .persist。虽然这不是非法的,但合并通常用于合并分离的实体等。
话虽如此,让我进入你问题的核心并给你一个 属性 这可能有助于你的订单。如果您的 ddl 包含外键约束,您没有在文本中说明。由于您关心订单,我假设您有这样的约束。如果您这样做,OpenJPA 对这个约束一无所知,因此,将不知道如何适当地排序。默认情况下,你不能依赖 SQL 的顺序,顺序的随机性正是我所期望的。但是,如果您需要以支持 FK 约束的方式进行排序,那么您需要允许 OpenJPA 'learn' 关于您的约束。为此,您需要在 persistence.xml 文件中设置此 属性(或者您可以将其设置为 JVM 自定义 属性):
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
这 属性 允许 OpenJPA 检查您的架构,这样做可以了解您的 FK 约束。有了这些知识,OpenJPA 就可以正确地排序 SQL。
最后,如果你没有 FK 约束,但你想以某种方式订购 SQL,那么你可能需要使用这个:
<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>
不要,我重复一遍,不要同时使用这两个属性。它可能有奇怪的副作用。请首先关注 SchemaFactory 属性,如果没有帮助,请尝试 UpdateManager。操作顺序告诉 OpenJPA 根据实体的持久化方式(即操作顺序)对 SQL 进行排序。这实际上可能对您的情况没有太大帮助,因为您坚持 A 并期望其他所有内容都被级联(OpenJPA 可能会首先坚持 A,但当涉及 B 和 C 时,这是一个先行的废话)。但是,如果您坚持 A,然后是 C,然后是 B,SQL 应该按照插入 A、C、然后是 B 的顺序插入 "operation-order"。
我正在 WebSphere 8.5.5 (OpenJPA 2.2.3) 中开展一个项目,该项目需要通过大型 JPA 注释实体模型进行级联创建和合并。通过在 grand-parent 上调用 EntityManager.merge() 或在事务提交时触发刷新来合并 grand-children 时,我们遇到了一个非常具体的问题。详情如下:
实体映射的相关部分:
- EntityA 有一个 oneToMany 到 EntityB
- EntityB 与 EntityC 有一对多关系
- EntityC 与 EntityD 有一对多关系
都有双向映射。实体 A 和 B 具有单列主键。实体 C 有一个复合主键,其中包含实体 B 主键的外键。实体 D 有一个复合键,其中包含实体 C 的复合键。请参阅下面的映射。
@Entity
@Table(name="TableA")
public class EntityA extends BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
@SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
@Column(name="TABLE_A_ID")
private Integer id;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
private List<EntityB> entityBList;
...
}
@Entity
@Table(name="TableB")
public class EntityB extends BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
@SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
@Column(name="TABLE_B_ID")
private Integer id;
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="TABLE_A_ID")
private EntityA entityA;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
private List<EntityC> entityCList;
...
}
@Entity
@Table(name="TableC")
public class EntityC extends BaseEntity {
@EmbeddedId
private EntityC_PK id = new EntityC_PK();
@MapsId("entityB_Id")
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
@JoinColumn(name="TABLE_B_ID")
private EntityB entityB;
@OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
private List<EntityD> entityDList;
...
}
@Embeddable
public class EntityC_PK implements BaseComponent {
@Column(name="TABLE_B_ID", nullable = false, updatable = false)
private Integer entityB_Id;
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
@SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
@Column(name="TABLE_C_ID")
private Integer entityC_Id;
...
}
@Entity
@Table(name="TABLE_D")
public class EntityD extends BaseEntity {
@EmbeddedId
private EntityD_PK id = new EntityD_PK();
@MapsId("entityC_Id")
@JoinColumns({
@JoinColumn(name = "TABLE_B_ID"),
@JoinColumn(name = "TABLE_C_ID")})
@ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private EntityC entityC;
...
}
@Embeddable
public class EntityD_PK implements BaseComponent {
@Embedded
private EntityC_PK entityC_Id;
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
@SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
@Column(name="TABLE_D_ID")
private Integer entity_id;
...
}
什么有效:
您可以在实体 A 上调用 EntityManager.persist()(附加所有 children),模型将正确地级联持久化。
什么不起作用:
如果您实例化实体 A 并调用 EntityManager.persist(entityA) 然后添加 children、grand-children 等,当您 EntityManager.merge(entityA)(或允许在提交事务时隐式合并)它将无法以正确的顺序执行 INSERT 语句。更令人困惑的是,INSERTS 的顺序在单元测试的重复执行中并不一致。尝试在实体 C 之前插入实体 D 失败。
问题:
我们如何更正 JPA 注释以在合并时强制执行正确的插入顺序(和 update/delete)?
编辑 1: insert/delete 顺序很关键,因为数据库会强制执行具有约束的外键关系。
首先让我声明(对不起,我可能说的很明显)您应该针对您的场景查看 JPA 规范......可嵌入对象有时对它们有不同的规则。接下来,您声明 'EntityManager.create()',但我认为您的意思是 .persist?您稍后会谈到合并,所以也许您指的是 .merge?无论哪种方式,如果您想保留新实体而不是合并,我建议您坚持使用 .persist。虽然这不是非法的,但合并通常用于合并分离的实体等。
话虽如此,让我进入你问题的核心并给你一个 属性 这可能有助于你的订单。如果您的 ddl 包含外键约束,您没有在文本中说明。由于您关心订单,我假设您有这样的约束。如果您这样做,OpenJPA 对这个约束一无所知,因此,将不知道如何适当地排序。默认情况下,你不能依赖 SQL 的顺序,顺序的随机性正是我所期望的。但是,如果您需要以支持 FK 约束的方式进行排序,那么您需要允许 OpenJPA 'learn' 关于您的约束。为此,您需要在 persistence.xml 文件中设置此 属性(或者您可以将其设置为 JVM 自定义 属性):
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
这 属性 允许 OpenJPA 检查您的架构,这样做可以了解您的 FK 约束。有了这些知识,OpenJPA 就可以正确地排序 SQL。
最后,如果你没有 FK 约束,但你想以某种方式订购 SQL,那么你可能需要使用这个:
<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>
不要,我重复一遍,不要同时使用这两个属性。它可能有奇怪的副作用。请首先关注 SchemaFactory 属性,如果没有帮助,请尝试 UpdateManager。操作顺序告诉 OpenJPA 根据实体的持久化方式(即操作顺序)对 SQL 进行排序。这实际上可能对您的情况没有太大帮助,因为您坚持 A 并期望其他所有内容都被级联(OpenJPA 可能会首先坚持 A,但当涉及 B 和 C 时,这是一个先行的废话)。但是,如果您坚持 A,然后是 C,然后是 B,SQL 应该按照插入 A、C、然后是 B 的顺序插入 "operation-order"。