关联关系是用到的最多的一种关系,非常重要,在内存中反映为实体关系,映射到DB中主键外键关系,实体间的关联,即对外键的维护,关联关系的发生,即对外键数据的改变。
在这里就不赘述什么是外键什么是主键了。
一丶关联的数量
实体对象间的关联从数量上可以划分为:
- 1:1(一对一)
- 1:N(一对多)
- N:1(多对一)
- M:N(多对多)
二丶关联属性
Java代码实体定义中,声明另一个实例类类型或其集合类型的属性,称为关联属性。
public class Department{ private Integer DepartNo; private String DepartName; private Set<Employee> employee;//这个就是关联的属性 }
三丶关联方向
(1)单向关联
指具有关系关联的实体对象间的加载与访问关系是单向的,只有一个实体对象可以加载和访问对方,但是对方看不到另一方的。
(2)双向关联
指具有关系关联的实体对象间的加载与访问时双向的,即,任何一方均可加载和访问另一方。
四丶级联操作
级联操作分为:级联保存,级联删除,级联更新……
如果现在有部门表和员工表,两个表之间是关联的。那么当添加部门的时候,也会将这个部门的员工添加到员工表,当添加员工的时候也会将员工添加到部门表中。
删除,更新同理。
五丶关联关系维护
(1)Java代码上的关联关系维护
通过实体类来维护
(2)数据库上的关联关系维护
通过外键来维护
当我们操作数据(例如插入save)的时候都会先操作对方表的数据:
如果我们插入的是单方的数据,它底层会先插入多方的数据(无外键),然后插单方的数据。所以最后外键使用update更新。
如果我们向多方插入数据,应该先插入单方数据,然后再插入多方,所以数据库里面使用inert语句维护关联关系。所以最后外键使用insert更新。
说了那么多我们接下来就来使用Hibernate实现这些关联:
六丶一对多单向关联(“一”能加载和访问“多”)
第一步:
创建两个关联的实体类
//Employee实体类 public class Employee { private Integer id; private String ename; public Employee() { super(); } public Employee(String ename) { super(); this.ename = ename; } } //这里省略了setter,getter方法
//部门实体类 public class Department { private Integer id; private String dname; private Set<Employee> employees; public Department() { super(); } public Department(String dname) { super(); this.dname = dname; } } //这里省略了setter,getter方法
第二步:
配置映射文件
Employee映射文件:
Department映射文件
在Department映射文件里面的Employee成员需要使用set标签,要关联表的外键名字(这样自动建出来的表会多一个外键名字叫做dept),最后还要指明关联关系为一对多,并且支出“多”对应的实体类路径。
第三步:
别忘了在主配置文件中注册映射文件
第四步:
完成测试
public class TestOneToMany { public static void main(String[] args) { Session session = hbnUtil.getSession(); session.beginTransaction(); //新建三个员工对象 Employee employee1 = new Employee("张三"); Employee employee2 = new Employee("李四"); Employee employee3 = new Employee("王二"); //新建一个Employee类型的HashSet Set<Employee> employees = new HashSet<Employee>(); //将员工保存到一个HashSet中 employees.add(employee1); employees.add(employee2); employees.add(employee3); //新建一个部门对象 Department dept = new Department("宣传部"); //将员工保存到部门对象中 dept.setEmployees(employees); //将员工持久化到DB中(这个时候还没有外键) session.save(employee1); session.save(employee2); session.save(employee3); //将dept持久化到DB中(这个时候Hibernate为Employee表Update上了外键) session.save(dept); //提交事务 session.getTransaction().commit(); } }
大家看上面的测试方法是不是觉得这个代码很臃肿?那接下来我们来进行优化一下:
有同学会有疑问,之前不是提到过可以进行级联操作吗,为什么持久化dept之前还要持久化employee呢?
对,所以我们可以对配置文件的改进来使得可以实现级联操作,我们来改一下:
<!--在“一”Department的配置文件set标签内添加cascade(我这里填的是all表示所有级联操作,包含保存和删除等等)--> <set name="employees" cascade="all"> <key column="dept"/> <one-to-many class="entity.Employee"/> </set>
添加过后我们测试代码就可以删去一下三行代码:
session.save(employee1);
session.save(employee2);
session.save(employee3);
下面是我们在数据库中生成的结果:
这样就完成了我们的一对多关系单向关联映射。
说完了一对多单向关联,我们再来看看一对多双向关联。
===================================================
七丶一对多双向关联(同时也是多对一双向关联)
双向关联实在单向关联的基础上进行修改:
第一步:
修改实体类,修改“多”的实体类,我们之前单向关联的时候Employee实体类里面没有dept(部门)字段,数据库中生成的dept是由Hibernate根据Department的映射文件来生成的dept字段。
所以我们要在Employee实体类中添加dept字段。
第二步:
修改Employe配置文件:
<class name="entity.Employee" > <id name="id"> <generator class="native"/><!-- increment --> </id> <property name="ename"/> <many-to-one name="dept" class="entity.Department" cascade="all" column="dept"/> <!--name指的是Employee实体类中的字段;column="dept"这个指的是Department配置文件里的column="dept" 如果都相同(Employeedept字段和Department所指定的column值相同)可以省略column="dept",否则不能省略,省略过后会Hibernate会根据这里指定的name值在数据库里多生成一个字段,下面举个栗子--> </class>
Department配置文件,column指定为dept1
Employee配置文件,没有指定column
然后我们运行测试类,看看生成结果:
结果多生成了一个。
但是如果我们再Department 的column指定的就是dept,且Employee的字段为dept,Employee配置文件中也是指定的dept,那么久只会生成一个dept。
上面说了那么多也没将测试类给出,其实测试类和之前我们的单向关联是相同的,但是值得一提的是双向关联真的可以双向了。
Employee employee1 = new Employee("张三"); Set<Employee> employees = new HashSet<Employee>(); employees.add(employee1); Department dept = new Department("宣传部"); //dept.setEmployees(employees); employee1.setDept(employees); session.save(employee1); /*可以看出这次我们保存的是employee1,但是我们同样将两张表的信息都更新了(在保存之前,一定要调用employee1.setDept(employees)方法)*/
处了上面的以外还要提到一个属性:inverse,双向关联中这个属性可以存在于一方的配置文件set标签中,默认是:inverse=“false”,表示“一”方维护关联关系,当将inverse设置为“true”时,“一”方将放弃关联关系。
==========================================
八丶自关联
自关联就是自己和自己关联,例如一个员工表,里面不仅有普通员工,也有非普通员工(Boss)。所以Boss和员工就在同一张Employee表中,但是不同的是,每个普通员工都有一个Boss,但是Boss没有Boss。
接下来我们看看Hibernate中我们是怎么样实现自关联的:
第一步:
定义实体类:
public class Employee { private Integer id; private String name; private Employee boss; private Set<Employee> commonemp; public Employee() { super(); } public Employee(String name) { this.name = name; } }//省略了getter,setter方法
第二步:
配置映射文件
<class name="entity.Employee" > <id name="id"> <generator class="native"/><!-- increment --> </id> <property name="name"/> <many-to-one name="boss" class="entity.Employee" cascade="all" column="boss"/> <set name="commonemp" cascade="all"> <key column="boss"/> <one-to-many class="entity.Employee"/> </set> </class>
第三步:
Test
//新建一个老板 Employee boss = new Employee("老板"); //新建一个普通员工 Employee commonEmp = new Employee("员工"); //获得set集合,并将员工添加到老板对象中 Set<Employee> set = new HashSet<Employee>(); set.add(commonEmp); //持久化 session.save(boss);
结果:
因为我们保存的是boss,所以它的boss项为null。
因为这里是双向自关联的,我们再来测试下插入员工:
//新建一个老板 Employee boss = new Employee("老板"); //新建一个普通员工 Employee commonEmp = new Employee("员工"); //为员工指定老板 commonEmp.setBoss(boss); //持久化 session.save(commonEmp);
结果:
这样就插入了员工,他的boss栏为1,即他的老板为id为1。
九丶多对一单向关联
说完一对多(多对一)双向关联过后就很好做多对一单向关联了。
第一步:
只要将“一”这一方的set集合删除(连带setter,getter方法,不删也不影响),还有配置文件里的set标签。
Department实体类:
public class Department { private Integer id; private String name; public Department() { super(); } public Department(String name) { this.name = name; //省略了getter,setter方法 }
Department(一方映射配置文件)
<class name="entity.Department" > <id name="id"> <generator class="native"/><!-- increment --> </id> <property name="name"/> </class>
Employee实体类:
public class Employee { private Integer id; private String name; private Department dept; public Employee() { super(); } public Employee(String name) { super(); this.name = name; } }
Employee(多方)映射配置文件
<class name="entity.Employee" > <id name="id"> <generator class="native"/><!-- increment --> </id> <property name="name"/> <many-to-one name="dept" class="entity.Department" cascade="all" /><!--因为不用自动生成主键,并且和Department里面column设置一致,所以省略column--> </class>
Test:
Department dept = new Department("宣传部"); Employee emp = new Employee("张三"); emp.setDept(dept); session.save(emp);
我们这就完成了多对一单向关联。
最后一个便是我们本篇文章的难点了(多对多关系关联映射):
===================================================
十丶多对多单向关联
什么是多对多?
现在有多个学生和多门课程,一个学生可以选几门课程,同时一门课也可以被多个学生选。
例如现在有两个学生张三,李四。有三门课,Java SE,JavaEE,Android。张三选JavaSE和JavaEE,李四选JavaSE和Android。
这样的关系叫做多对多关系。
我们都知道多对多关系在数据库中是通过中间表来完成它们之间的关联的,那么Hibernate怎么关联,我们接着往下看:
//学生实体类 public class Student { private Integer sid; private String sname; private Set<Course> courses; public Student() { super(); } public Student(String sname) { super(); this.sname = sname; } }//省略了setter,getter方法
//课程实体类 public class Course { private Integer cid; private String cname; public Course() { super(); } public Course(String cname) { this.cname = cname; } }//省略了getter,setter方法
<!--Course映射配置文件--> <class name="entity.Course" > <id name="cid"> <generator class="native"/><!-- increment --> </id> <property name="cname"/> </class>
<!--Student映射配置文件--> <class name="entity.Student" > <id name="sid"> <generator class="native"/><!-- increment --> </id> <property name="sname"/> <set name="courses" cascade="all" table="middle"> <!--name为Student内的courses,这里面要加一个table属性,创建一个关联表middle--> <key column="sid"/><!--设置关联表middle的主键,在数据库中middle的主键同时也是外键和student关联--> <many-to-many class="entity.Course" column="cid"/><!--这里设置和Course多对多关系,在数据库中column="cid"为关联表的外键,和course关联--> </set> </class>
Test:
//新建课程 Course c1 = new Course("Java SE"); Course c2 = new Course("Java EE"); Course c3 = new Course("Android"); //新建学生 Student s1 = new Student("张三"); Student s2 = new Student("李四"); //新建两个集合 Set<Course> courses1 = new HashSet<Course>(); Set<Course> courses2 = new HashSet<Course>(); //将课程放入集合 courses1.add(c1); courses1.add(c2); courses2.add(c1); courses2.add(c3); //为学生添加课程集合 s1.setCourses(courses1); s2.setCourses(courses2); //持久化 session.save(s1); session.save(s2);
数据库结果:
这里的一对多单向关联有学生维持关联关系,从数据库结果我们也可以看出,学生是“一”,middle是“多”,课程是“一”,所以多对多就是由两个一对多组成。student和middle表关系中,sid是外键也是主键。
course和middle表关系中,cid是外键。
十一丶多对多双向关联
双向关联是在单向关联的基础上改一下就好:
将Course实体类添加一个Set集合
配置文件修改成这样:
上面配置文件key为当前实体类的cid这样的配置文件你会发现数据库中新建的middle表有两个主键:
这样和单向关联就有很大的差别了,这里的middle在和course和student的关系中主键是不同的(当然你也可以认为是相同的,毕竟两个都是主键,这就是和单向关联的一个差别)
Test:
//新建课程 Course c1 = new Course("Java SE"); Course c2 = new Course("Java EE"); Course c3 = new Course("Android"); //新建学生 Student s1 = new Student("张三"); Student s2 = new Student("李四"); //新建三个集合,放学生 Set<Student> c1s = new HashSet<Student>(); Set<Student> c2s = new HashSet<Student>(); Set<Student> c3s = new HashSet<Student>(); //将选c1课程的放c1s中 c1s.add(s1); c1s.add(s2); //将选c2课程的放入c2s中 c2s.add(s1); //将选c3课程的放入c3s中 c3s.add(s2); //为课程关联学生 c1.setStudents(c1s); c2.setStudents(c2s); c3.setStudents(c3s); //持久化课程 session.save(c1); session.save(c2); session.save(c3);
测试的结果和单向的结果是一样的。
双向关联,双方都可以维护关联关系。