【JPA】@OneToOne 一对一双向关联注解

原文请点击此处


@OneToOne 定义:一对一关系。

      这次,我们引用一个新的数据模型:丈夫(husband) 和 妻子(wife)。前提:众所周知,在我们伟大的祖国,法律上只存在一种婚姻关系,一夫一妻制。所以,一个丈夫只能有一个妻子,一个妻子也只有一个丈夫。(大家千万别较真,我已经限定了场景,法律上只允许一夫一妻。小三不在本例研究范围内)
      对于这种数据模型来说,双向一对一关系已经很明显了。我们通过丈夫应该能找到他的妻子。同样,通过妻子,应该能找到她的丈夫。


好了,让我们来看例子吧:

@Entity
@Table(name = "husband")
public class Husband (){
     
     @Id  //JPA注释: 主键 
     @GeneratedValue(strategy = GenerationType.AUTO)   //设置 id 为自增长
     private Long id;

     private String name;

     //由于,husband 是这个一对一的关系的主控方,所以,在husband表中添加了一个 wife 的外键。
     //通过这个外键来维护 people和pet的一对一关系,而不是用第三张码表。这个是通过@JoinColumn注释实现的。
     @OneToOne //JPA注释: 一对一 关系
     @JoinColumn(name="wife_fk" )// 在husband中,添加一个外键 "wife_fk"
     private Wife wife;

     //省略 get / set  方法...
}



@Entity
@Table(name = "wife")
public class Wife (){
     
     @Id  
     @GeneratedValue(strategy = GenerationType.AUTO)  
     private Long id;

     private String name;

     //注意:由于是双向的一对一关系。作为关系的被维护端。需要在wife实体中添加和 husband 关联的属性
     //mappedBy 注释:指定了,这个实体是被关系维护端的那个属性所维护。
     //在本例中,Wife实体是被 Hhusband实体中的外键“wife”所维护。
     @OneToOne(mappedBy="wife")
     private Husband husband;
    
     //省略 get / set  方法...
}

      那么,现在已经很明显了。在Wife 的 Model 中,已经找到了与 Husband 关联的属性了。但是,如果进入到数据库中查看Wife这张表,你并不会在表里找到关联到Husband的外键。为什么会这样呢?因为,我们定义关系的时候,规定了关系的维护端是Husband。所以,只有Husband中有管理到Wife的外键。而这个一对一关系,就是靠这个外键来维护的。那当我们设置了,mappedBy后,使关系成为双向的时候,是怎样通过Wife实体来得到她对应的Husband实体的呢?这都是由JPA来做的。我们不必在这上面花费过多的精力。如果,你感兴趣,也可以自己研究思考一下。


-----------------------------------------测试--------------------------------------------------------------------------
测试方法:
//省略包的引用:HusbandDao , WifeDao

public class TestOneToOneEitherSide() {

      //测试方法1-------创建实体:丈夫 和 妻子
      public void testCreate() {
            
            Husband wang = new Husbnad();
            wang.setName("老王");

            Wife li = new Wife();
            li.setName("莉莉");
            //由于关系是双向的,所以,你不必手动的来设置“莉莉”的丈夫。JPA会自动完成这个操作。所以,即使将这个操作注释掉,双向关系还是会正确的建立的。
            //wife.setHusband(wang);
            wifeDao.save(li);

            //将“莉莉” 交给 “老王”。
            //但是,这一步,就不可以省略了。必须得手动的或者说显式的设置“老王”和“莉莉”的关系。如果,你省略了这一步,那么就没法建立起 “老王” 和 “莉莉” 的你一对一关系了,无论是双向的还是单向的。
            wang.setWife(li);
            //此时,老王 和 莉莉 ,已经是双向的一对一关系了。通过他俩任何一方,都可以找到另一个对象。
            husbandDao.save(wang);

            //验证方法1:
            //在表 Husband 中找到名字是老王的丈夫。
            Husband husband = husbandDao.findByName("老王");
            //通过老王得到他的妻子实体,并调用妻子的getName()方法,得到老王妻子的姓名。
            Wife wangWife = husband.getWife();
            String wifeName = wangWife.getName();
            System.out.println(wifeName);//结果为: 莉莉

            //验证方法2:与方法1类似,简写。
            Wife wife = wifeDao.findByName("莉莉");
            String husbandName = wife.getHusband().getName();
            System.out.println(husbandName);//结果为: 老王
            
            //通过这两个验证方法,我们已经知道,在设置了双向一对一关系后。我们已经可以通过关系的一方,得到与之对应的另一方了。其实,双向的实际使用意义也正是如此。通过关系的任意一方可以得到与之对应的另一方
      }

     
      //测试方法2-------删除实体:丈夫
      public void testDeleteHusband() {

            //在表 Husband 中找到名字是老王的丈夫。
            Husband wang = husbandDao.findByName("老王");
            //校验空值和空对象的方法省略,因为肯定会得到老王这个实体的。
            husbandDao.delete(wang);

            //验证方法1:
            //在表 Husband 中找到名字是老王的丈夫。
            Husband husband = husbandDao.findByName("老王");
            System.out.println(husband);//结果为: 空(null)

            //验证方法2:与方法1类似,简写。
            Wife wife = wifeDao.findByName("莉莉");
            if(wife != null){
                  Husband wifeHusband = wife.getHusband();
                  System.out.println(wifeHusband);//结果为: 空(null)
            }
            
            //名字叫“老王”的丈夫已经被顺利的删除掉了。没有报异常,他的妻子“莉莉”并没有被一同删掉。(因为没有添加级联删除的原因)。此时,我们发现“莉莉”的丈夫属性也是空的。老王这个关系的维护端被删除的时候,JPA会做相应的操作,并不会影响到“莉莉”。表Wife中本来也没有与Husband 关联的外键,删除 老王,当然不会对 莉莉 产生影响了。因为她的丈夫属性上有mappedBy 注释。
}


