JPA ManyToMany级联与关系(排序)表

时间:2017-09-06 16:41:23

标签: jpa many-to-many eclipselink

数据设计

我的实体< - > B实体关系通过R实体完成。但是,R有其他信息阻止使用@ManyToMany:

id  | id_a | id_b | previous | ordering_index
----------------------------------------------
 1  |  1   |  10  |    2     |       1
 2  |  1   |  15  |   null   |       0
 3  |  1   |  18  |    1     |       2

JPA实体

@Entity
// ...
public class A{
    // ...
    @OneToMany(mappedBy="master", cascade = Cascade.ALL, orphanRemoval = true)
    @OrderBy("ordering")
    private List<R> bList;
    // ....
}

@Entity
// ...
public class B{
    // ...
    @OneToMany(mappedBy="slave", cascade = Cascade.REMOVE)
    private List<R> aList;
    // ....
}

@Entity
// ...
public class R{
    @Id
    // ...
    private long id;

    @ManyToOne
    @JoinColumn(name = "id_a", referencedColumnName = "id")
    private A master;

    @ManyToOne
    @JoinColumn(name = "id_b", referencedColumnName = "id")
    private B slave;

    @OneToOne
    @JoinColumn(name = "previous", referencedColumnName = "id")
    private R previous;

    @Column(name = "ordering_index")
    private int ordering;
}

关系不是双向的:用户界面允许将B实体添加到A实体,但编辑B实体不允许编辑aList

到目前为止:问题

在创建,更新和删除时,R实体在数据库中被正确保留和删除

  1. 如果我将B添加到A,A在bList中正确拥有B,但B在aList中没有A
  2. 删除B后,R实体被正确删除,但受影响的A实体仍然在其aList中有B
  3. 我尝试了什么

    1. 只是看看,我试图在slave属性中添加一些级联属性,这是一个坏主意
    2. 我想为负责B实体的EJB修饰CRUD方法:删除后,找到所有受影响的A实体并删除bList ==&gt;中的关系。因so-far > 1)而失败:B无法识别与
    3. 相关的A实体
    4. 在创建和更新A的操作时,还通过在aList中添加R实体来更新B实体,然后继续entityManager.merge()操作=&gt; JPA错误,因为R已存在(即使我没有级联合并属性?)
    5. 在删除B之前获取B的新副本,但与2相同,B实体没有最新的aList并且无法识别它与哪个A实体相关< / LI>

      到目前为止,为了正确配置关系,我错过了什么?

      编辑:解决方案(草案)

      我附加解决方案以连接评论和答案的反馈。正如克里斯正确提到的,它是关于JPA交易范围的。在进一步讨论之前,请先详细说明一下:

      1. 在R的设计(数据库和实体定义)中,主设备和从设备之间存在单一性约束
      2. 由于存在多个R实体,因此使用泛型类型。但是,在JPA中,不允许使用泛型类型的接口,我不能从R实体调用a.getBlist()。
      3. 我的解决方案是挖掘我的第三次尝试(创建和更新A的操作,也更新B实体),如Chris&#39;回答:

        public class aEjb{
            // ...
            public void update(A a){
        
                // by A -> R cascade, all the bList will be updated
                em.merge(a);
        
                // BUT !!!
                // as the transaction did not end, the newly created
                // relationship do not have a primary key generated
                // yet so if I do:
                for(R r : a.getBList()){
                    B b = r.getSlave();
                    // I'm here adding r = (null, a, b)
                    b.add(r);
                    // as r has a null primary key, JPA will try to
                    // create it, even if I removed all cascade annotation
                    // on b side => I didn't really understand why though
                    bEjb.update(b);
                    // as there is UNIQUE(a,b) constraint, JPA will
                    // throw an exception
                }
        
                // the solution was to fetch a fresh copy of the 
                // cascaded created R entity. As the primary key
                // is not known yet, I have to go through sth like
                for(R r : a.getBlist()){
                    B b = r.getSlave();
                    R updatedR = rEjb.findByMasterAndSlave(a, b);
                    // I'm here adding updatedR = (123, a, b)
                    b.add(updatedR)
                    // as updatedR primary key is not null, JPA 
                    // won't force any cascade operation, and that's
                    // great becaues I don't want any cascade here
                    bEjb.update(b);
        
                }
        
            }
            // ...
        }
        

        附加说明

        到目前为止,bListaList是最新的。如果删除了B实体,我可以循环使用aList。但是,又出现了一个问题:如果我从A实体中删除了B实体但只删除了链接,而不是删除了这些实体中的任何一个,该怎么办?

        解决方案是避免选项orphanRemoval = true

        在:

        @Entity
        // ...
        public class A{
            // ...
            @OneToMany(mappedBy="master", cascade = Cascade.ALL, orphanRemoval = true)
            @OrderBy("ordering")
            private List<R> bList;
            // ....
        }
        

        后:

        @Entity
        // ...
        public class A{
            // ...
            @OneToMany(mappedBy="master", cascade = Cascade.ALL)
            @OrderBy("ordering")
            private List<R> bList;
        
            @Transient
            private List<R> orphanBList;
            // ....
        
            public void addB(B b){
                R r = new R(a,b);
                bList.add(r);
            }
        
            public void removeR(R r){
                bList.remove(r);
                orphanBList.add(r);
            }
        }
        

        然后我继续进行类似的EJB操作,以更新受影响的B实体

2 个答案:

答案 0 :(得分:1)

A-> R和B-> R是两个独立的双向关系,必须保持这些关系以使您的对象模型与数据库中的对象保持同步。当您添加新的R实例时,您需要关联A和B引用的两侧,并且由于您要更改A和B实例,因此如果它们已分离,则您负责合并这些更改。有许多方法可以解决这个问题,最简单的方法是:

em.getTransaction().begin();
A a = em.find(A.class, AsID);
B b = em.find(B.class, BsID);
R r = new R(a, b);
a.bList.add(r);
b.aList.add(r);
em.persist(r);
em.getTransaction().commit();

在上面的代码中,顺序并不是那么重要,因为它完全在同一个上下文中完成 - A和B仍然是受管理的,因此会自动选择更改。由于Cascade.ALL设置在A-&gt; R关系上,因此甚至不需要持久调用,但我发现最好明确调出。无论您是在查看bList还是aList,这都将确保R始终在列表中。

要删除A,您必须采取以下形式:

em.getTransaction().begin();
A a = em.find(A.class, AsID);
for (R r: a.bList) {
  if (r.slave != null)
    r.slave.aList.remove(r);
}
em.remove(a);
em.getTransaction().commit();

同样,这是有效的,因为所有内容都在相同的上下文中进行管理。如果你通过DAO类型调用来回传递这个,你必须自己处理合并分离的实体,特别是将对r.slave的更改合并回到持久化上下文中。

答案 1 :(得分:0)

你需要添加这样的构造函数来解决问题1:

var persons = new List<Person> {
      new Person { Name = "James", Age = 18, Male = true },
      new Person { Name = "Nicolas", Age = 52, Male = true },
      new Person { Name = "Susan", Age = 37, Male = false }
  };

var result = persons.Aggregate(new Result(), (c, n) =>
{
    c.Names.Add(n.Name);
    c.Age.Add(n.Age);
    c.Male.Add(n.Male);
    return c;
});

尝试按照相同的方式解决问题2。