一、Hibernate关联关系
1 . 实体之间的关系
- 泛化关系
通过对象之间的继承来实现 - 关联关系
通过一个对象持有另一个对象的实现来实现
类与类之间最常见的关系就是关联关系
2 . 关联关系
- 一对多
- 多对一
- 多对多
- 一对一
二、多对一关系
1.数据库表
drop table if exists t_emp;
drop table if exists t_dept;
create table t_dept(
id int primary key auto_increment,
name varchar(200)
)engine=Innodb default charset=utf-8;
create table t_emp(
id int primary key auto_increment,
name varchar(200),
dept_id int,
foreign key(dept_id) references t_dept(id)
)engine=Innodb default charset=utf-8;
#插入数据
insert into t_emp(name) values('d1'),('d2'),('d3');
insert into t_dept(name,dept_id) values('e1',1);
insert into t_dept(name,dept_id) values('e2',2);
insert into t_dept(name,dept_id) values('e3',3);
insert into t_dept(name,dept_id) values('e4',1);
insert into t_dept(name,dept_id) values('e5',1);
insert into t_dept(name,dept_id) values('e6',1);
insert into t_dept(name,dept_id) values('e7',2);
多对一关系:多个员工可以在同一个部门
多方----------->员工
一方----------->部门
2 . 配置方式
定义员工实体类
public Class Emp implements Serializable(
private Integer id;
private String name;
private Dept dept; //在多方定义一方的引用
//省略get\set方法
定义部门实体类
public Class Dept implements Serializable(
private Integer id;
private String name;
//省略get\set方法
编写Emp.hbm.xml映射文件
<?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映射文件、通过映射文件说明实体类和数据表的关联关系 -->
<hibernate-mapping package="cn.com.pojo">
<!-- 类和表的映射 -->
<class name="Emp" table="t_emp">
<!-- 表中列和类中属性的映射 -->
<!-- 主键 -->
<id name="id" column="id">
<!-- 指定主键生成策略、native是自增主键 -->
<generator class="native"></generator>
</id>
<!-- 普通字段 -->
<property name="name" column="name" type="java.lang.string"></property>
<!--
配置多对一的关系
name : 属性名
column : 列名、外检列
class : 属性的类型
-->
<!--
lazy :数据加载策略,可取值如下
false :立即加载关联数据,查询两次
proxy :懒加载,以代理对象的方式进行加载,默认值
no-proxy :懒加载,该方式在编译时需要进行字节码的增强,一般不怎么使用
-->
<many-to-one name="dept" column="dept_id" class="Dept" lazy="false"></many-to-one>
</class>
</hibernate-mapping>
编写Dept.hbm.xml映射文件
<?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映射文件、通过映射文件说明实体类和数据表的关联关系 -->
<hibernate-mapping package="cn.com.pojo">
<!-- 类和表的映射 -->
<class name="Dept" table="t_dept">
<!-- 表中列和类中属性的映射 -->
<!-- 主键 -->
<id name="id" column="id">
<!-- 指定主键生成策略、native是自增主键 -->
<generator class="native"></generator>
</id>
<!-- 普通字段 -->
<property name="name" column="name" type="java.lang.string"></property>
</class>
</hibernate-mapping>
3 . 编写测试类
//get/load查询
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
Emp emp = session.load(Emp.Class,1);
//打印员工所在的部门名称
System.out.println(emp.getDept().getName());
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
3 . get/load查询
3.1默认不查询关联信息,只有在访问时才会查询
调用load/get查询对象时,默认只会查询当前类对应的表,不会查询关联表,称为关联数据延迟加载,只有当第一次访问时才会进行数据的查询。Session关闭之后会抛出NoSession异常。
3.2解决No Session问题
解决方式:
- 使用lazy=“false”
- 使用fetch=“join”
3.3 lazy配置
```
<!--
lazy :数据加载策略,可取值如下
false :立即加载关联数据,查询两次
proxy :懒加载,以代理对象的方式进行加载,默认值
no-proxy :懒加载,该方式在编译时需要进行字节码的增强,一般不怎么使用
-->
<many-to-one name="dept" column="dept_id" class="Dept" lazy="false"></many-to-one>
```
3.4 fetch配置
```
<!--
fetch :抓取数据,可取值如下
join :立即加载关联数据,使用连接查询,只查询一次
注意:lazy配置无效,总是会立即加载数据使用连接查询
select :懒加载,会再次执行一次select查询,默认值
-->
<many-to-one name="dept" column="dept_id" class="Dept" fetch="join"></many-to-one>
```
4 . HQl单表查询
//HQL单表查询
public static void main(String[] args){
Emp emp = null;
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
String hql = "from Emp em where em.id=:id";
Query query = session.createQuery(hql);
query.setInteger("id":1);
emp = query.uniqueREsult();
//打印员工所在的部门名称
System.out.println(emp.getName());
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
System.out.println(emp.getDept().getName());
}
结论:对于HQL查询,lazy配置项有效,fetch配置项无效
5 . HQL联接查询
- left join : 只做连接操作,不查关联数据,根据lazy配置项择机进行再次查询
- left join fetch : 既做连接操作,也查关联数据,lazy配置无效,一次性将数据全部查出
//HQL多表连接查询
public static void main(String[] args){
Emp emp = null;
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
//1.HQL中的联接查询与SQl在写法上有所不同,连接的是类的属性,不是类
//2.HQL查询不能省略 select 部分
//3.left join 只做连接操作,不查关联数据
//4.left join fetch 既做连接操作,也查关联数据
String hql = "select em from Emp em left join em.dept fetch ed where em.id=:id";
Query query = session.createQuery(hql);
query.setInteger("id":1);
emp = (Emp)query.uniqueREsult();
//打印员工所在的部门名称
System.out.println(emp.getName());
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
System.out.println(emp.getDept().getName());
}
6 . 增、删、改基本操作
6.1基本操作
添加操作
-
多方对象中的一方为null,或持久态或游离态
正常保存
-
多方对象中的一方为临时态
报错:org.hibernate.TransientObjectException:object references an unsaved transient instanc
解决方法:
1).转化为持久态 2).配置casade (casade:级联)
<!-- cascade:级联操作 none :不进行级联操作,默认值 all : 对所有操作进行级联操作(insert/delete/update) save-update :执行保存和更新时执行级联操作 delete : 执行删除时,执行级联操作,主要用于一对多操作 --> <many-to-one name="dept" column="dept_id" class="Dept" fetch="join" cascade="save-update"></many-to-one>
//添加操作
public static void main(String[] args){
Emp emp = null;
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
//Emp emp = new Emp(null,"e11",null);
//session.save(emp);
//Dept dept = new Dept();
//dept.setId(1); //游离态对象
//Emp emp = new Emp(null,"e12",dept);
//session.save(emp);
//报错:外键约束问题
//Dept dept = new Dept();
//dept.setId(4); //瞬时态对象
//Emp emp = new Emp(null,"e13",dept);
//session.save(emp);
//Dept dept = (Dept)session.load(Dept.Class,1);//持久态
//Emp emp = new Emp(null,"e14",dept);
//session.save(emp);
//报错:引用了未保存的瞬时态的对象
//dept = new Dept();
//dept.setName("d4"); //方式一:转换成持久态对象
//session.save(dept); //持久态
//Emp emp = new Emp(null,"e14",dept);
//session.save(emp);
dept = new Dept();
dept.setName("d5"); //方式二:使用cascade,在配置文件中配置cascade="save-update"
Emp emp = new Emp(null,"e14",dept);
session.save(emp);
Query query = session.createQuery(hql);
query.setInteger("id":1);
emp = (Emp)query.uniqueREsult();
//打印员工所在的部门名称
System.out.println(emp.getName());
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
System.out.println(emp.getDept().getName());
}
更新操作
//更新操作
public static void main(String[] args){
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
Emp emp = (Emp)session.load(Emp.Class,1);
//Dept dept = (Dept)session.Load(Demp.Class,2);//持久态
//emp.setDept(dept);
//游离态update
//Dept dept = new Dept();
//dept.setId(3);
//emp.setDept(dept);
//持久态update
//Dept dept = (Dept)session.Load(Demp.Class,2);//持久态
//dept.setName("new d2");
//emp.setDept(dept);
//Dept dept = new Dept();//瞬时态insert
//dept.setName("d6");
//emp.setDept(dept);
//session.update(emp);//不需要调用update方法,会进行脏检查并同步更新数据库
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
}
删除操作
//HQL多表连接查询
public static void main(String[] args){
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
//级联删除雇员所在的部门,如果该部门没有其他员工,则正常删除,否则报错(外键约束)
Emp emp = (Emp)session.load(Emp.Class,1);
session.delete(emp);
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
}
二、一对多
一方------------->班级
多方------------->学生
1 . 创建表:t_class t_student
创建类实体:CLazz Student
在CLazz中添加属性:
private Set<Student> students = new HashSet<Student>(); //表示一对多关联关系
编写Clazz.hbm.xml、Student.hbm.xml
在CLazz.hbm.xml中添加一对多配置
<!--
配置一对多关系
name:属性名
table:属性关联的表
cacscade:级联操作
none
all
save-update
delete:级联删除,删除过程
1.将所有引用一方数据的外键全部设置为null(将CLazz对象对应的所有Student对象的class_id设置weinull)
2.删除对应集合中的数据(将Clazz中的set集合中的student删除)
3.删除自己对应的数据(将Clazz对象删除)
-->
<set name="students" table="t_student">
<!-- 关联的列,外键列,即关联表t_student中的哪个字段关联到t_class表-->
<key column="class_id"></key>
<!-- 关联的类,属性的类型-->
<one-to-many class="Student">
</set>
2 . 编写测试类
//查询操作
public static void main(String[] args){
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
CLazz clazz = session.load(CLazz.Class,1);
System.out.println(clazz.getName);
System.out.println("======================");
for(Student student:clazz.getStudents()){
System.out.println(student.getName);//延时加载,在xml中配置lazy="false"立即加载,fetch="join"联接查询
}
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
}
//添加操作
public static void main(String[] args){
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
//CLazz clazz = new Clazz(null,"cls4");
//session.save(clazz);//添加班级
CLazz clazz = new Clazz(null,"cls45);
Set<Student> suts = new HashSet<Student>();
stus.add(new Student(null,"tom"));
stus.add(new Student(null,"jacck"));
//将学生放到班级中:
//1).在班级中设置级联操作cascade="save-update",保存班级时,同时保存学生
clazz.setStudents(stus);
session.save(c);
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
}
删除操作
//删除操作
public static void main(String[] args){
try(){
Session session = HibernateUtil.getSession();
TransationManager.beginTransation();
//删除时,如果配置了级联操作,班级学生全部删除,否则删除班级
//CLazz clazz = (Clazz)session.load(CLazz.Class,1);
//session.delete(clazz);
//只会删除班级,班级中所有学生的外键设置为null
//CLazz clazz = new Clazz();
//clazz.setId(1);
//session.delete(clazz);
CLazz clazz = new Clazz();
clazz.setId(2);
Student sut = new Studnt();
stu.setId(2);
Set<Student> suts = new HashSet<Student>();
stus.add(stu);
clazz.setStudents(stus);
session.delete(clazz);
TransationManager.commit();
}catch(Exception e){
TransationManager.rollBack();
e.printStackTace();
}finally{
HibernateUtil.close();
}
}
3.Inverse配置
inverse 反转 一般指控制权的反转
<!--
inverse : 反转
false : 一方维护关联关系,默认值
true : 由对方维护关联关系,由多方维护,一方放弃对set集合的维护
-->
<set name="students" table="t_student" inverce="true">
<!-- 关联的列,外键列,即关联表t_student中的哪个字段关联到t_class表-->
<key column="class_id"></key>
<!-- 关联的类,属性的类型-->
<one-to-many class="Student">
</set>
4 . 双向一对多的关系
前面我们配置的均为单项关联关系
- 单项多对一
- 单项一对多
如果同时配置了单向一对多和单向多对一,则就是 双向一对多 或 双向多对一
inverse 只能在set中配置,一般都会配置,因为关联关系由多方维护更合适。
Clazz clazz = new Clazz(null,"cls5");
Student sut1 = new Student(null,"tom");
Student sut2 = new Student(null,"jack");
//将学生添加到班级中
Set<Student> suts = new HashSet<Student>();
suts.add(stu1);
suts.add(stu2);
clazz.setStudents(stus);
//指定学生在哪个班级
stu1.setClazz(clazz);
stu2.setClazz(clazz);
session.save(clazz);
注意:关于栈溢出的问题
Exception in thread "main" java.lang.StackOverflowError
原因:因为重写了关联实体的toString()方法,并且包含关联属性,当调用toString()方法时会导航查询关联对象,关联对象会导航查询对方,最终导致栈溢出。
解决:一般不建议重写toString()方法,或者重写toString()方法不要包含关联属性
三、多对多关系
关系型数据库中如何实现多对多的关系?
- 使用中间表,将多对多的关系转化为两个一对多
- 用中间表来维护多对多关系和数据
1.数据库表
学生表:t_stu 、 课程表:t_course 、学生选课表: t_stu_cource
多对多关系:一个学生可以选择多门课程、一个课程也可以被多个学生选择
多方------>学生
多方------>课程
2 . 配置方式
创建实体类 Student 、Course (注意:要在各个实体类中加上对方的集合)
在hbm.xml中加上配置
<!--
Student.hbm.xml配置多对多
-->
<set name="courses" table="t_stu_cource">
<!-- 关联到当前Student的外键列-->
<key cloumn="stu_id"></key>
<!-- class指定属性的类型、cloumn用来指定关联的列,外键列-->
<many-to-many class="Course" column="course_id"></many-to-many>
</set>
<!--
Course.hbm.xml配置多对多
-->
<set name="stus" table="t_stu_cource">
<key column="course_id"></key>
<many-to-many class="Student" column="stu_id"></many-to-many>
</set>
3 . 基本操作
//查询操作
Student stu = sesion.get(Student.class,1);
System.out.println(stu.getName());
System.out.println("========================");
for(Course c : stu.getCourse){
//查看学生选修了那些课程
System.out.println(c.getName());
}
//添加操作
Student stu1 = new Student(null,"tom");
Student stu2 = new Student(null,"jack");
Course c1 = new Course(null,"java");
Course c2 = new Course(null,"Html5");
//tom\jack同时选择了java课程
c1.getStudents().add(stu1);
c1.getStudents().add(stu2);
stu1.getCourse().add(c1);
stu2.getCourse().add(c1);
//jack 还选择了Html5
c2.getStudents().add(stu2);
stu2.getCourse().add(c2);
//保存
session.save(c1);
session.save(c2);
//希望在保存课程时级联保存学生,在课程中多对多配置 cascade="save-update"
//设置inverse="true",将关联关系管理交给学生,不设置将会插入重复数据(双方都会维护关联关系,各自插入一次)
四、一对一关系
- 两种实现方式
- 基于外键的一对一
- 基于主键的一对一
2.基于外键的一对一
2.1数据表
create table t_card(
id int primary key auto_increment,
name varchar(200)
)
create table t_person(
id int primary key auto_increment,
name varchar(200),
card_id int unique, 通过unique指定该字段唯一
forgien key(card_id) references t_card(id)
)
创建实体类 Person Card (在Person中添加属性Card、Card中添加属性Person)
编写hbm.xml
Person.hbm.xml
<!--
配置中一对一关系(使用many-to-one来配置多对一关联关系,通过unique变为一对一关系)
unique:将多对一变成一对一
-->
<many-to-one name="card" column="card_id" class="Card" unique="true" cascade="save-update" lazy="false"></many-to-one>
Card.hbm.xml
<!--
配置中一对一关系(使用one-to-one来配置一对一关联关系)
property-ref:引用的关联类的属性,使用外键关联
-->
<one-to-one name="person" class="Person" property-ref="card"></one-to-one>
3.测试类
//添加操作
Card card = new Card(null,"zs111111111");
Person person = new Person(null,"zhangsan");
card,setPerson(person);
person.serCard(card);
session.save(person);
//查询操作
Person person = session.get(Person.Class,1);
System.out.println(person.getName);
System.out.println("==================");
System.out.println(person.getCard().getName());
3.基于主键的一对一
基于数据库主键
create table t_card(
id int primary key auto_increment,
name varchar(200)
)
create table t_person(
id int primary key auto_increment,
name varchar(200),
forgien key(id) references t_card(id)
)
//hbm.xml配置两边都用<on-to-one>配置