hibernate基础知识二

来源:互联网 发布:spark 推荐算法 编辑:程序博客网 时间:2024/06/03 22:11
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1 Hibernate_day02
今日任务
案例一:使用 Hibernate 完成 CRM 客户管理中查询所有客户的操作
教学导航
教学目标
教学方法 案例驱动法
案例一: 使用 Hibernate 完成 CRM 客户管理中
查询所有客户的操作
1.1案例需求:
1.1.1 需求描述
CRM 系统中客户信息管理模块功能包括:
新增客户信息
客户信息查询
修改客户信息
删除客户信息
1.2相关知识点:
1.2.1 Hibernate 持久化类的编写规则
1.2.1.1 什么是持久化类:
Hibernate 是持久层的 ORM 映射框架, 专注于数据的持久化工作。 所谓的持久化, 就是将内存
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
中的数据永久存储到关系型数据库中。 那么知道了什么是持久化, 什么又是持久化类呢? 其实所谓
的持久化类指的是一个 Java 类与数据库表建立了映射关系, 那么这个类称为是持久化类。 其实你可
以简单的理解为持久化类就是一个 Java 类有了一个映射文件与数据库的表建立了关系。 那么我们在
编写持久化类的时候有哪些要求呢? 接下来我们来看一下:
1.2.1.2 持久化类的编写规则:
我们在编写持久化类的时候需要有以下几点需要注意:
持久化类需要提供无参数的构造方法。 因为在 Hibernate 的底层需要使用反射生成类的实
例。
持久化类的属性需要私有, 对私有的属性提供公有的 get set 方法。 因为在 Hibernate
层会将查询到的数据进行封装。
持久化类的属性要尽量使用包装类的类型。因为包装类和基本数据类型的默认值不同, 包
装类的类型语义描述更清晰而基本数据类型不容易描述。 举个例子:
假设表中有一列员工工资, 如果使用 double 类型, 如果这个员工工资忘记录入到系统中, 系统会将默认值0
存入到数据库, 如果这个员工工资被扣完了, 也会向系统中存入 0.那么这个 0 就有了多重含义, 而如果使用包装类
类型就会避免以上情况, 如果使用 Double 类型, 忘记录入工资就会存入 null, 而这个员工工资被扣完了, 就会存
0, 不会产生歧义。
持久化类要有一个唯一标识OID 与表的主键对应。 因为 Hibernate 中需要通过这个唯一标
OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否是同一个对象的,
在关系型数据库的表中是通过主键区分是否同一条记录。 那么 Hibernate 就是通过这个 OID
来进行区分的。 Hibernate 是不允许在内存中出现两个 OID 相同的持久化对象的。
持久化类尽量不要使用final 进行修饰。 因为 Hibernate 中有延迟加载的机制, 这个机制中
会产生代理对象, Hibernate 产生代理对象使用的是字节码的增强技术完成的, 其实就是产
生了当前类的一个子类对象实现的。 如果使用了 final 修饰持久化类。 那么就不能产生子类,
从而就不会产生代理对象, 那么 Hibernate 的延迟加载策略( 是一种优化手段) 就会失效。.
持久化类我们已经可以正常编写了, 但是在持久化类中需要有一个唯一标识OID与表的主键去
建立映射关系。 而且主键一般我们是不会让客户手动录入的, 一般我们是由程序生成主键。 那么
Hibernate 中也提供了相应的主键生成的方式, 那么我们来看下 Hibernate 的主键生成策略。
1.2.2 Hibernate 主键生成策略
1.2.2.1 主键的类型:
在讲解 Hibernate 的主键生成策略之前, 先来了解两个概念, 即自然主键和代理主键, 具体如下:
自然主键: 把具有业务含义的字段作为主键, 称之为自然主键。 例如在customer表中, 如
果把 name 字段作为主键, 其前提条件必须是: 每一个客户的姓名不允许为null, 不允许客
户重名, 并且不允许修改客户姓名。 尽管这也是可行的, 但是不能满足不断变化的业务需
求, 一旦出现了允许客户重名的业务需求, 就必须修改数据模型, 重新定义表的主键, 这
给数据库的维护增加了难度。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
代理主键: 把不具备业务含义的字段作为主键, 称之为代理主键。 该字段一般取名为“ID”,
通常为整数类型, 因为整数类型比字符串类型要节省更多的数据库空间。 在上面例子中,
显然更合理的方式是使用代理主键。
1.2.2.2 Hibernate的主键生成策略:
Hibernate 中, 提供了几个内置的主键生成策略, 其常用主键生成策略的名称和描述如下
主键生成策略
名称 描述
increment 用于 longshort、 或int类型, 由 Hibernate 自动以递增的方式生
成唯一标识符, 每次增量为 1。 只有当没有其它进程向同一张表中插入
数据时才可以使用, 不能在集群环境下使用。 适用于代理主键。
identity 采用底层数据库本身提供的主键生成标识符, 条件是数据库支持
自动增长数据类型。 在 DB2MySQLMS SQL ServerSybase
HypersonicSQL 数据库中可以使用该生成器, 该生成器要求在数据库中
把主键定义成为自增长类型。 适用于代理主键。
sequence Hibernate 根据底层数据库序列生成标识符。 条件是数据库支持序
列。 适用于代理主键。
native 根据底层数据库对自动生成表示符的能力来选择 identity
sequencehilo三种生成器中的一种, 适合跨数据库平台开发。 适用于
代理主键。
uuid Hibernate 采用 128 位的 UUID 算法来生成标识符。 该算法能够在
网络环境中生成唯一的字符串标识符, 其 UUID 被编码为一个长度为
32 位的十六进制字符串。 这种策略并不流行, 因为字符串类型的主键
比整数类型的主键占用更多的数据库空间。 适用于代理主键。
assigned java 程序负责生成标识符,如果不指定 id 元素的 generator 属性,
则默认使用该主键生成策略。 适用于自然主键。
1.2.3 Hibernate 的持久化对象的三种状态
1.2.3.1 持久化对象三种状态的概述
了解了主键的生成策略之后, 我们可以进一步来了解持久化类了。 Hibernate 为了更好的来管理
持久化类, 特将持久化类分成了三种状态。 在 Hibernate 中持久化的对象可以划分为三种状态, 分别
是瞬时态、 持久态和脱管态, 一个持久化类的实例可能处于三种不同状态中的某一种, 三种状态的
详细介绍如下。
1、 瞬时态(transient
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
瞬时态也称为临时态或者自由态, 瞬时态的实例是由 new 命令创建、 开辟内存空间的对象,
存在持久化标识 OID( 相当于主键值) , 尚未与 Hibernate Session 关联, 在数据库中也没有记录,
失去引用后将被 JVM 回收。瞬时状态的对象在内存中是孤立存在的, 与数据库中的数据无任何关联,
仅是一个信息携带的载体。
2、 持久态(persistent
持久态的对象存在持久化标识OID, 加入到了 Session 缓存中, 并且相关联的 Session 没有关
闭, 在数据库中有对应的记录, 每条记录只对应唯一的持久化对象, 需要注意的是, 持久态对象是
在事务还未提交前变成持久态的。
3、 脱管态(detached
脱管态也称离线态或者游离态, 当某个持久化状态的实例与 Session 的关联被关闭时就变成了脱
管态。 脱管态对象存在持久化标识 OID, 并且仍然与数据库中的数据存在关联, 只是失去了与当前
Session 的关联, 脱管状态对象发生改变时 Hibernate 不能检测到。
1.2.3.2 区分对象的三种状态:
为了帮助大家更好的理解持久化对象的三种状态, 接下来通过具体的案例来演示持久化对象的
三种状态。
@Test
// 测试三种状态
public voiddemo1(){
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();//瞬时态对象: 没有持久化标识OID, 没有被session
管理.
customer.setCust_name("老王");
Serializable id = session.save(customer);//持久态对象:有持久化标识OID, 被session
管理.
tx.commit();
session.close();
System.out.println(customer);//托管态对象:有持久化标识OID, 没有被session 管理
}c
ustomer 对象由 new 关键字创建, 此时还未与 Session 进行关联, 它的状态称为瞬时态; 在执行
session.save(customer)操作后, book 对象纳入了 Session 的管理范围, 这时的 customer 对象变成了
持久态对象, 此时 Session 的事务还没有被提交; 程序执行完 commit()操作并关闭了 Session 后,
customer 对象与 Session 的关联被关闭, 此时 customer 对象就变成了脱管态。
至此, 便介绍完持久化对象的三种状态。 在 Hibernate 的学习过程中, 三种状态的特征变化以及
各自的特点是非常重要, 有助于更好的理解 Hibernate, 所以希望大家能够掌握。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.2.3.3 Hibernate持久化对象的三种状态转换
已经了解了 Hibernate 持久化对象的三种状态, 然而这三种状态是可以通过一系列方法进行转换
的, 首先来看一下持久化对象的状态演化图
从图中可以看出, 当一个对象被执行 new 关键字创建后, 该对象处于瞬时态; 当对瞬时态对象
执行 Session save()saveOrUpdate()方法后, 该对象将被放入 Session 的一级缓存( 在后面会有介
绍) , 对象进入持久态; 当对持久态对象执行 evict()close()clear()操作后, 对象进入脱管态; 当
直接执行 Session get()load()find()iterate()等方法从数据库里查询对象时, 查询到的对象也
处于持久态; 当对数据库中的纪录进行 update()saveOrUpdate()以及 lock()等操作后, 此时脱管态
的对象就过渡到持久态; 由于瞬时态和脱管态的对象不在 session 的管理范围, 所以会在一段时间后
JVM 回收。
持久化对象的三种状态可以通过调用 Session 中的一系列方法实现状态间的转换, 具体如下:
1、 瞬时态转换到其他状态
通过前面学习可知, 瞬时态的对象由 new 关键字创建, 瞬时态对象转换到其他状态总结如下:
瞬时态转换为持久态: 执行 Sessionsave()saveOrUpdate()方法。
瞬时态转换为脱管态: 为瞬时态对象设置持久化标识 OID
由于持久化对象状态演化图中没有涉及到瞬时态转换到脱管态的情况, 这里做下简要的说明,
在前面学习中可知, 脱管态对象存在 OID, 但是没有 Session 的关联, 也就是说脱管态和瞬时态的区
别就是 OID 有没有值, 所以可以通过为瞬时态对象设置 OID, 使其变成脱管态对象。
Customer customer = new Customer(); //瞬时态
customer.setCust_id(1); //脱管态
2、 持久态对象转换到其他状态
持久化对象可以直接通过 Hibernate Session get()load()方法, 或者 Query 查询从数据库
中获得, 持久态对象转换到其他状态总结如下:
持久态转换为瞬时态: 执行 Sessiondelete()方法, 需要注意的是被删除的持久化对象, 不
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
建议再次使用。
持久态转换为脱管态: 执行Sessionevict()close()clear()方法。evict ()方法用于清除一
级缓存中某一个对象; close()方法用于关闭Session, 清除一级缓存;clear()方法用于清除一
级缓存的所有对象。
3、 脱管态对象转换到其他状态
脱管态对象无法直接获得, 是由其他状态对象转换而来的, 脱管态对象转换到其他状态总结如
下:
脱管态转换为持久态: 执行Sessionupdate ()saveOrUpdate()lock()方法。
脱管态转换为瞬时态: 将脱管态对象的持久化标识OID设置为null
由于持久化对象状态演化图中没有涉及到脱管态转换到瞬时态的情况, 这里做下简要的说明,
跟瞬时态转换到脱管态的情况相似, 脱管态和瞬时态的区别就是 OID 有没有值, 所以可以通过将脱
管态对象的 OID设置为null, 使其变成瞬时态对象。 例如在session.close()操作后, 加入代码
customer.setCust_id(null);customer对象将由脱管态转换为瞬时态。
1.2.3.4 持久态对象能够自动更新数据库
我们之前已经介绍了持久化对象的三种状态了, 其实我们主要去研究持久态对象就够了, 持久
态对象其实有一个非常重要的特性: 持久态对象可以自动更新数据库。
@Test
// 测试持久化类的持久态对象有自动更新数据库的能力
public voiddemo2(){
Session session= HibernateUtils.openSession();
Transaction tx=session.beginTransaction();
// 获得持久态的对象.
Customer customer=session.get(Customer.class, 1l);//持久态对象.
customer.setCust_name("王五");
// session.update(customer); //不用手动调用update方法就可以更新
tx.commit();
session.close();
}
执行测试我们会发现, 我们并没有手动调用 update方法,Hibernate就可以将数据自动更新了。
持久态对象就有这样的一个功能。 持久态对象之所以有这样的功能其实都依赖了Hibernate的一级缓
存。 接下来我们就开始学习 Hibernate的一级缓存。
1.2.4 Hibernate 的一级缓存
缓存是计算机领域非常通用的概念。 它介于应用程序和永久性数据存储源(如硬盘上的文件或者
数据库)之间, 其作用是降低应用程序直接读写永久性数据存储源的频率, 从而提高应用的运行性能
缓存中的数据是数据存储源中数据的拷贝。 缓存的物理介质通常是内存。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
Hibernate 的缓存分为一级缓存和二级缓存,Hibernate的这两级缓存都位于持久化层, 存储的都
是数据库数据的备份。 其中第一级缓存为 Hibernate的内置缓存, 不能被卸载。 接下来围绕Hibernate
的一级缓存进行详细地讲解。
1.2.4.1 什么是Hibernate的一级缓存
Hibernate 的一级缓存就是指Session缓存, Session缓存是一块内存空间, 用来存放相互管理的
java 对象, 在使用Hibernate查询对象的时候, 首先会使用对象属性的 OID值在Hibernate的一级缓
存中进行查找, 如果找到匹配 OID值的对象, 就直接将该对象从一级缓存中取出使用, 不会再查询
数据库; 如果没有找到相同 OID值的对象, 则会去数据库中查找相应数据。 当从数据库中查询到所
需数据时, 该数据信息也会放置到一级缓存中。 Hibernate的一级缓存的作用就是减少对数据库的访
问次数。
Session接口的实现中包含一系列的Java集合,这些Java集合构成了Session缓存。 只要
Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期。 固一级缓存也被称为
Session基本的缓存。
Hibernate 的一级缓存有如下特点:
当应用程序调用Session接口的 save()update()saveOrUpdate时, 如果 Session缓存中没
有相应的对象, Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存
中去。
当调用Session接口的 load()get()方法, 以及Query 接口的list()iterator()方法时, 会判
断缓存中是否存在该对象, 有则返回, 不会查询数据库, 如果缓存中没有要查询对象, 再
去数据库中查询对应对象, 并添加到一级缓存中。
当调用Sessionclose()方法时,Session缓存会被清空。
1.2.4.2 测试一级缓存:
我们已经大致了解了什么是一级缓存了, 那么一级缓存具体是否存在呢, 我们可以通过如下的
程序来证明一级缓存是存在的。
@Test
// 证明 Hibernate的一级缓存的存在:
public voiddemo1(){
Session session= HibernateUtils.openSession();
Transaction tx=session.beginTransaction();
Customer customer1=session.get(Customer.class, 1l);//马上发生一条sql查询1
客户.并将数据存入了一级缓存
System.out.println(customer1);
Customer customer2=session.get(Customer.class, 1l);//没有发生SQL语句从一级缓
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
存中获取数据.
System.out.println(customer2);
System.out.println(customer1==customer2);// true一级缓存缓存的是对象的地址.
tx.commit();
session.close();
}
以上代码中, 第一次执行 Sessionget()方法获取customer1对象时, 由于一级缓存中没有数
据, 所以 Hibernate会向数据库发送一条sql语句, 查询id等于1的对象; 当再次调用了Session
get()方法获取customer2对象时, 将不会再发送 sql语句, 这是因为customer2对象是从一级缓存中
获取的。
接下来, 验证一下代码的执行结果是否和描述的相一致。 在 Customer customer1 =
session.get(Customer.class, 1l);这一行设置断点, 用debug方式执行demo1()方法, 程序进入断点后点
击单步跳过( 或者按 F6) , 代码执行过System.out.println(customer1);语句后, 控制台的输出结果,
如下图所示。
输出结果
从上图的输出结果中可以看出, 执行第一次 session.get()方法后Hibernate向数据库发送了一条
select 语句, 这说明此时customer1对象是从数据库中查找的。 执行输出语句输出 customer1对象中
的数据后, 继续执行程序, 当执行到输出 customer2对象的代码处时, 控制台输出结果如下图所示。
输出结果
从上图的输出结果可以看出, customer2对象的查询结果被直接打印了, 说明第二次调用Session
对象的 get()方法没有向数据库发送select语句, 而是直接从一级缓存中获取 customer2对象。
之前我们介绍过 Hibernate的持久态对象能够自动更新数据库, 其实就是依赖了一级缓存, 那么
一级缓存为什么就可以去更新数据库了呢, 其实是因为一级缓存的一块特殊的区域就是快照区。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.2.4.3 一级缓存的内部结构:( 快照区)
Hibernate 向一级缓存放入数据时, 同时复制一份数据放入到Hibernate快照中, 当使用 commit()
方法提交事务时, 同时会清理 Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照
中的对象是否一致, 如果两个对象中的属性发生变化, 则执行update语句, 将缓存的内容同步到数
据库, 并更新快照; 如果一致, 则不执行update语句。Hibernate 快照的作用就是确保一级缓存中的
数据和数据库中的数据一致。
1.2.5 Hibernate 的事务控制
Hibernate 是对JDBC的轻量级封装, 其主要功能是操作数据库。 在操作数据库过程中, 经常会
遇到事务处理的问题, 那么我们接下来就来介绍 Hibernate中的事务管理。
在学习 Hibernate中的事务处理之前, 先来回顾一下什么是事务。
1.2.5.1 什么是事务
在数据库操作中, 一项事务( Transaction) 是由一条或多条操作数据库的SQL语句组成的一个
不可分割的工作单元。 当事务中的所有操作都正常完成时, 整个事务才能被提交到数据库中, 如果
有一项操作没有完成, 则整个事务会被回滚。
其实事务总结起来理解为: 逻辑上的一组操作, 组成这组操作的各个单元, 要么一起成功, 要
么一起失败。
1.2.5.2 事务的四个特性
事务有很严格的定义, 需要同时满足四个特性, 即原子性、 一致性、 隔离性、 持久性。 这四个
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
特性通常称之为 ACID特性, 具体如下:
原子性(Atomic) : 表示将事务中所做的操作捆绑成一个不可分割的单元, 即对事务所进
行的数据修改等操作, 要么全部执行, 要么全都不执行。
一致性(Consistency) : 表示事务完成时, 必须使所有的数据都保持一致状态。
隔离性(Isolation) : 指一个事务的执行不能被其它事务干扰。 即一个事务内部的操作及使
用的数据对并发的其他事务是隔离的, 并发执行的各个事务之间不能互相干扰。
持久性(Durability) : 持久性也称永久性(permanence) , 指一个事务一旦提交, 它对数
据库中数据的改变就应该是永久性的。 提交后的其他操作或故障不会对其有任何影响。
1.2.5.3 事务的并发问题
在实际应用过程中, 数据库是要被多个用户所共同访问的。 在多个事务同时使用相同的数据时,
可能会发生并发的问题, 具体如下。
1) 脏读: 一个事务读取到另一个事务未提交的数据。
2) 不可重复读: 一个事务读到了另一个事务已经提交的update的数据, 导致在同一个事务中
的多次查询结果不一致。
3) 虚读/幻读: 一个事务读到了另一个事务已经提交的 insert的数据, 导致在同一个事务中的
多次查询结果不一致。
1.2.5.4 事务的隔离级别
为了避免事务并发问题的发生, 在标准 SQL规范中, 定义了4个事务隔离级别, 不同的隔离级
别对事务的处理不同。
读未提交(Read Uncommitted1级) : 一个事务在执行过程中, 既可以访问其他事务未提
交的新插入的数据, 又可以访问未提交的修改数据。 如果一个事务已经开始写数据, 则另
外一个事务则不允许同时进行写操作, 但允许其他事务读此行数据。 此隔离级别可防止丢
失更新。
已提交读(Read Committed2级) : 一个事务在执行过程中, 既可以访问其他事务成功提
交的新插入的数据, 又可以访问成功修改的数据。 读取数据的事务允许其他事务继续访问
该行数据, 但是未提交的写事务将会禁止其他事务访问该行。 此隔离级别可有效防止脏读。
可重复读(Repeatable Read4级) : 一个事务在执行过程中, 可以访问其他事务成功提交
的新插入的数据, 但不可以访问成功修改的数据。 读取数据的事务将会禁止写事务( 但允
许读事务) , 写事务则禁止任何其他事务。 此隔离级别可有效的防止不可重复读和脏读。
序列化/串行化(Serializable8级) : 提供严格的事务隔离。 它要求事务序列化执行, 事
务只能一个接着一个地执行, 但不能并发执行。 此隔离级别可有效的防止脏读、 不可重复
读和幻读。
隔离级别 含义
READ_UNCOMMITTED 允许你读取还未提交的改变了的数据。 可能导致脏、 幻、 不可
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
重复读
READ_COMMITTED 允许在并发事务已经提交后读取。 可防止脏读, 但幻读和 不
可重复读仍可发生
REPEATABLE_READ 对相同字段的多次读取是一致的, 除非数据被事务本身改变。
可防止脏、 不可重复读, 但幻读仍可能发生。
SERIALIZABLE 完全服从ACID的隔离级别, 确保不发生脏、 幻、 不可重复读。
这在所有的隔离级别中是最慢的, 它是典型的通过完全锁定在事务
中涉及的数据表来完成的。
事务的隔离级别 , 是由数据库提供的 , 并不是所有数据库都支持四种隔离级别
MySQLREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READ
SERIALIZABLE ( 默认REPEATABLE_READ
OracleREAD_UNCOMMITTEDREAD_COMMITTEDSERIALIZABLE
( 默认 READ_COMMITTED
在使用数据库时候, 隔离级别越高, 安全性越高 , 性能越低 。
实际开发中, 不会选择最高或者最低隔离级别, 选择 READ_COMMITTEDoracle默认) 、
REPEATABLE_READ (mysql默认)
1.2.5.5 Hibernate中的事务管理
Hibernate中 , 可 以 通 过 代 码 来 操 作 管 理 事 务 , 如 通 过 “Transaction tx =
session.beginTransaction();” 开启一个事务; 持久化操作后, 通过“tx.commit();” 提交事务; 如果事
务出现异常, 又通过“ tx.rollback();” 操作来撤销事务( 事务回滚) 。
除了在代码中对事务开启, 提交和回滚操作外, 还可以在 Hibernate 的配置文件中对事务进行配
置。 配置文件中, 可以设置事务的隔离级别。 其具体的配置方法是在hibernate.cfg.xml文件中的
<session-factory>标签元素中进行的。 配置方法如下所示。
<!—
事务隔离级别
hibernate.connection.isolation = 4
1Read uncommitted isolation
2Read committed isolation
4Repeatable read isolation
8Serializable isolation
-->
<propertyname="hibernate.connection.isolation">4</property>
到这我们已经设置了事务的隔离级别, 那么我们在真正进行事务管理的时候, 需要考虑事务的
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
应用的场景, 也就是说我们的事务控制不应该是在 DAO层实现的, 应该在Service层实现, 并且在
Service 中调用多个DAO实现一个业务逻辑的操作。 具体操作如下显示:
其实最主要的是如何保证在 Service中开启的事务时使用的Session对象和DAO中多个操作使
用的是同一个 Session对象。
其实有两种办法可以实现:
可以在业务层获取到Session, 并将Session 作为参数传递给 DAO
可以使用ThreadLocal将业务层获取的 Session绑定到当前线程中, 然后在DAO中获取
Session 的时候, 都从当前线程中获取。
其实使用第二种方式肯定是最优方案, 那么具体的实现已经不用我们来完成了,Hibernate的内
部已经将这个事情做完了。 我们只需要完成一段配置即可。
Hibernate5 中自身提供了三种管理Session对象的方法
Session 对象的生命周期与本地线程绑定
Session 对象的生命周期与JTA事务绑定
Hibernate 委托程序管理Session对象的生命周期
Hibernate的配置文件中, hibernate.current_session_context_class属性用于指定Session管理
方式,可选值包括
threadSession对象的生命周期与本地线程绑定
jtaSession对象的生命周期与 JTA事务绑定
managedHibernate委托程序来管理 Session对象的生命周期
hibernate.cfg.xml中进行如下配置:
<!-- 配置session绑定本地线程-->
<propertyname="hibernate.current_session_context_class">thread</property>
hibernate 提供sessionFactory.getCurrentSession()创建一个sessionThreadLocal绑定方法。
HibernateUtil工具类中更改getCurrentSession方法:
//获取当前线程绑定的会话
public staticSession getCurrentSession(){
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
returnsessionFactory.getCurrentSession();
}
Hibernate中提供的这个与线程绑定的session可以不用关闭, 当线程执行结束后, 就会自
动关闭了。
案例二: 完成一对多的关联关系映射并操作
1.3案例需求:
1.3.1 需求描述
一个客户对应多个联系人, 单独在联系人管理模块中对联系人信息进行维护, 功能包括:
添加联系人、 修改联系人、 删除联系人。
添加联系人: 添加联系人时指定所属客户, 添加信息包括联系人名称、 联系电话等
修改联系人: 允许修改联系人所属客户、 联系人名称、 联系人电话等信息
删除联系人: 删除客户的同时删除下属的联系人, 可以单独删除客户的某个联系人
1.4相关知识点
1.4.1 表关系的分析:
Hibernate 框架实现了ORM的思想, 将关系数据库中表的数据映射成对象, 使开发人员把对数
据库的操作转化为对对象的操作, Hibernate的关联关系映射主要包括多表的映射配置、 数据的增加、
删除等。
数据库中多表之间存在着三种关系, 也就是系统设计中的三种实体关系。 如图所示。
从图可以看出, 系统设计的三种实体关系分别为: 多对多、 一对多和一对一关系。 在数据库中,
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
实体表之间的关系映射是采用外键来描述的, 具体如下。
1.4.1.1 表与表的三种关系:
【 一对多】
建表原则: 在多的一方创建外键指向一的一方的主键:
【 多对多】
建表原则: 创建一个中间表, 中间表中至少两个字段作为外键分别指向多对多双方的主键.
【 一对一】
建表原则有两种:
一种: 唯一外键对应: 假设一对一种的任意一方为多, 在多的一方创建外键指向一的一方的主键,
然后将外键设置为唯一。
二种: 主键对应: 一方的主键作为另一方的主键。
数据库表能够描述的实体数据之间的关系, 通过对象也可以进行描述, 所谓的关联映射就是将
关联关系映射到数据库里, 在对象模型中就是一个或多个引用。 在 Hibernate 中采用Java对象关系
来描述数据表之间的关系, 具体如图所示。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
从图可以看出, 通过一对一的关系就是在本类中定义对方类型的对象, 如A中定义B类类型的
属性 bB类中定义A类类型的属性a; 一对多的关系, 图中描述的是一个A对应多个B类类型的
情况, 需要在 A类以Set集合的方式引入B类型的对象, 在B类中定义A类类型的属性a; 多对多
的关系, 在 A类中定义B类类型的Set集合, 在B类中定义A类类型的Set集合, 这里用Set集合
的目的是避免了数据的重复。
以上就是系统模型中实体设计的三种关联关系, 由于一对一的关联关系在开发中不常使用, 所
以我们不单独讲解, 了解即可。 那么接下来我们就先来学习一下一对多的关系映射吧。
1.4.2 Hibernate 的一对多关联映射
1.4.2.1 创建表:
创建联系人表 cst_linkman
导入 crm/sql/crm_cst_linkman.sql
联系人表中存在外键( lkm_cust_id即所属客户id) , 外键指向客户表:
ALTER TABLE cst_linkman
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
ADD CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES
`cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
1.4.2.2 创建实体:
【 客户的实体】 :
public classCustomer {
privateLongcust_id;
privateStringcust_name;
privateStringcust_source;
privateStringcust_industry;
privateStringcust_level;
privateStringcust_phone;
privateStringcust_mobile;
// 一个客户有多个联系人: 客户中应该放有联系人的集合.
privateSet<LinkMan>linkMans=newHashSet<LinkMan>();
publicLong getCust_id() {
returncust_id;
}p
ublic voidsetCust_id(Longcust_id) {
this.cust_id=cust_id;
}p
ublicString getCust_name() {
returncust_name;
}p
ublic voidsetCust_name(Stringcust_name) {
this.cust_name=cust_name;
}p
ublicString getCust_source() {
returncust_source;
}p
ublic voidsetCust_source(Stringcust_source) {
this.cust_source=cust_source;
}p
ublicString getCust_industry() {
returncust_industry;
}p
ublic voidsetCust_industry(Stringcust_industry) {
this.cust_industry=cust_industry;
}p
ublicString getCust_level() {
returncust_level;
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
}p
ublic voidsetCust_level(Stringcust_level) {
this.cust_level=cust_level;
}p
ublicString getCust_phone() {
returncust_phone;
}p
ublic voidsetCust_phone(Stringcust_phone) {
this.cust_phone=cust_phone;
}p
ublicString getCust_mobile() {
returncust_mobile;
}p
ublic voidsetCust_mobile(Stringcust_mobile) {
this.cust_mobile=cust_mobile;
}p
ublicSet<LinkMan> getLinkMans() {
returnlinkMans;
}p
ublic voidsetLinkMans(Set<LinkMan>linkMans) {
this.linkMans=linkMans;
}
}
【 联系人的实体】 :
public class LinkMan {
private Longlkm_id;
private Stringlkm_name;
private Stringlkm_gender;
private Stringlkm_phone;
private Stringlkm_mobile;
private Stringlkm_email;
private Stringlkm_qq;
private Stringlkm_position;
private Stringlkm_memo;
// Hibernate是一个ORM的框架:在关系型的数据库中描述表与表之间的关系, 使用的是外键.开发语
言使用是 Java, 面向对象的.
private Customercustomer;
public Long getLkm_id() {
return lkm_id;
}p
ublic void setLkm_id(Longlkm_id) {
this.lkm_id=lkm_id;
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
}p
ublic String getLkm_name() {
return lkm_name;
}p
ublic void setLkm_name(Stringlkm_name) {
this.lkm_name=lkm_name;
}p
ublic String getLkm_gender() {
return lkm_gender;
}p
ublic void setLkm_gender(Stringlkm_gender) {
this.lkm_gender=lkm_gender;
}p
ublic String getLkm_phone() {
return lkm_phone;
}p
ublic void setLkm_phone(Stringlkm_phone) {
this.lkm_phone=lkm_phone;
}p
ublic String getLkm_mobile() {
return lkm_mobile;
}p
ublic void setLkm_mobile(Stringlkm_mobile) {
this.lkm_mobile=lkm_mobile;
}p
ublic String getLkm_email() {
return lkm_email;
}p
ublic void setLkm_email(Stringlkm_email) {
this.lkm_email=lkm_email;
}p
ublic String getLkm_qq() {
return lkm_qq;
}p
ublic void setLkm_qq(Stringlkm_qq) {
this.lkm_qq=lkm_qq;
}p
ublic String getLkm_position() {
return lkm_position;
}p
ublic void setLkm_position(Stringlkm_position) {
this.lkm_position=lkm_position;
}p
ublic String getLkm_memo() {
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
return lkm_memo;
}p
ublic void setLkm_memo(Stringlkm_memo) {
this.lkm_memo=lkm_memo;
}p
ublic Customer getCustomer() {
return customer;
}p
ublic void setCustomer(Customercustomer) {
this.customer=customer;
}
}
1.4.2.3 创建映射:
【 客户的映射】
<hibernate-mapping>
<classname="cn.itcast.hibernate.domain.Customer"table="cst_customer">
<idname="cust_id">
<generatorclass="native"/>
</id>
<propertyname="cust_name"length="32"/>
<propertyname="cust_source"column="cust_source"/>
<propertyname="cust_industry"column="cust_industry"/>
<propertyname="cust_level"column="cust_level"/>
<propertyname="cust_phone"column="cust_phone"/>
<propertyname="cust_mobile"column="cust_mobile"/>
<!--配置关联对象-->
<!--
set标签:
* name属性:多的一方的集合的属性名称.
-->
<setname="linkMans">
<!--
key标签:
* column属性:多的一方的外键的名称.
-->
<keycolumn="lkm_cust_id"></key>
<!--
one-to-many标签:
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
* class属性:多的一方的类全路径
-->
<one-to-manyclass="cn.itcast.hibernate.domain.LinkMan"/>
</set>
</class>
</hibernate-mapping>
使用 set集合来描述Customer.java类中的属性linkMans。 在Hibernate的映射文件中, 使用<set>
标签用来描述被映射类中的 Set集合,<key>标签的column属性值对应文件多的一方的外键名称,
Customer.java客户类中, 客户与联系人是一对多的关系,Hibernate 的映射文件中, 使用
<one-to-many>标签来描述持久化类的一对多关联, 其中class属性用来描述映射的关联类。
【 联系人映射】
<hibernate-mapping>
<classname="cn.itcast.hibernate.domain.LinkMan"table="cst_linkman">
<idname="lkm_id"column="lkm_id">
<generatorclass="native"/>
</id>
<propertyname="lkm_name"/>
<propertyname="lkm_gender"/>
<propertyname="lkm_phone"/>
<propertyname="lkm_mobile"/>
<propertyname="lkm_email"/>
<propertyname="lkm_qq"/>
<propertyname="lkm_position"/>
<propertyname="lkm_memo"/>
<!--配置关联对象: -->
<!--
many-to-one:标签.代表多对一.
* name :一的一方的对象的名称.
* class :一的一方的类的全路径.
* column :表中的外键的名称.
-->
<many-to-onename="customer"class="cn.itcast.hibernate.domain.Customer"
column="lkm_cust_id"/>
</class>
</hibernate-mapping>
<many-to-one>标签定义两个持久化类的关联, 这种关联是数据表间的多对一关联, 联系人与客
户就是多对一的关系, 所以用<many-to-one>标签来描述。<many-to-one>标签的name属性用来描述
customer LinkMan.java类中的属性的名称, class属性用来指定映射的类,column属性值对应表中
的外键列名。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.4.2.4 将映射添加到配置文件
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
<mapping resource="com/itheima/domain/LinkMan.hbm.xml"/>
1.4.2.5 编写测试代码:
@Test
// 保存一个客户和两个联系人:
public voiddemo1(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
// 创建一个客户
Customer customer=newCustomer();
customer.setCust_name("姜总");
// 创建两个联系人:
LinkMan linkMan1=newLinkMan();
linkMan1.setLkm_name("李秘书");
LinkMan linkMan2=newLinkMan();
linkMan2.setLkm_name("王助理");
// 建立关系:
customer.getLinkMans().add(linkMan1);
customer.getLinkMans().add(linkMan2);
linkMan1.setCustomer(customer);
linkMan2.setCustomer(customer);
session.save(customer);
session.save(linkMan1);
session.save(linkMan2);
transaction.commit();
}
在配置文件中添加了自动建表信息后, 运行程序时, 程序会自动创建两张表, 并且插入数据。
使用 Junit4运行demo1()方法后, 控制台输出结果, 如图所示
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
从图的输出结果可以看到, 控制台成功输出了三条 insert语句和两条update语句, 此时查询数
据库中两张表及表中的数据后, 查询结果如图所示。
从上图的查询结果可以看出, 数据表创建成功, 并成功插入了相应数据。 那么一个基本的一对
多的关联关系映射就已经配置好了。 以上我们的代码可以发现我们建立的关系是双向的, 即客户关
联了联系人, 同时联系人也关联了客户。
//客户关联了联系人
customer.getLinkMans().add(linkMan1);
customer.getLinkMans().add(linkMan2);
//联系人关联客户
linkMan1.setCustomer(customer);
linkMan2.setCustomer(customer);
这就是双向关联, 那么既然已经进行了双向的关联关系的设置, 那么我们还保存了双方, 那如
果我们只保存一方是否可以呢? 也就是说我们建立了双向的维护关系, 只保存客户或者只保存联系
人是否可以。 那么我们来进行一下测试。
@Test
// 保存操作只保存一个方向是否可以?
public voiddemo2(){
Session session= HibernateUtils.getCurrentSession();
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
Transaction transaction=session.beginTransaction();
// 创建一个客户
Customer customer=newCustomer();
customer.setCust_name("刘总");
// 创建两个联系人:
LinkMan linkMan1=newLinkMan();
linkMan1.setLkm_name("王秘书");
// 建立关系:
customer.getLinkMans().add(linkMan1);
linkMan1.setCustomer(customer);
session.save(customer);//瞬时对象异常,持久态的对象关联了一个瞬时态对象的异常.
//session.save(linkMan1);
transaction.commit();
}
我们执行这段代码, 会出现如下错误:
这样操作显然不行, 无论从那一方保存都会出现同样的异常: 瞬时对象异常, 一个持久态对象
关联了一个瞬时态对象, 那就说明我们不能只保存一方。 那如果我们就想只保存一个方向应该如何
进行操作呢, 那么我们可以使用 Hibernate的级联操作, 那么我们来看下Hibernate的级联吧。
1.4.3 一对多的相关操作:
级联操作是指当主控方执行保存、 更新或者删除操作时, 其关联对象( 被控方) 也执行相同的
操作。 在映射文件中通过对cascade属性的设置来控制是否对关联对象采用级联操作, 级联操作对各
种关联关系都是有效的。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.4.3.1 级联保存或更新
级联是有方向性的, 所谓的方向性指的是, 在保存一的一方级联多的一方和在保存多的一方级
联一的一方。
【 保存客户级联联系人】
首先要确定我们要保存的主控方是那一方, 我们要保存客户, 所以客户是主控方, 那么需要在
客户的映射文件中进行如下的配置。
然后我们就可以编写测试代码了, 代码如下:
@Test
// 级联保存: 只保存一边的问题.
// 级联是有方向性, 保存客户同时级联客户的联系人.
// Customer.hbm.xml中的<set>标签上配置cascade="save-update"
public voiddemo3(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
// 创建一个客户
Customer customer=newCustomer();
customer.setCust_name("刘总");
// 创建两个联系人:
LinkMan linkMan1=newLinkMan();
linkMan1.setLkm_name("王秘书");
// 建立关系:
customer.getLinkMans().add(linkMan1);
linkMan1.setCustomer(customer);
session.save(customer);
transaction.commit();
}
【 保存联系人候级联客户.
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
同样我们需要确定主控方, 现在我们的主控方是联系人。 所以需要在联系人的映射文件中进行
配置, 内容如下:
然后我们就可以编写代码进行测试了。 测试代码如下:
@Test
// 级联保存: 只保存一边的问题.
// 级联是有方向性, 保存联系人同时级联客户.
// LinkMan.hbm.xml中在<many-to-one>标签上配置cascade="save-update"
public voiddemo4(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
// 创建一个客户
Customer customer=newCustomer();
customer.setCust_name("刘总");
// 创建两个联系人:
LinkMan linkMan1=newLinkMan();
linkMan1.setLkm_name("王秘书");
// 建立关系:
customer.getLinkMans().add(linkMan1);
linkMan1.setCustomer(customer);
session.save(linkMan1);
transaction.commit();
}
到这我们已经可以看到级联保存或更新的效果了。 那么我们维护的时候都是双向的关系维护。
那么这种关系到底表述的是什么含义呢? 我们可以通过下面的测试来更进一步了解关系的维护( 对
象导航) 的含义。
1.4.3.2 测试对象的导航的问题
我们所说的对象导航其实就是在维护双方的关系。
customer.getLinkMans().add(linkMan1);
linkMan1.setCustomer(customer)
这种关系有什么用途呢? 我们可以通过下面的测试来更进一步学习 Hibernate
我们在客户和联系人端都配置了 cascade="save-update", 然后进行如下的关系设置。 会产生什么
样的效果呢?
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1 @Test
2 //测试对象导航和级联操作:
3 public voiddemo5(){
4 Session session= HibernateUtils.getCurrentSession();
5 Transaction transaction=session.beginTransaction();
6 //创建一个客户
7 Customer customer=newCustomer();
8 customer.setCust_name("刘总");
91
0 //创建三个联系人:
11 LinkMan linkMan1=newLinkMan();
12 linkMan1.setLkm_name("王秘书");
13 LinkMan linkMan2=newLinkMan();
14 linkMan2.setLkm_name("李秘书");
15 LinkMan linkMan3=newLinkMan();
16 linkMan3.setLkm_name("张助理");
17
18 //建立关系:
19 linkMan1.setCustomer(customer);
20 customer.getLinkMans().add(linkMan2);
21 customer.getLinkMans().add(linkMan3);
22
23 //条件是双方都配置了cascade="save-update"
24 session.save(linkMan1);//数据库中有几条记录?发送几条insert语句. 4
25 // session.save(customer); //发送几条insert语句? 3
26 // session.save(linkMan2); //发送几条insert语句? 1
27 transaction.commit();
28 }
我们执行第 24行的时候, 问会执行几条insert语句呢? 其实发现会有4insert语句的, 因为
联系人 1关联了客户, 客户又关联了联系人2和联系人3, 所以当保存联系人1的时候, 联系人1
是可以进入到数据库的, 它关联的客户也是会进入到数据库的(因为联系人一端配置了级联), 那么客
户进入到数据库以后, 联系人 2和联系人3也同样会进入到数据库(因为客户一端配置了级联), 所以
会有 4insert语句。
我们执行 25行的时候, 问会有几条insert语句? 这时我们保存的客户对象, 可以对象关联了联
系人 2和联系人3, 那么客户在保存进入数据库的时候, 联系人2和联系人3也会进入到数据库,
所以是 3insert语句。
同理我们执行 26行代码的时候, 只会执行1insert语句, 因为联系人2会保存到数据库, 但
是联系人 2没有客户客户建立关系。 所以客户不会保存到数据库。
到这我们应该更加理解了 Hibernate的这种关系的建立和级联的关系了。 那么级联还有那些操作
呢? 接下来我们来学习级联删除的操作。
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.4.3.3 Hibernate的级联删除
我们之前学习过级联保存或更新, 那么再来看级联删除也就不难理解了, 级联删除也是有方向
性的, 删除客户同时级联删除联系人, 也可以删除联系人同时级联删除客户( 这种需求很少) 。
原来 JDBC中删除客户和联系人的时候, 如果有外键的关系是不可以删除的, 但是现在我们使
用了 Hibernate, 其实Hibernate可以实现这样的功能, 但是不会删除客户同时删除联系人, 默认情况
Hibernate会怎么做呢? 我们来看下面的测试:
@Test
// 删除有关联关系的对象:(默认情况: 先将关联对象的外键置为NULL删除客户对象)
public voiddemo6(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
Customer customer=session.get(Customer.class, 1l);
session.delete(customer);
transaction.commit();
}
默认的情况下如果客户下面还有联系人, Hibernate会将联系人的外键置为null, 然后去删除客
户。 那么其实有的时候我们需要删除客户的时候, 同时将客户关联的联系人一并删除。 这个时候我
们就需要使用 Hibernate的级联保存操作了。
【 删除客户的时候同时删除客户的联系人】
确定删除的主控方式客户, 所以需要在客户端配置:
如果还想有之前的级联保存或更新, 同时还想有级联删除, 那么我们可以进行如下的配置:
配置完成以后我们可以来编写测试代码了, 代码如下:
@Test
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
// 级联删除:级联删除有方向性.
// 删除客户同时级联删除联系人.
// Customer.hbm.xmlset标签上配置cascade="delete"
public voiddemo7(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
// 级联删除: 必须是先查询在删除的.
// 因为查询到客户, 这个时候客户的联系人的集合中就会有数据.
Customer customer=session.get(Customer.class, 1l);
session.delete(customer);
transaction.commit();
}
【 删除联系人的时候同时删除客户.
同样我们删除的是联系人, 那么联系人是主控方, 需要在联系人端配置:
如果需要既做保存或更新有有级联删除的功能, 也可以如下配置:
编写测试代码如下:
@Test
// 级联删除:级联删除有方向性.
// 删除联系人同时级联删除客户.
// LinkMan.hbm.xmlmany-to-one标签上配置cascade="delete"
public voiddemo8(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
// 级联删除: 必须是先查询在删除的
LinkMan linkMan=session.get(LinkMan.class, 3l);
session.delete(linkMan);
transaction.commit();
}
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
1.4.3.4 双向关联产生多余的SQL语句
到这我们已经了解了 Hibernate级联的基本配置和使用。但是有些时候我们需要进行如下的操作:
数据库中记录如下:
客户表
联系人表:
需要将 2号联系人关联给2号客户。 也就是将2号李秘书这个联系人关联给2号刘总这个客户。
编写修改 2号客户关联的联系人的代码。
@Test
// 2号联系人关联的客户改为2号客户.
public voiddemo9(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();
Customer customer=session.get(Customer.class, 2l);
LinkMan linkMan=session.get(LinkMan.class, 2l);
linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);
transaction.commit();
}
运行该代码, 控制台会输出如下内容:
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
我们会发现执行了两次 update语句, 其实这两个update都是修改了外键的操作, 那么为什么发
送两次呢? 那么我们来分析一下产生这种问题的原因:
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
我们已经分析过了, 因为双向维护了关系, 而且持久态对象可以自动更新数据库, 更新客户的
时候会修改一次外键, 更新联系人的时候同样也会修改一次外键。 这样就会产生了多余的SQL, 那
么问题产生了, 我们又该如何解决呢?
其实解决的办法很简单, 只需要将一方放弃外键维护权即可。 也就是说关系不是双方维护的,
只需要交给某一方去维护就可以了。 通常我们都是交给多的一方去维护的。 为什么呢? 因为多的一
方才是维护关系的最好的地方, 举个例子, 一个老师对应多个学生, 一个学生对应一个老师, 这是
典型的一对多。 那么一个老师如果要记住所有学生的名字很难的, 但如果让每个学生记住老师的名
字应该不难。 其实就是这个道理。 所以在一对多中, 一的一方都会放弃外键的维护权( 关系的维护)。
这个时候如果想让一的一方放弃外键的维护权, 只需要进行如下的配置即可。
inverse 的默认值是false, 代表不放弃外键维护权, 配置值为true, 代表放弃了外键的维护权。
这个时候再来执行之前的操作:
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
这个时候我们会发现就不会出现上述的问题了, 不会产生多余的 SQL 了( 因为一的一方已经放
弃了外键的维护权) 。
那么这个问题我们已经解决了, 但是有很多同学对应 cascade inverse还是不是太懂, 我们可
以通过下面的案例区分 cascadeinverse的区别, 因为这两个参数我们以后的开发中会经常使用。
1.4.3.5 区分cascadeinverse:
@Test
// cascadeinverse
// cascade强调的是操作一个对象的时候, 是否操作其关联对象
// inverse强调的是外键的维护权.
public voiddemo10(){
Session session= HibernateUtils.getCurrentSession();
Transaction transaction=session.beginTransaction();

Customer customer=newCustomer();
customer.setCust_name("王总");
LinkMan linkMan=newLinkMan();
linkMan.setLkm_name("李秘书");
// Customer.hbm.xml中的set上配置cascade="save-update" inverse="true"
customer.getLinkMans().add(linkMan);
session.save(customer);//客户关联的联系人是否被保存到数据库,外键是啥?
transaction.commit();
}
这个时候我们会发现, 如果在 set集合上配置cascade="save-update" inverse="true"了, 那么执行
保存客户的操作, 会发现客户和联系人都进入到数据库了, 但是没有外键, 是因为配置了cascade
了所以客户关联的联系人会进入到数据库, 但是客户一端放弃了外键维护权, 所以联系人插入到数
据库以后是没有外键的。
0 0
原创粉丝点击