JPA 中级联之OneToMany

在JPA中@OneToMany注解一对多的关系,而@ManyToOne注解多对一的关系。

  1. 说配置

    按不同的业务需求可分为单项关联和双向关联。

    单项关联

      单项关联即单独使用@ManyToOne,或者@OneToMany

      例如:有dialog对话表和person人员表,一个person有多个dialog,实体关联关系显然是一对多

    • 使用ManyToOne,只需要配置many方,dialog表即可
      // 这里的personId属性是不必要的,仅仅只是示例
      // 因为@ManyToOne默认是EAGER的,所以person属性自然可以拿到对应personId
      // 但是呢,双向关联时,千万不能这么配置!必须干掉这样的配置
      @Column(name = "person_id")
      public void getPersonId() {
              return personId;
      }
      
      // optional默认是true,表示left join,而false表示inner join
      @ManyToOne(optional=false)
      // referencedColumnName是被关联表的字段名称,一般不需要配置,除非person表的主键名称不是id
      @JoinColumn(name = "person_id",referencedColumnName="id")
      public Person getPerson() {
      	return person;
      }
      

        


      这样配置的意义是侧重dialog表的数据保存,存储person的每个dialog,而不关心每个person具体有多少dialog记录。

    • 使用OneToMany,只需要配置one方,person表即可
      @OneToMany(fetch=FetchType.LAZY)
      @JoinColumn(name="person_id")
      public Set<Dialog> getDialogs() {
                 return dialogs;      
      }
      

      这样配置的意义是侧重每个person具体有多少dialog记录。

      注意:insertable,updatable只有双向关联才有作用,所以在@OneToMany和@ManyToOne无需配置。

     双向关联

      双向关联时,many方使用@ManyToOne注解,one方使用@OneToMany注解

      继续使用上述的例子。

      • 此时的many方dialog表配置
        @ManyToOne
        @JoinColumn(name = "person_id",insertable=true,updatable=false)
        public Person getPerson() {
        	return person;
        }
        

        insertable表示当dialog记录insert的时候,与person表关联的字段person_id是否需要插入,默认true,是需要插入的。
        updatable表示当dialog记录update的时候,与person表关联的字段person_id是否需要更新,默认是true,是需要更新的。
        具体的配置看业务需求。我这里的updatable配置为false,是因为在业务中dialog记录不可能去更新(说过的话怎么可能更改呢)。

      • 此时的one方person表配置
        @OneToMany(fetch=FetchType.LAZY,,cascade=CascadeType.ALL,mappedBy="person")
        @JoinColumn(name="person_id")
        @Fetch(FetchMode.SELECT)
        public Set<Dialog> getDialogs() {
                   return dialogs;      
        }
        

        mappedBy用来指定关联关系的被维护方。这句话有点抽象,我详细解释下。
        此处的关联关系是person表的主键id去映射dialog表的外键person_id。
        而person是被维护方,dialog是维护方,说的是dialog表中外键的person_id的增删改查,由dialog表来负责,person表你一点也不需要操心。

        cascade表示级联权限,分为CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REMOVE,CascadeType.REFRESH,CascadeType.DETACH六种。
        此时的配置表示Person对Dialog有CascadeType.ALL权限,即所有的权限。

  2. 说保存

    单向关联时,不管是mang方还是one方的数据CRUD都与另一方无关,即不受另一方的约束,只是能保存其映射属性,而非实体。
    这里重点说说双向关联的保存。

    one方保存many方

    虽说是保存,还是分两种情况。
    1.one方新建,many方新建,双方需要相互set
    示例代码:
    Person person = new Person();
    person.setName("kevin");
    person.setAge(11);
    
    Set<Dialog> dialogs = new HashSet<Dialog>();
    for(int i = 0; i < 2; i++) {
       Dialog dialog = new Dialog();
       dialog.setContent(content);
      // dialog中set person属性 dialog.setPerson(person); dialogs.add(dialog); } // person中set dialogs集合 person.setDialogs(dialogs); this.getEntityDao.save(dialogs);


    2.one方持久态,many方新建
    通常这种情况,需要先delete之前的many方数据,再insert新的数据到many方。
    示例代码:

    Person person = this.findById(id);
    
    Set<Dialog> dialogs = person.getDialogs();
    // 先删除之前的老数据
    if(dialogs != null && examinees.size() > 0) {
      // 如果是双向级联必须解除双方级联关系后删除,必须是双方
      for(Dialog d : dialogs) {
           d.setPerson(null);
      }
      person.setDialogs(null);
    this.getEntityDao().deleteAll(dialogs); } // 这一步非常非常关键! dialogs = person.getDialogs(); if(dialogs == null) { dialogs = new HashSet<Dialog>(); } for(int i = 0; i < 2; i++) { Dialog dialog = new Dialog(); dialog.setContent(content); dialog.setPerson(person); dialogs.add(dialog); } person.setDialogs(dialogs); // 此处需要使用save方法 this.getEntityDao.save(person);

    仍然需要相互set来维持彼此的关联关系,但不同的是dialogs这次是从person中get出来的。
    中间dialogs = person.getDialogs这句是再次获取dialogs,非常非常关键!不然会抛出异常
    org.springframework.orm.hibernate3.HibernateSystemException: Found two representations of same collection。

    原因分析:
    第一次Set<Dialog> dialogs = person.getDialogs()获取到了dialogs集合,
    person.setDialogs(null)后dialogs为null,紧接着又重新dialogs = new HashSet<Dialog>(),产生新的集合dialogs。
    所以commit的时候,内存中两种dialog集合,一个是delete状态,另一个是new状态。所以才导致这种异常。
    dialogs = person.getDialogs()再次获取dialogs维持其dialogs最新的状态。

  3. 说删除

    如上,这里的删除讲述双向关联的删除,分三种情况。
    1.one方删除时,同时删除many方
    one方中@OneToMany中cascade配置包含CascadeType.REMOVE或CascadeType.ALL
    示例代码:
    Person person = this.findById(id);
    this.getEntityDao().delete(paper);
    

    注意:CascadeType.REMOVE威力强大,请慎用。

    2.只删除many方
    双向关联时,删除任意一方都必先解除双发级联关系,即set对方为null。
    示例代码:

    Person person = this.findById(id);
    
    Set<Dialog> dialogs = person.getDialogs();
    // 先删除之前的老数据
    if(dialogs != null && examinees.size() > 0) {
      // 如果是双向级联必须解除双方级联关系后删除,必须是双方
      for(Dialog d : dialogs) {
           d.setPerson(null);
      }
      person.setDialogs(null);
    this.getEntityDao.deleteAll(dialogs); }


    3.只删除one方
    如上所说,必须先解除双方级联关系
    示例代码:

    Person person = this.findById(id);
    
    Set<Dialog> dialogs = person.getDialogs();
    // 先删除之前的老数据
    if(dialogs != null && examinees.size() > 0) {
      // 如果是双向级联必须解除双方级联关系后删除,必须是双方
      for(Dialog d : dialogs) {
           d.setPerson(null);
      }
      person.setDialogs(null);
      this.getEntityDao.delete(person);
    }
  4. 总结

    1.按照自己业务需求,确定是单向关联还是双向关联,并合理的进行注解
    2.单向关联的增删改查都相对简单,一般不会出什么错。
    3.双向关联时级联保存需要相互set,删除时按照不同的需求进行删除。

  

  

posted @ 2016-01-11 14:58  贝拉巴拉  阅读(1596)  评论(0编辑  收藏  举报