专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  监控系统选型,一篇全搞定! ·  昨天  
Java编程精选  ·  WebSocket 的 6 ... ·  2 天前  
芋道源码  ·  Spring ... ·  2 天前  
芋道源码  ·  Hutool中的这些工具类,太实用了! ·  2 天前  
51好读  ›  专栏  ›  ImportNew

JavaEE – JPA(7):ORM的核心注解 – 关系类型

ImportNew  · 公众号  · Java  · 2016-12-18 20:08

正文

(点击 上方公众号 ,可快速关注)


来源:dm_vincent

链接:blog.csdn.net/dm_vincent/article/details/52877296


关系映射的处理绝对是一个JPA应用最为重要的部分之一。关系映射处理的好,不仅仅是建模上的成功,而且在程序性能上也会更胜一筹。关系映射处理的不好很容易造成程序性能底下,各种Bug频繁出现,而且这些Bug通常还会比较隐蔽,总是在关键时刻掉链子。我想这也是为什么很多开发人员说JPA入门容易,精通难得原因之一。因为关系确实不是那么好处理的,不仅需要对业务有相当深刻的见解,更需要对JPA提供的各种关系映射类型有入木三分的理解。


本文就尝试来理一理JPA中的各种关系映射类型。


关系的基本术语


在介绍JPA提供的几种关系映射类型之前,有必要先来学习一下关于关系的三个基本术语:角色,方向和基数。这对于理解关系的本质十分有帮助。


角色(Role)


所谓”一个巴掌拍不响”,一个关系不可能只有一个参与方,而且任何由多个参与方组成的关系必定都可以拆解成两两关系。因此,在这里我们也只考虑由两个参与方所组成的关系。比如我们常见的雇佣关系,就是企业和员工之间的一种关系,那么企业和员工在这层关系中就分别扮演着雇佣者和被雇佣者的角色。而且在现实生活中,一个人是可以同时扮演者多种角色的,比如被雇佣者在家庭中可以作为妻子/丈夫/孩子/父亲/母亲等角色,反映到程序中就是一个实体可以被别的实体所引用,一旦被引用,即代表了关系的建立。被引用的次数越多,那么就表示这个实体所承担的角色就越多。这一点很好理解,比如当Employee实体在Department实体中被引用,表示Employee承担的是部门员工的角色;当Employee实体在Payroll实体中被引用时,就表示Employee此时承担的是薪酬领取者的角色。


方向(Directionality)


除了角色之外,方向是关系的另一个要素。关系不会毫无缘由的诞生,总需要有一个角色来打破僵局,建立这层关系。那么主动建立的一方我们可以将其称为源角色(Source Role,简称Source),而被动响应的一方则可以被称为目标角色(Target Role,简称Target)。


反映到程序中,关系的方向指的就是主动引用,比如我们在Employee类型中引用Department类型,那么就是由Employee指向Department的一层关系,Employee扮演的是Source,Department扮演的是Target。如果在Department类型中也引用了Employee类型,那么就是由Department指向Employee的一层关系,Department扮演的是Source,Employee扮演的是Target。


因此如果互相引用,那关系就是一个双向关系了。就好比我知道我爸爸是谁,我爸爸也知道我是谁。


而单向关系在这个世界中其实更多一些,比如我知道马云是谁,而马云肯定不知道我是谁。又或者一个男生暗恋一个女生,这些都是单向关系。


基数(Cardinality)


关系的最后一个要素便是基数。所谓的基数实际上描述的是一个很简单的概念,比如法律上的一夫一妻制和一夫多妻制。即参与到关系的两个角色,在数量上的特征。一个部门可以有多个员工,而一个员工通常只属于一个部门。一个学生可以参与到多个社团,而一个社会也可以拥有多个学生作为其成员。


反映到程序中,就是引用类型是一个单值类型,还是一个集合类型的区别。如果在Employee类型中引用Department类型,通常只会引用声明一个department实体。但是反过来再Department类型中引用其Employee类型的时候,通常会使用一个集合来表示其下所有的Employee。


