JPA OneToOne单向和双向测试验证

最近学习Jpa,其中的OneToOne等关系映射比较模糊,今天主要尝试写了个OneToOne的demo,当做练手,也加深下理解。
说起OneToOne,就是一对一映射,现实生活中比较常见的例子就是一个人有一个身份证,一个丈夫只能有一个老婆,当然一个老婆只能有一个丈夫,以上都是正常情况下的现实场景,作奸犯科的当然不在考虑了。一个丈夫实例应该仅仅和一个妻子实例一一对应。下面新建工程,基本的测试demo template我会在之后上传。
由于OneToOne有单向和双向的区别,这里先简单介绍单向的,然后再介绍双向的。这是我的目录结构:
这里写图片描述
entity包下是对实体的定义,
repo包下是对数据接入层接口的定义
service和serviceimpl 是服务的接口定义和实现,在这里不需要,暂时不用管这个包
test下是自己的测试类,为啥不用Junit嘞?这样自己测试可以方便的看到数据库变化(其实是自己还不熟悉Junit…)

s废话不多说,开始干活

新建Husband类和Wife类

Husband.class

public class Husband implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer hid;

    @NonNull@Column(nullable = false)
    private String name;

    @OneToOne(targetEntity = Wife.class)
    @JoinColumn(name = "wife_id",referencedColumnName = "wid")
    private Wife wife;

    public Husband(){
    }

    public Husband(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "hid=" + hid +
                ", name='" + name + '\'' +
                ", wife=" + wife +
                '}';
    }
}

Wife.class

public class Wife implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer wid;

    @NonNull@Column(nullable = false)
    private String name;

    public Wife(){
    }

    public Wife(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "wid=" + wid +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public int hashCode(){
        return wid;
    }

    @Override
    public boolean equals(Object obj){
        if(!(obj instanceof Wife))
            return  false;
        Wife wife = (Wife) obj;
        if(wife.getWid() == this.wid)
            return true;
        else
            return false;
    }
}

接下来新建两个数据接入层接口,有人喜欢命名为 ××Dao,有人喜欢命名为 ××Repo,我采用后者,
HusbandRepo

@Transactional
public interface HusbandRepo extends JpaRepository<Husband,Integer> {

    public Husband save(Husband husband);

    public Husband findByName(String name);
}

WifeRepo

public interface WifeRepo extends JpaRepository<Wife,Integer> {

    public Wife save(Wife wife);

    public Wife findByName(String name);

}

关于这两个接口因为继承了JpaRepositiry,很多方法都已经预设好了,相当方便,只需要写两个我们需要的自定义的方法即可。每个Repo都有保存实例和通过名字查找实例的方法。

接下来写OneToOneTest测试类

public class One2OneTest {
    public static void main(String[] args){
        ApplicationContext conatext = new ClassPathXmlApplicationContext("classpath*:*config.xml");
        HusbandRepo husbandRepo = conatext.getBean("husbandRepo",HusbandRepo.class);
        WifeRepo wifeRepo = conatext.getBean("wifeRepo",WifeRepo.class);
        //添加测试代码
    }
}

测试新建实例

        Husband husband = new Husband("Jack");
        Wife wife = new Wife("Rose");
        husband.setWife(wife);
        wifeRepo.save(wife);
        husbandRepo.save(husband);

由于Husband和Wife使用各自的Repo进行持久化,Husband是关系维护端且无持久化级联设置,所以只能先wifeRepo.save(wife),把wife纳入实例管理,然后再使用husbandRepo.save(husband)这样才能在数据库找到对应的wife进行对应。执行完毕后,数据库表:
这里写图片描述
两个实例都已存储完成。

然后再考虑另一种情况,绝对的钟情与对方在现实生活中是不可能的,也就是说,作为主导方的Husband,也有可能将别人的妻子据为己有,所以数据库中已经绑定wife是否能被其他Husband绑定呢?

        Husband husband = new Husband("Mike");
        Wife wife = wifeRepo.findByName("Rose");
        husband.setWife(wife);
        husbandRepo.save(husband);

新建一个Husband Mike,将Mike的wife设置为Jack的wife Rose,程序竟然没报错,数据库如图:
这里写图片描述
Jack和Mike的妻子竟然是同一个,Jack的老婆被抢走了竟然不知道这一点,多可怕!!!所以关于OneToOne映射,我的理解是对关系维护端而言的,一个Husband最多有且仅有一个wife,但是被管理的wife而言,对这一切一无所知(最起码在单向OneToOne是这样)。

再考虑如果我们删除掉Husband而保留对应的Wife会怎样?在这里我们新建一对夫妇,老王和老王媳妇儿

        Husband husband = new Husband("老王");
        Wife wife = new Wife("老王媳妇儿");
        husband.setWife(wife);
        wifeRepo.save(wife);
        husbandRepo.save(husband);

这里写图片描述

然后删除老王

Husband husband = husbandRepo.findByName("老王");
        husbandRepo.delete(husband.getHid());

程序未报错,再看数据库
这里写图片描述
老王已经不在了,而老王媳妇儿还在

下一步,删除wife而保留对应的husband,在执行下面代码之前,先把老王恢复回来吧!

        Wife wife = wifeRepo.findByName("老王媳妇儿");
        wifeRepo.delete(wife.getWid());

