使用复合EmbeddedId(包含另一个EmbeddedId的EmbeddedId)插入JPA实体的顺序

时间:2016-12-14 19:45:12

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

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

实体映射的相关部分:

  1. EntityA拥有oneToMany to EntityB
  2. EntityB有一个toToMany到EntityC
  3. EntityC拥有oneToMany to EntityD
  4. 所有都具有双向映射。实体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语句。为了使事情更加混乱,INSERTS的顺序在重复执行单元测试时并不一致。尝试在实体C之前插入实体D失败。

    问题:

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

    编辑1: 插入/删除顺序至关重要,因为数据库使用约束强制执行外键关系。

1 个答案:

答案 0 :(得分:2)

让我首先陈述(也许我说明显的,抱歉)你应该检查你的场景的JPA规范.......嵌入式有时会对它们有不同的规则。接下来,你说'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。操作顺序告诉OpenJPA根据您的实体的持久性,或者换句话说,操作的顺序来命令SQL。这可能实际上对你的情况没有太大的帮助,因为你坚持A并期望其他所有内容都是级联的(OpenJPA可能会先保留A,但是当涉及到B和C时,它将是一个先行的crapshoot)。但是,如果你持久化A,然后是C,那么B,SQL应该按顺序插入A,C,然后按B设置“操作顺序”。