关系映射


基础概念介绍完毕,下面开始进入正题。


首先,根据关系中目标角色的数量,可以将关系简单分为两种:


  1. 单值映射(Single-Valued Mapping)


  2. 集合映射(Collection-Viewed Mapping)


单值映射


所谓单值映射,就是目标角色的基数(Cardinality)等于1。也就意味着存在两种情况:


一对一(One-to-One)


典型的例子比如,Employee类型(雇员)和Workspace类型(工位)之间的关系。此时使用JPA提供的@OneToOne注解进行描述:


@Entity

public class Employee {

@Id

private int id;

private String name;

@OneToOne

@JoinColumn(name="WSPACE_ID")

private Workspace workspace;

// ...

}

@Entity

public class Workspace {

@Id

private int id;

private String location;

}


上述代码是一对一单向关系的示例代码。其中出现了一个名为@JoinColumn的注解,可以将这个注解理解成外键。而这个外键的列名则是通过@JoinColumn注解中的name属性进行声明。同时,还需要注意的是当@OneToOne和@JoinColumn一起使用的时候,这个外键所在的列实际上还需要满足唯一性的约束。因为每个Workspace的实例实际上是被唯一的一个Employee实例所独占的,所以在该外键列中不可能存在相等的值。


那么一对一双向关系如何用JPA提供的注解进行声明呢?比如下面这一段代码,Employee不仅引用了Workspace类型,Workspace中也同时引用了Employee类型:


@Entity

public class Employee {

@Id

private int id;

private String name;

@OneToOne

@JoinColumn(name="WSPACE_ID")

private Workspace workspace;

// ...

}

@Entity

public class Workspace {

@Id

private int id;

private String location;

@OneToOne(mappedBy = "workspace")

private Employee employee;

// ...

}


注意在上述的Workspace类中,也使用了@OneToOne注解来声明一个从Workspace指向Employee的关系。但是这里并没有使用@JoinColumn注解来声明外键的相关信息。也就是说,上述实体类对应的数据库表中并不会含有引用Employee类型的外键列。这是JPA中规定的对于双向一对一关系的映射方式。尤其注意@OneToOne注解中的mappedBy属性,这个属性的值实际上是关系中源角色(Employee)一方引用目标角色(Workspace)一方的引用名称,加入我们把Employee类中的workspace改成ws,那么相应的Workspace类中@OneToOne的mappedBy属性也需要被改成ws。这种定义方式,保证了所定义的双向关系是由参与的两个@OneToOne来完成的。


如果不考虑规范的话,更加直观地定义双向一对一的方式应该是这样的:


@Entity

public class Employee {

@Id

private int id;

private String name;

@OneToOne

@JoinColumn(name="WSPACE_ID")

private Workspace workspace;

// ...

}

@Entity

public class Workspace {

@Id

private int id;

private String location;

@OneToOne

@JoinColumn(name="E_ID")

private Employee employee;

// ...

}


这种定义方式当然可以,但是这样定义的两个一对一关系之间就没有了联系,它们实际上定义了两个单向的一对一关系。JPA不会认为这两个一对一关系实际上共同构成了一个双向的一对一关系。体现在表结构上,就是在Workspace对应的表结构中,也会有一个外键列用来引用Employee的相应记录。


因此,如果你希望定义的是一个双向的一对一关系,还是遵守JPA的规范,正确地使用@OneToOne注解及其mappedBy属性和用于定义外键列的@JoinColumn注解吧。


最后补充一点,由于@JoinColumn是用来定义外键关系的,那么拥有该注解的一方可以被称为关系中的所有方(Owning Side),而被引用的一方则被称为反转方(Inverse Side)。所以如果是定义双向关系的话,在反转方一般就需要使用mappedBy属性了。







请到「今天看啥」查看全文