原文:http://blog.youkuaiyun.com/u012163684/article/details/52574269
最近学习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 +
'}';
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
接下来新建两个数据接入层接口,有人喜欢命名为 ××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
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 +
'}';
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
仔细对比,发现仅仅是在@OneToOne注解后加了cascade策略,CascadeType.PERSIST代表级联保存,CascadeType.REMOVE代表级联删除,所以现在可以在保存Husband的时候保存Wife,在删除Husband时候删除Wife了
Husband husband = new Husband("Tom");
Wife wife = new Wife("Jerry");
husband.setWife(wife);
husbandRepo.save(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")
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中会爆出错误。
总结上述
- 无论单向还是双向,关系的保存都需要关系维护段来进行操作
- 所谓一对一映射,其实是关系维护端的映射关系,关系维护端确实只关联一个被维护端,但是被维护端和几个维护段关联就不得而知了
- 关系维护端可以主动修改关系,而被维护端只能被动接受