代码之家  ›  专栏  ›  技术社区  ›  Al-un

带关系(排序)表的JPA多通道级联

  •  0
  • Al-un  · 技术社区  · 8 年前

    我的A实体<-&燃气轮机;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;
    }
    

    aList .

    在创建、更新和删除后,R实体被正确地持久化并在数据库中删除

    1. 如果我把B加在A上,A就正确地把B加在了里面 bList
    2. 删除B后,R实体被正确删除,但受影响的A实体中仍有B

    我试过的

    1. 为了看一看,我尝试向 slave 属性,这是个坏主意
    2. 我想为负责B实体的EJB修饰CRUD方法:删除后,找到所有受影响的A实体,并删除中的关系 bList公司 so-far > 1) :B无法识别与其相关的A实体
    3. 列表 entityManager.merge() 操作=>JPA错误,因为R已经存在(即使我没有级联合并属性?)
    4. 列表 无法识别与其相关的实体

    编辑:解决方案(草稿)

    我附加了解决方案来连接评论和答案的反馈。正如Chris正确提到的,这是关于JPA事务范围的。在继续之前,请先了解一些细节:

    1. 在R的设计(数据库和实体定义)中,主从之间存在唯一性约束

    在创建和更新A操作时,也更新B实体 )根据克里斯的回答:

    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);
    
            }
    
        }
        // ...
    }
    

    附加说明

    目前为止 bList公司 列表 是最新的。如果删除B实体,我有一个可用的 循环通过。然而,又出现了一个问题:如果我从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 回复  |  直到 8 年前
        1
  •  1
  •   Chris    8 年前

    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仍然是受管理的,因此更改会自动进行。实际上甚至不需要persist调用,因为您有级联。全部设置在A上->但是我发现明确地说出来更好。这将确保R始终在列表中,无论您是在查看bList还是aList。

    要删除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();
    

        2
  •  0
  •   Riadh    8 年前

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

    public R(A master, B slave, R previous, int ordering){
        this.master = master;
        master.getBList().add(this);
        this.slave = slave;
        slave.getAList().add(this);
        this.previous = previous;
        this.ordering = ordering;
    }
    

    试着用同样的方法解决问题2。