然后程序提示
这里写图片描述
因为老王的外键还保留着老王媳妇儿的主键,你把老王媳妇儿删除了,老王找不到媳妇儿了,他表示不开心(一句商量都没有就把媳妇儿丢了),所以为了安抚各方,我们应该把老王的媳妇儿外键给清掉,这样就可以顺利删除老王媳妇儿了,代码很简单,去遍历所有Husband就行了,因为从老王媳妇儿那里无从知晓谁是她丈夫。

        Wife wife = wifeRepo.findByName("小六媳妇儿");

        List<Husband> list = husbandRepo.findAll();
        for(int i=0;i<list.size();i++){
            Husband husband = list.get(i);
            if(husband.getWife().equals(wife)){
                husband.setWife(null);
                husbandRepo.save(husband);
            }

        }

        wifeRepo.delete(wife.getWid());

这样就顺利删除了作为被维护端的Wife,所以在单向的OneToOne中,关系被维护端要负责与被维护端的关系建立,同时也要负责与被维护端的关系解除,而被维护端对这些一无所知。

有没有感觉每次新建一次联系都要先使用wifeRepo.save()再使用husbandRepo.save()比较繁琐?既然使用了框架,为啥还这么麻烦?下一步只需要稍稍改动一个地方即可。修改Husband类

public class Husband implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer hid;

    @NonNull@Column(nullable = false)
    private String name;

    @OneToOne(targetEntity = Wife.class,cascade = {CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "wife_id",referencedColumnName = "wid")
    private Wife wife;

    public Husband(){
    }

    public Husband(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "hid=" + hid +
                ", name='" + name + '\'' +
                ", wife=" + wife +
                '}';
    }
}

仔细对比,发现仅仅是在@OneToOne注解后加了cascade策略,CascadeType.PERSIST代表级联保存,CascadeType.REMOVE代表级联删除,所以现在可以在保存Husband的时候保存Wife,在删除Husband时候删除Wife了

        //加入级联持久化,Husband可以直接把Wife保存,不需要先保存Wife
        Husband husband = new Husband("Tom");
        Wife wife = new Wife("Jerry");
        husband.setWife(wife);
        husbandRepo.save(husband);

        //加入级联删除,删除Husband
        Husband husband = husbandRepo.findByName("Tom");
        husbandRepo.delete(husband.getHid());

OK,单向OneToOne的主要问题已经解决,下面是对双向OneToOne的一些测试。

双向OneToOne
双向OneToOne其实是在原来单向的基础上,使得被维护端也可以知道与自己有关那一方的信息,在原来单向的工程的基础上只需修改一点,打开Wife类,为其增加一个字段Husband,

    @OneToOne(mappedBy = "wife")
    private Husband husband;

mappedBy表明了关系被维护端,这样我们就可以通过Wife得到对应的husband。

下面进行测试
先来一组夫妻的新建:

        Husband husband = new Husband("Jack");
        Wife wife = new Wife("Rose");
        husband.setWife(wife);
        husbandRepo.save(husband);

可以发现每次新建都是通过关系维护端Husband来保存Wife,能不能通过Wife来保存Husband呢?编写代码

        Husband husband = new Husband("Barney");
        Wife wife = new Wife("Robin");
        wife.setHusband(husband);
        wifeRepo.save(wife);

结果如图
这里写图片描述
可见二者都可以保存,但是Husband方的外键无值,也就是说Barney和Robin并未建立夫妻的联系。后来想到如果在Wife加上级联操作会不会能成功,所以做了如下改变:

给Wife添加级联

@OneToOne(mappedBy = "wife",cascade = {CascadeType.PERSIST,CascadeType.REMOVE})
    private Husband husband;

测试代码

        Husband husband = new Husband("Marshall");
        Wife wife = new Wife("Lily");
        wife.setHusband(husband);
        wifeRepo.save(wife);

结果如图
这里写图片描述
仍然不能建立关系,所以关系的建立还是要依赖关系维护端来操作。

最后一个问题,既然关系被维护端可以知道对应的维护端,那么如果两个Husband来共同映射一个Wife会怎样?

        Husband husband = new Husband("Ted");     this is fine
        Wife wife = new Wife("Tracy");
        husband.setWife(wife);
        husbandRepo.save(husband);

        wife = wifeRepo.findByName("Tracy");
        husband = wife.getHusband();
        System.out.print("Tracy's wife is "+husband);

        husband = husbandRepo.findByName("Barney");
        husband.setWife(wife);
        husbandRepo.save(husband);

        husband = husbandRepo.findByName("Barney");
        Wife wife1 = husband.getWife();
        System.out.println("Barney's wife is "+wife1);

我们先建立一对夫妻Ted和Tracy,然后设置Barney的妻子也为Tracy

这里写图片描述

然后从Wife获得Husband信息

        wife = wifeRepo.findByName("Tracy");
        husband = wife.getHusband();
        System.out.print("Tracy's wife is "+husband);

结果
这里写图片描述
错误信息指出,有多个行的Wife外键是4,所以多个Husband绑定一个Wife在双向OneToOne中会爆出错误。

总结上述

  1. 无论单向还是双向,关系的保存都需要关系维护段来进行操作
  2. 所谓一对一映射,其实是关系维护端的映射关系,关系维护端确实只关联一个被维护端,但是被维护端和几个维护段关联就不得而知了
  3. 关系维护端可以主动修改关系,而被维护端只能被动接受
  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值