hibernate关联关系

本文详细介绍了Hibernate中的关联关系,包括实体间的泛化关系和关联关系,重点解析了一对多、多对一、多对多和一对一的关系配置、映射文件编写、测试类实现以及懒加载和Fetch策略。同时,讨论了双向关联、删除操作、栈溢出问题的解决方案和多对多关系的中间表实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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",将关联关系管理交给学生,不设置将会插入重复数据(双方都会维护关联关系,各自插入一次)

四、一对一关系

  1. 两种实现方式
  • 基于外键的一对一
  • 基于主键的一对一

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>配置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值