- 注:这是笔者在学习时的小小记录,只是为了对自己的知识查缺补漏,有可能有些地方写错,仅供参考
Hibernate的多表关系映射
- 简述
一般在数据库中,表与表之间很多时候都不是独立存在,而是存在着一定的关系,而关系是通过外键来建立的,一般有一对一关系,一对多关系,多对多关系,在hibernate中,同样支持建立这种关系,关系是通过关系映射文件建立在持久化类之间。
一对多关系的建立
首先要建立两个持久化类,在一的一方要有多的一方的对象集合,在多的一方要有一的一方的对象,然后要配置两个持久化类的关系映射文件,最后别忘了核心配置文件要加载关系映射文件。
- 持久化类代码实例
public class Employee {
private Long eid;
private String ename;
/*
* 在多的一方有一的一方的对象
*/
private Company company;
//省略getter和setter
}
public class Company {
private Long cid;
private String cname;
/*
* 在一的一方有多的一方的对象集合
*/
private Set<Employee> employees = new HashSet<Employee>();
//省略getter和setter
}
- 关系映射文件代码实例
- Employee的文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wzm.entity.Employee" table="employee">
<id name="eid" column="eid" length="20">
<generator class="native"></generator>
</id>
<property name="ename" column="ename" length="25"></property>
<!-- many-to-one表明这是多的一方
column:表中外键的字段名
class:所关联的一的一方的全类名
-->
<many-to-one name="company" column="cid" class="com.wzm.entity.Company"></many-to-one>
</class>
</hibernate-mapping>
- Company的文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wzm.entity.Company" table="company">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<!-- 在一对多关系中,在一的一方的映射文件中配置set标签
name:一的一方所关联的多的一方的属性名
inverse:为true,放弃外键的维护
cascade:设置级联方式
lazy:设置延迟加载,默认为true
-->
<set name="employees" inverse="true" cascade="save-update,delete" lazy="true">
<!-- key为外键,column:为表中外键的字段名 -->
<key column="cid"></key>
<!-- one-to-many表名这是一的一方,class为所关联的多的一方的全类名 -->
<one-to-many class="com.wzm.entity.Employee"/>
</set>
</class>
</hibernate-mapping>
一对多关系的操作
在一对多关系中,我们可以正常对对象进行增删改查操作,但是可能会导致一些问题,如SQL语句冗余(具体原因在案例注释中),这我们可以用放弃外键维护来解决,同时有时候普通操作太繁琐,我们可以通过级联来对对象进行操作。
- 普通操作的代码案例及问题
/*
* 一对多关系的普通保存的SQL冗余问题
*/
@Test
public void test1() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//创建一个company对象,属于一的一方
Company company = new Company();
company.setCname("byte");
//创建两个employee对象,属于多的一方
Employee e1 = new Employee();
e1.setEname("xiaoming");
Employee e2 = new Employee();
e2.setEname("daming");
//将多的一方关联到一的一方
company.getEmployees().add(e1);
company.getEmployees().add(e2);
//将一的一方关联到多的一方
e1.setCompany(company);
e2.setCompany(company);
/*
* 开始保存数据
* 注意:这里我们只保存三条数据,但是在控制台显示的SQL语句有五条
* 其中有三条正常的insert语句,还多出来两条update语句
* update employee set cid=? where eid=?
* 这是因为双方都在维护外键,因为在一的一方也就是company中有关联employee,
* 因此当保存company时,hibernate发现company中有关联employee,会认为employee存在表中,
* 并且会主动为employee的外键设置上与company对应的值,这一点可以通过只保存company来证明,
* 只保存company也会出现update语句,但是此时表中无employee的数据,此时就会报错
*/
session.save(company);
session.save(e1);
session.save(e2);
//提交事务
transaction.commit();
//关闭资源
session.close();
}
- 问题解决的案例
/*
* SQL冗余问题解决
*/
@Test
public void test2() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//创建一个company对象,属于一的一方
Company company = new Company();
company.setCname("byte");
//创建两个employee对象,属于多的一方
Employee e1 = new Employee();
e1.setEname("xiaoming");
Employee e2 = new Employee();
e2.setEname("daming");
//将多的一方关联到一的一方
company.getEmployees().add(e1);
company.getEmployees().add(e2);
//将一的一方关联到多的一方
e1.setCompany(company);
e2.setCompany(company);
/*
* 为了避免出现冗余的SQL语句,因此需要其中一方放弃对外键的维护,
* 在一对多的关系中,因为在数据库中外键字段一般存储在多的一方,
* 因此让一的一方放弃外键,需要在映射文件的set标签中添加inverse参数,值为true
* <set name="employees" inverse="true">
* 这样单独保存company也不会报错了
*/
session.save(company);
session.save(e1);
session.save(e2);
Company company2 = session.get(Company.class, 1L);
System.out.println(company2);
//提交事务
transaction.commit();
//关闭资源
session.close();
}
- 级联操作的案例
/*
* 一对多关系的级联保存
*/
@Test
public void test3() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//创建一个company对象,属于一的一方
Company company = new Company();
company.setCname("byte");
//创建两个employee对象,属于多的一方
Employee e1 = new Employee();
e1.setEname("xiaoming");
Employee e2 = new Employee();
e2.setEname("daming");
//将多的一方关联到一的一方
company.getEmployees().add(e1);
company.getEmployees().add(e2);
//将一的一方关联到多的一方
e1.setCompany(company);
e2.setCompany(company);
/*
* 有时候我们希望在保存一个对象的同时能主动把对象所关联的其他对象一起保存了
* 这时候我们就可以使用级联来操作,级联就是当我们在操作一对象时,
* 如果对象有关联其他对象,那么也会对这些对象执行同样的操作
* 我们得在操作的一方添加cascade参数,值可以有save-update,delete
* <set name="employees" inverse="true" cascade="save-update,delete">
* 级联双方都可添加,但是一般默认添加在一的一方
*
* 注意:级联的使用双方都得关联
* 将多的一方关联一的一方,如果多的一方不关联,虽然一的一方所关联的对象能保存到数据库,
* 但是所保存的对象的外键是空的,不会设置值
* company.getEmployees().add(e1);
* e2.setCompany(company);
*/
session.save(company);
//提交事务
transaction.commit();
//关闭资源
session.close();
}
多对多关系的建立
多对多的建立比一对多的建立复杂一点,但是熟能生巧,首先要建立两个持久化类,双方都要有对方的对象集合,然后要配置两个持久化类的关系映射文件,最后别忘了核心配置文件要加载关系映射文件。
- 持久化类代码实例
public class Student {
private Long sid;
private String sname;
/*
* 多对多- 双方都有对方的对象集合
*/
private Set<Teacher> teachers = new HashSet<>();
//省略getter和setter
}
public class Teacher {
private Long tid;
private String tname;
/*
* 多对多- 双方都有对方的对象集合
*/
private Set<Student> students = new HashSet<>();
//省略getter和setter
}
- 关系映射文件代码实例
- Student的文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wzm.entity.Student" table="student">
<id name="sid" column="sid" length="20">
<generator class="native"></generator>
</id>
<property name="sname" column="sname" length="25"></property>
<!-- 建立多对多的映射关系
name:自己所关联的set集合的属性名
table:数据库中的中间表名
inverse:放弃对外键的维护,任何一方放弃都可以
lazy:延迟加载,默认为true
-->
<set name="teachers" table="tea_stu_tab" inverse="true">
<!-- column:自己在中间表的外键字段名 -->
<key column="sid" ></key>
<!-- column:对方在中间表的外键字段名
class:对方的全类名
-->
<many-to-many column="tid" class="com.wzm.entity.Teacher"></many-to-many>
</set>
</class>
</hibernate-mapping>
- Teacher的文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wzm.entity.Teacher" table="teacher">
<id name="tid" column="tid" length="20">
<generator class="native"></generator>
</id>
<property name="tname" column="tname" length="25"></property>
<!-- 建立多对多的映射关系
name:自己所关联的集合的属性名
table:数据库中的中间表名
-->
<set name="students" table="tea_stu_tab">
<!-- column:自己在中间表的外键字段名 -->
<key column="tid"></key>
<!-- column:对方在中间表的外键字段名
class:对方的全类名
-->
<many-to-many column="sid" class="com.wzm.entity.Student"></many-to-many>
</set>
</class>
</hibernate-mapping>
多对多关系的操作
在一对多的操作中,就算双方不放弃外键的维护,对于数据的保存没有影响,但是在多对多中,如果没有一方放弃对外键的维护,在保存时会造成主键冲突而无法保存数据,在多对多关系中,也比较少使用级联操作,有时候还要避免级联操作。
- 多对多普通保存案例
/*
* 多对多的普通保存案例
*/
@Test
public void test4() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//为对象之间建立关系
Teacher teacher1 = new Teacher();
teacher1.setTname("老师一");
Teacher teacher2 = new Teacher();
teacher2.setTname("老师二");
Student student1 = new Student();
student1.setSname("学生一");
Student student2 = new Student();
student2.setSname("学生二");
teacher1.getStudents().add(student1);
teacher1.getStudents().add(student2);
teacher2.getStudents().add(student1);
student1.getTeachers().add(teacher1);
student1.getTeachers().add(teacher2);
student2.getTeachers().add(teacher1);
/*
* 在多对多的映射关系中,一般不使用级联操作,特别是要避免级联删除操作,因为这可能会导致数据丢失
* 级联的操作与一对多一样,同样需要参数cascade
* 同时注意:在多对多的映射关系中,必须要有一方放弃对外键的维护,任何一方放弃都可以,
* 如果双方都对外键进行维护,那么在保存时会出现中间表的主键冲突而报错
* 放弃外键的维护同样需要在set标签添加inverser=true
*<set name="teachers" table="tea_stu_tab" inverse="true">
*/
session.save(teacher1);
session.save(teacher2);
session.save(student1);
session.save(student2);
//提交事务
transaction.commit();
//关闭资源
session.close();
}
- 多对多常用操作
/*
* 多对多的对象操作
*/
@Test
public void test5() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
/* 为老师增加学生
Teacher teacher = session.get(Teacher.class, 1L);
Student student = session.get(Student.class, 1L);
teacher.getStudents().add(student);
*/
/* 为老师更换学生
Teacher teacher = session.get(Teacher.class, 1L);
Student student1 = session.get(Student.class, 1L);
Student student2 = session.get(Student.class, 2L);
teacher.getStudents().remove(student1);
teacher.getStudents().add(student2);
*/
//提交事务
transaction.commit();
//关闭资源
session.close();
}
对象导航查询与延迟加载
在hibernate的关系映射中,不管是一对一还是一对多还是多对多,它们都有着对象导航查询和延迟加载的特性,这些特性能够更好的简化我们的代码编写和提高程序效率。
- 代码实例
/*
* 对象导航查询与延迟加载
*/
@Test
public void test6() {
//获取session,自定义的工具类,详细见笔记一
Session session = HibernateUtil.openSession();
//开启事务
Transaction transaction = session.beginTransaction();
//查询一个老师教多少学生
/*
* 在关系映射中,当查询一个对象,hibernate会将该对象所关联的对象也一并查询出来,这叫对象导航查询
* 但是,关系映射的查询默认是有延迟加载的,只有用到才查,可以用参数lazy控制,默认为true
*/
Teacher teacher = session.get(Teacher.class, 1L);
/*
* 这里利用老师来得到老师所教的学生个数,所以用到了学生这个对象,hibernate会开始执行查询操作
*/
teacher.getStudents().size();
//提交事务
transaction.commit();
//关闭资源
session.close();
}