“Hibernate笔记 4:核心知识”的版本间差异
跳到导航
跳到搜索
第215行: | 第215行: | ||
Session 缓存清理的时机:(触发策略) | Session 缓存清理的时机:(触发策略) | ||
# '''提交前''':当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。 | # '''提交前''':当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。 | ||
#* 之所以在事务快结束时:一方面是因为可以减少访问数据库的频率,另一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间; | |||
# '''查询前''':当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。 | # '''查询前''':当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。 | ||
# '''flush''':当应用程序显示调用 Session 的 flush() 方法的时候。 | # '''flush''':当应用程序显示调用 Session 的 flush() 方法的时候。 |
2022年6月14日 (二) 08:07的版本
持久化类的编写规则
如果一个 Java 类与数据库表建立了映射关系,那么这个类称为是持久化类。 (该类有对应的映射文件与数据库表相关联)
编写规则:
- 持久化类衙要提供无参数构造方法。
- 因为在 Hibernate 的底层需要使用反射生成类的实例;
- 持久化类必须:属性私有,且提供公有的 get 和 set 方法。
- 因为在 Hibernate 底 层会将查询到的数据进行封装;
- 持久化类的属性要尽量使用包装类的类型。
- 因为包装类和基本数据类型的默认值不同,包装类的类型语义描述更消晰而基本数据类型不容易描述;
- 持久化类要有一个唯一标识 OID 与数据库表的主键相对应。
- Hibernate 通过 OID 区分在内存中的持久化类;
- 持久化类尽量不要使用 final 进行修饰。
- 因为 Hibernate 中有延迟加载的机制,这个机制中会产生【代理对象】,Hibernate 产生代理对象使用的是【字节码的增强技术】完成的,其实就是产生了当前类的一个子类对象来实现的。
- 如果使用了 final 修饰待久化类,那么就不能产生子类,从而不能产生【代理对象】,那么 Hibernate 的【延迟加载】策略就会失效。
此外,“从代码规范上讲,持久化对象是一定要实现序列化接口的,保证能在异构化系统或网络中进行数据传输。”
什么是 OID?
OID(object identifier,“对象标识符”),是 hibernate 用于区分两个对象(持久化类)是否是同一个对象的标识。
持久化类的 OID 用于与数据库表的主键相映射,所以一般不手动指定。
OID 在对象持久化之前是 null,持久化的时候 hibernate 或者我们手动指定一个 id(被插入到数据库当做主键,在 session中 当做索引),所以,需要保证 OID 与主键的一致性,比如:类型、长度等。
主键生成策略
Hibernate 的主键依据:不同的主键类型、不同的数据库,可以有不同生成策略。
主键类型:
- 自然主键:带有业务含义的主键(比如:学号,工作编号)。
- 如果不手动指定主键就提交缓存进行更新,则会报错!
- 主键生成策略:
- assinged:Hibernate不维护主键,开发人员需要手动设置主键;
- 如果不指定过元素的
<generator>
属性,则默认使用该主键生成策略。
- 如果不指定过元素的
- assinged:Hibernate不维护主键,开发人员需要手动设置主键;
- 代理主键:通过编码自动生成的,无业务含义的主键。
- 主键生成策略:
- increment:(由 Hibernate 提供)自动增长。
- 适用主键:short、int、long 类型的主键;
- 不适用与并发访问数据库;
- identity:(由数据库提供)自动增长。
- 适用主键:short、int、long 类型的主键;
- 适用数据库:支持自动增长的数据库(如:DB2、 MySQL、 MS SQL Server、 Sybase 和 HypersomcSQL);(不适用于 Oracle)
- sequence:(由数据库提供)序列。
- 适用主键:short、int、long 类型的主键;
- 适用数据库:支持序列的方式的数据库(如:Oracle、db2、sap、db、postgresql);
- native:本地策略,根据底层的数据库不同,自动选择使用 identity 还是 sequence。
- 适用主键:short、int、long 类型的主键;
- uuid:Hibernate 采用 128 位的 UUID 算法来生成标识符。
- 该算法能够在网络环境中生成唯一的字符串标识符,其 UUID 被编码为一个长度为 32 位的十六进制字符串。
- 适用于:字符串类型的主键。
- 这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。
- increment:(由 Hibernate 提供)自动增长。
- 主键生成策略:
生命周期(实体状态)
Session 的生命周期是以一个逻辑事物的开始和结束为边界,Session 的主要功能是提供创建、读取和删除映射的实体类的操作。
实体可能存在于三种状态:
- 瞬时态(transient):实体对象在内存是自由存在的(即为普通的 Java 对象),即:“该实体从未与任何持久化上下文关联过,它没有持久化标识”。
- 特点:
- 不存在待久化标识 OID (相当于主键值);
- 尚未与 Session 关联;
- 数据库无对应记录;
- 对象失去引用后将被 JVM 回收。
- 特点:
- 持久态(persistent):实体对象处于 Hibernate 框架所管理的状态,即:“该实体在数据库中有对应的记录,并拥有一个持久化标识”。
- 特点:
- 存在待久化标识 OID ;
- 已与 Session 关联(加入到了 Session 缓存,且 Session 未关闭);
- 数据库有对应记录;
- 持久态对象发生改变时,Hibernate 将会依据其改变自动更新。【无需 update 等操作】
- 在一个 Session 中,对持久对象的改变不会马上对数据库进行变更,而是发生在 Transaction 终止,执行 commit 之后。
- 特点:
- 游离态(detached,脱管态):由持久态实体对象转变(关联的 Session 被关闭)而来。
- 特点:
- 存在持久化标识 OID;
- 失去了与 Session 的关联;
- 数据库有对应记录;
- 游离态对象发生改变时,Hibernate 不能检测到。
- 对象的引用依然有效,也可以继续被修改,但修改将不会影响到到数据库中的数据。
- 特点:
“瞬时态”与“游离态”的区别是“是否存在 OID”。(数据库对应记录不是重点)??? 如: Customer customer = new Customer(); // 瞬时态 customer.setCust_id(1); // 脱管态 customer.setCust_id(null); // 瞬时态
状态转换
三种状态可以互相转换:
持久态的自动更新
Hibernate 会依据 persistent 状态的实体对象的属性变化,而自动更新(无需 update 等操作)数据库中相对应的记录。 注意:在一个 Session 中,对持久对象的改变不会马上对数据库进行变更,而是发生在 Transaction 终止,执行 commit 之后。
示例:
@Test // 测试:持久态对象的自动更新 public void demo() { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransac七ion(); // 获得持久态对象. Customer customer = session.get(Customer.class, 11); // 修改持久态对象 customer.setCust name("主五"); // session.update(custorner); // 不用手动调用 update 方法就可以更新 tx.commit(); session.close(); }
- 如上,更改持久态对象属性之后,不调用 update 等方法,依旧可以更新数据库记录。
持久态的自动更新,依赖于 Hibernate 一级缓存的“脏检查机制”。
Hibernate 缓存
Hibernate 的缓存分为“一级缓存”和“二级缓存”,Hibernate 的这两级缓存都位于持久化层,存储的都是数据库数据的备份。 作用:减少对数据库的访问次数。
其中:
- “一级缓存”:Session 级别的缓存,事务范围。
- Hibernate 的内置缓存(默认的缓存机制),不能被卸载;
- “二级缓存”:SessionFactory 级别的缓存,应用范围(应用中的所有 Session 共享)。
- 默认关闭,需要手动配置;
一级缓存(Session 缓存)
Session 缓存:是一块内存空间,用来存放相互管理的对象。 作用: 1、减少访问数据库的频率。【查询时、缓存清理时】 2、保证缓存中的对象与数据库中的相关记录保持同步。【脏检查、快照】
在使用 Hibernate 查询对象的时候,首先会使用对象属性的 OID 值在 Hibernate 的一级缓存中查找:
- 如果找到匹配 OID 值的对象:就直接将该对象从“一级缓存”中取出使用,不会再查询数据库;
- 如果没有匹配 OID 值的对象:去数据库中查找相应数据,并将查询到的数据信息放置到“一级缓存”。
示例:
@Test // 测试 Hibernate 一级缓存的存在 public void demo() { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查询客户1:发出 sql 查询,并将结果放入一级缓存 Customer customerl = session.get(Customer.class, 11); System.out.println(customerl); // 查询客户1:从一级缓存中获取数据,而不再发出 sql 查询 Customer customer2 = session.get(Customer.class, 11) ; System.out.println(customer2); // 验证两次查询结果:true。(一级缓存的内容是对象的地址) System.out.println(customerl == customer2); tx.commit(); session.close(); }
- 从控制台输出(已配置 Hibernate 输出底层 sql 语句)可以发现,以上代码只发出了一次查询语句。
相关方法
Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
- 增加:
- 调用 Session 接口的 save()、update()、saveOrUpdate() 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
- 调用 Session 接口的 load()、get(),及 Query接口的 list()、iterator() 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
- 删除:调用 Session 的 evict() 方法时,会从 Session 缓存移除对应数据。
- 清空:调用 Session 的 clear()、close() 方法时,会清空 Session 缓存。
脏检查机制(快照)
脏检查:当刷新缓存(缓存清理)时,Hibernate 会对 Session 中的持久状态的对象进行检测,判断对象的数据是否发生了改变,这种判断称为“脏检查”。 原理:“快照” 1、Hibernate 向“一级缓存”放入数据时,同时复制一份数据放入到 Hibernate “快照区”中。 2、持久化对象发生更改时,只会修改缓存中数据,并不会修改对象的快照,也不会直接更改到数据库。 3、Session刷新缓存时,通过 OID 判断 Session 缓存中的对象和快照中的对象是否一致: ——如果两个对象中的属性发生变化(脏对象),则执行 update 语句, 将缓存的内容同步到数据库,并更新快照。 ——如果数据一致,则不执行 update 语句。 快照:相当于数据库数据的副本,确保缓存的数据与数据库一致。
通常脏数据的检查有如下两种办法:
- 数据对象监控:通过“拦截器”对数据对象的 setter 方法进行监控来实现的。(类似于数据库中的触发器)
- 当某一个对象的属性调用了 setter 方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。
- 特点:提高了数据更新的同步性。【实时更新】
- 但,如果同一实体对象发生多次属性变化,将会造成大量拦截器回调方法的调用。这些拦截器都是通过 Dynamic Proxy 或者 CGLIB 实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
- 数据版本比对:通过保存数据对象的最近读取版本来实现。
- 在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。
- 特点:降低了数据更新的同步性。【非实时更新】
- 但,如果同一实体对象发生多次属性变化,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。
缓存清理机制(缓存刷新机制)
缓存清理:Session 在某些时间点,对当前持久化状态的缓存数据进行检查(脏检查),并且按照缓存中持久态对象的属性变化来同步更新数据库的过程。 ——【“缓存清理”并非“清空缓存”!!!可以理解为“(从缓存到数据库的)缓存刷新”】 当 Session 缓存中对象的属性每次发生了变化,Session 并不会立即清理缓存和执行相关的 SQL update 语句,而是在特定的时间点才清理缓存,这使得 Session 能够把几条相关的 SQL 语句合并为一条 SQL 语句,以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
Session 缓存清理的时机:(触发策略)
- 提交前:当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
- 之所以在事务快结束时:一方面是因为可以减少访问数据库的频率,另一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间;
- 查询前:当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
- flush:当应用程序显示调用 Session 的 flush() 方法的时候。
如下图:
FlushMode Session.find()/iterate() Session.commit() Session.flush() FlushMode.AUTO ✔ ✔ ✔ FlushMode.COMMIT ✘ ✔ ✔ FlushMode.NEVER ✘ ✘ ✔
- 清理(✔),不清理(✘)
二级缓存(SessionFactory 缓存)
SessionFactory 在 Hibernate 中起到一个缓冲区作用,Hibernate 可以将自动生成的 SQL 语句、映射数据以及某些可重复利用的的数据放在这个缓冲区中。同时它还保存了对数据库配置的所有映射关系,维护了当前的二级缓存。
Flush机制
事务管理
在 Hibernate 中,可以通过代码来操作管理事务(如:开启、提交、回滚),此外,还可以在 Hibernate 的配置文件中对事务进行配置。 事务管理方式: 1、代码。 ——如【Hibernate笔记 3:核心API#Transaction】中所示; 2、配置文件。
通过配置文件设置事务管理,步骤:
- 配置:事务隔离级别;
- 配置:Session 管理方式;(绑定到本地线程)
- HibernateUtil 工具类修改 Session 获取方式:
/** * 获取当前线程绑定的会话: */ public static Session getCurrentSession(){ return sessionFactory.getCurrentSession(); }
- 所有相关配置内容,见:Hibernate笔记 2:配置文件详解#配置:事务管理
关于“Session 管理方式:绑定到本地线程”
在真正进行事务管理的时候,事务控制应该在 Service 层(而非 DAO 层)实现, 并且在 Service 中调用多个 DAO 实现一个业务逻辑的操作。
所以,最主要的是:如何保证“在 Service 中开启的事务时所使用的”和“DAO 中多个操作所使用的”是同一个 Session 对象。
对于保证“同一 Session 对象”,有两种方式:
- 在业务层获取到 Session, 并将 Session 作为参数传递给 DAO;
- 使用 ThreadLocal 将业务层获取的 Session 绑定到当前线程中, 然后在 DAO 中获取 Session 的时候, 都从当前线程中获取。【⭐绑定 Session 到当前线程⭐】
显然,“绑定 Session 到当前线程”是更优解。 —— 对此,Hibernate 中,对此已经提供了支持,只需配置即可。
而后,常用的 HibernateUtil 工具类中,获取 Session 的方式也应由 sessionFactory.openSession()
替换为使用 sessionFactory.getCurrentSession()
。
关于“事务”
事务的“ACID”:Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性) ——见:InnoDB:InnoDB 和 ACID 模型 事务的“并发问题”:脏读、不可重复读、幻读 ——见:InnoDB 锁机制解析#锁问题 事务的“隔离级别”:“READ UNCOMMITTED”、“READ COMMITTED”、“REPEATABLE READ”、“SERIALIZABLE” ——见:InnoDB:InnoDB 事务模型#事务隔离级别 相关内容,见:InnoDB:InnoDB 锁和事务模型