【问题标题】:Insert order of JPA entity with compound EmbeddedId (an EmbeddedId that contains another EmbeddedId)插入具有复合 EmbeddedId(包含另一个 EmbeddedId 的 EmbeddedId)的 JPA 实体的顺序
【发布时间】:2017-04-30 06:35:10
【问题描述】:

我正在开发一个 WebSphere 8.5.5 (OpenJPA 2.2.3) 项目,该项目需要通过大型 JPA 注释实体模型进行级联创建和合并。通过在祖父母上调用 EntityManager.merge() 或在事务提交时触发刷新来合并孙子时,我们遇到了一个非常具体的问题。以下是详细信息:

实体映射的相关部分:

  1. EntityA 与 EntityB 有 oneToMany
  2. EntityB 与 EntityC 之间存在 oneToMany
  3. EntityC 与 EntityD 之间存在 oneToMany

都有双向映射。实体 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()(附加所有子级),模型将正确级联持久化。

什么不起作用:

如果您实例化实体 A 并调用 EntityManager.persist(entityA) 并在 EntityManager.merge(entityA) 时添加子代、孙代等(或在提交事务时允许隐式合并),它将失败以正确的顺序执行 INSERT 语句。为了使事情更加混乱,插入的顺序在单元测试的重复执行中是不一致的。尝试在实体 C 之前插入实体 D 失败。

问题:

我们如何更正 JPA 注释以在合并时强制执行正确的插入顺序(和更新/删除)?

编辑 1: 插入/删除顺序很关键,因为数据库强制执行带有约束的外键关系。

【问题讨论】:

标签: java jpa jakarta-ee openjpa websphere-8


【解决方案1】:

让我首先声明(也许我说的很明显,抱歉)您应该针对您的场景查看 JPA 规范…….embedables 有时对它们有不同的规则。接下来,您声明“EntityManager.create()”,但我认为您的意思是 .persist?您稍后会谈到合并,所以也许您的意思是 .merge?无论哪种方式,如果您想保留新实体而不是合并,我建议您坚持使用 .persist。虽然它不是非法的,但合并通常用于合并分离的实体等。

除此之外,让我深入了解您的问题,并为您提供可能有助于您的订单的房产。如果您的 ddl 包含外键约束,您没有在文本中说明。由于您关心订单,我假设您有这样的限制。如果你这样做,OpenJPA 对这个约束一无所知,因此,将不知道如何适当地排序。默认情况下,你不能依赖 SQL 的顺序,而顺序的随机性正是我所期望的。但是,如果您需要以支持 FK 约束的方式进行排序,那么您需要允许 OpenJPA 来“了解”您的约束。为此,您需要在 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。 operation-order 告诉 OpenJPA 根据持久化实体的方式对 SQL 进行排序,换句话说,就是操作的顺序。这实际上可能对您的情况没有太大帮助,因为您坚持 A 并期望其他所有内容都被级联(OpenJPA 可能会首先坚持 A,但是当涉及到 B 和 C 时,它是一个首先发生的废话)。但是,如果您持久化 A,然后 C,然后 B,则 SQL 应该按照设置“操作顺序”的顺序插入 A、C、然后 B。

【讨论】:

  • 感谢 Heath 的有用回答。我发现了更多与您的解决方案完全一致的信息。 link。您没有提到的一个选项是使用 OpenJPA 的 @ForeignKey 注释来标记哪些关系具有外键并需要特定的插入顺序。
  • 谢谢!我故意把它排除在外(也许我不应该这样做)。 OpenJPA 的“@ForeignKey”有两个问题:1)它是适当的。 2) 它让开发人员有责任将“@ForeignKey”放在所有正确的位置(错过一个位置,所有的赌注都被取消了)。在你的情况下,有几个类没什么大不了的,但在一个大型应用程序中,它会让事情变得更难。我不会说不使用'@FK',但我会认真考虑使用单行属性(无代码更改)而不是在域模型中的正确位置添加'@FK'。祝你好运,享受!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-31
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多