      //测试方法3-------直接删除实体:妻子
      //这种直接删除的方式,会产生异常
      public void testDeleteWife() {

            //得到名字是“莉莉”的实体
            Wife li = wifeDao.findByName("莉莉");
            wifeDao.delete(li);

            //此时后台会报异常,数据库回滚,无法删除"莉莉"
      }
-------------------------------------------------------------------------------------------------------------------
      假设,数据库没有回滚,那么莉莉删除掉后。在表 Husband 中,老王这个实体的 外键 还是 "莉莉" 的id。但是,此时,莉莉已经被删除掉了。这样当然会产生异常。在上一篇帮助手册【JPA】 @OneToOne 单向 中,我们曾经遇到过这种情况。删除关系被维护端之前,没有从关系维护端手动解除关系,后台就一定会报异常。在任何关系中,都是这样,不论他是单向的还是双向的,不论是一对一、一对多、多对多。只要想删除关系的被维护端,必须先从关系的维护端手动解除关系。
      在看正确的删除被维护端方法前,我们应该回忆一下,在【JPA】 @OneToOne 单向 中,删除关系被维护端是一件多么痛苦的事儿啊。我得通过循环去 关系维护端所在的表中,找到要删除的实体对应的维护端。但是,在双向的 一对一 关系中。找到实体的维护端,好像并不是一件痛苦的事儿。
-------------------------------------------------------------------------------------------------------------------
      //测试方法4-------解除关系并删除实体:妻子
      //正确的删除方法
      public void rightDeleteWife(){
            
            //得到名字是“莉莉”的实体
            Wife li = wifeDao.findByName("莉莉");
            //得到她的关系维护端,也就是她的丈夫
            Husband liHusband = li.getHusband();
            //此时,我们要强迫 “老王” 解除 他和 “莉莉” 的关系
            liHusband.setWife(null);
            wifeDao.delete(li);

            //验证方法:
            Husaband wang = husbandDao.findByName("老王");
            Wife wangWife = wang.getWife();
            //当存在老王这个实体,并且老王没有妻子时,打印我们的校验信息
            if(wang != null  &&  wangWife == null){
                  System.out.println("老王单身了");
            }

            //果然,这句话被打印出来了。莉莉 被成功的删除掉了,老王的外键也被置空了。看来,想要删除被维护端,只能手动的先从关系维护端解除关系再删除。
      }

===============================================================
      至此,一对一关系,已经向大家全部介绍完毕了。其实,建立一对一关系,还有别的方法。比如共享主键之类的。但是,用这两篇文章的方法建立一对一关系比较方便,也不会产生第三张码表。数据库比较干净。如果,大家对另外两种建立方法感兴趣,可以自己查看我们翻译的中文API。这里就不做阐述了。
      这两篇文章,都强调了关系被维护端的删除操作。就是想让大家重视,关系的维护端的作用和删除操作易发生的异常。至于级联操作,由于大家刚刚熟悉关系,所以,稍后会在 @OneToMany(一对多) 关系中,向大家慢慢阐述。而且,级联操作,在一对多关系中的作用比较大。但是,一定要慎用,否则会产生你意料不到的麻烦。请期待【JPA 级联保存/级联删除】@OneToMany 一对多(单向和双向)注解

猜你喜欢

转载自blog.csdn.net/QQ70945934/article/details/77989259