02.Hibernate一级缓存详解
1、缓存(一级缓存、Session缓存)
概念
主要:根据OID来进行判断
Hibernate中的缓存分为一级缓存和二级缓存,这两个级别的缓存都位于持久化层,并且存储的都是数据库数据的备份。其中一级缓存是 Hibernate 的内置缓存,在前面的学习中已经使用过。本节将针对 Hibernate 的一级缓存进行详细讲解。
一级缓存其实就是 Session 缓存。Session 缓存是一块内存空间,用于存储与管理 Java 对象。
在使用 Hibernate 查询对象时,首先会使用对象的 OID 值在 Hibernate 的一级缓存中查找,如果找到匹配的对象,则直接将该对象从一级缓存中取出使用;如果没有找到匹配的对象,则会去数据库中查询对应的数据。当从数据库中查询到所需数据时,该数据信息会存储到一级缓存中。由此可知,Hibernate 一级缓存的作用就是减少对数据库的访问次数。
特点
1)当应用程序调用 Session 接口的 save()、update()、saveOrUpdate() 时,如果 Session 缓存中没有相应的对象,则 Hibernate 就会自动把从数据库中查询到的相应对象信息加入到一级缓存中。
2)当调用 Session 接口的 load()、get() 方法,以及 Query 接口的 list()、iterator() 方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,则再去数据库中查询对应对象,并添加到一级缓存中。
3)当调用 Session 的 close() 方法时,Session 缓存会被清空。
4)Session 能够在某些情况下,按照缓存中对象的变化,执行相关的 SQL 语句同步更新数据库,这一过程被称为刷出缓存(flush)。
在默认情况下,Session 在如下几种情况中会刷出缓存。
1)当应用程序调用 Transaction 的 commit() 方法时,该方法先刷出缓存(调用 session.flush() 方法),然后再向数据库提交事务(调用 commit() 方法)。
2)当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。
3)调用 Session 的 flush() 方法。
案例1:证明一级缓存的存在
// 证明一级缓存的存在
@Test
public void test2() {
Session session = HibernateUtils.getSession(); // 得到session对象
session.beginTransaction();
// 获取goods1对象时,由于一级缓存中没有数据,所以会发送SQL语句,查询数据库中的内容
Goods goods1 = (Goods) session.get(Goods.class, 1);
System.out.println(goods1);
// 获取goods2对象时,不会发出SQL语句,会从Session缓存中获取数据
Goods goods2 = (Goods) session.get(Goods.class, 1);
System.out.println(goods2);
session.getTransaction().commit();
session.close();
}
在上述代码中,第一次执行 get() 方法时,由于一级缓存中不存在 id 为 1 的 Goods 对象,所以 Hibernate 会发出 SQL 语句查询数据库中 id 为 1 的数据。查到后,会将该数据信息保存到一级缓存中。当再次调用 get() 方法获取该对象时,此时将不会发出 SQL 语句,这是因为该对象是在一级缓存中获取的。
案例2:一级缓存的常用操作
1. 刷出(flush)
一级缓存刷出功能是指调用 Session 的 flush() 方法时会执行刷出缓存的操作。
// 刷出
@Test
public void test4() {
Session session = HibernateUtils.getSession(); // 得到session对象
session.beginTransaction();
Goods goods = (Goods) session.get(Goods.class, 2);
goods.setPrice(5.5);
session.flush(); // 执行刷出操作,此时会发送update语句
session.getTransaction().commit();
session.close();
}
在 flush() 方法处设置断点,利用 Debug 模式运行程序。当程序停止在断点处时,控制台中会显示查询 Goods 对象时所发出的 select 语句。当程序向下执行完 session.flush() 语句时,控制台会输出 update 语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MepcE4Uj-1609491479896)(images1\img03.png)]
解释:发出了 update 语句,这说明在提交事务前,Hibernate 程序会默认先执行 flush() 方法。
2. 清除(clear)
程序在调用 Session 的 clear() 方法时,可以执行清除缓存数据的操作。
// 清除
@Test
public void test5() {
Session session = HibernateUtils.getSession(); // 得到session对象
session.beginTransaction();
Goods goods = (Goods) session.get(Goods.class, 2);
System.out.println(goods);
goods.setPrice(6.5);
session.clear(); // 清空一级缓存
session.getTransaction().commit();
session.close();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJjrWyJ8-1609491479897)(F:\S3课程\md文档总结笔记(ycj)\Hibernate_md总结文档\images1\img04.png)]
解释:从图 2 中可以看出,执行后的 test5() 方法只输出了 select 语句,而没有输出 update 语句。同时,查看数据库时,会发现数据库中的数据也没有发生变化。这是因为在执行 clear() 方法时,清空了一级缓存中的数据,所以 Goods 对象的修改操作并没有生效。
需要注意的是,如果将上述方法中的 session.clear() 方法更改为 session.evict(goods)方法,也可以实现同样的效果。这两个方法的区别是:clear() 方法是清空一级缓存中所有的数据,而 evict() 方法是清除一级缓存中的某一个对象。
3. 刷新(refresh)
程序在调用 Session 的 refresh() 方法时,会重新查询数据库,并更新 Hibernate 快照区和一级缓存中的数据。
// 刷新
@Test
public void test6() {
Session session = HibernateUtils.getSession(); // 得到session对象
session.beginTransaction();
Goods goods = (Goods) session.get(Goods.class, 2);
goods.setPrice(7.5);
session.refresh(goods); // 查询数据库,恢复快照和一级缓存中的数据
session.getTransaction().commit();
session.close();
}
2、Session对象的方法
save:保存对象
persist():保存一个对象,跟save作用一样
*如果手动设置了ID,则save方法会忽略ID,而persist方法则会抛出一个异常
get:根据OID获取对象
load:加载一个对象,跟get作用一样
1、get会立即执行查询,而load不会立即执行查询,在用到该对象的时候才会去执行查询操作(懒加载)
2、load加载之后,如果session被管理,则直接抛出异常
3、如果数据库中没有与之对应的结果,get回返回一个null,而load方法会抛出异常
clear:清空缓存
refresh:重新加载某个对象
evict:可以将某个被session管理的持久化对象,从session中移除,使其变为游离对象
close:关闭session
update:更新对象(游离),该对象会转变为持久化对象
delete:从数据库中删除一个对象
saveOrUpdate:系统会判断该对象,如果是临时对象,就会执行保存操作,如果是游离对象,则执行更新操作
flush:发送让session对象SQL语句
3、事务的隔离级别(可重复读)
TRANSACTION_NONE(0):不使用事务的隔离级别
TRANSACTION_READ_UNCOMMITED(1):读未提交:脏读、不可重复读、幻读
TRANSACTION_READ_COMMITED(2):读已提交:不可重复读、幻读
TRANSACTION_PREPETABLE_READ(4):可重复度:幻读
TRANSACTION_SERIALIZABLE(8):序列化
脏读:读取到了未提交的数据
不可重复读:两次读取结果不一致
幻读:数据记录数量发生了改变
在Hibernate中设置事务的隔离级别
4、对象的状态
持久化对象的状态及状态转换
在 Hibernate中,持久化对象是存储在一级缓存当中的,一级缓存指 Session 级别的缓存,它可以根据缓存中的持久化对象的状态改变同步更新数据库。
Hibernate 是持久层的 ORM 框架,专注于数据的持久化工作。在进行数据持久化操作时,持久化对象可能处于不同的状态当中。这些状态可分为三种,分别为瞬时态、持久态和脱管态。下面分别针对这三种状态进行简单介绍。
1)瞬时态(transient)
瞬时态也称为临时态或者自由态,瞬时态的对象是由 new 关键字开辟内存空间的对象,不存在持久化标识 OID(相当于主键值),且未与任何的 Session 实例相关联,在数据库中也没有记录,失去引用后将被 JVM 回收。瞬时对象在内存孤立存在,它是携带1信息的载体,不和数据库的数据有任何关联关系。
2)持久态(persistent)
持久态的对象存在一个持久化标识 OID,当对象加入到 Session 缓存中时,就与 Session 实例相关联。它在数据库中存在与之对应的记录,每条记录只对应唯一的持久化对象。需要注意的是,持久态对象是在事务还未提交前变成持久态的。
3)脱管态(detached)
脱管态也称离线态或者游离态,当持久化对象与 Session 断开时就变成了脱管态,但是脱管态依然存在持久化标识 OID,只是失去了与当前 Session 的关联。需要注意的是,脱管态对象发生改变时 Hibernate 是不能检测到的。
在 Hibernate 运行时,持久化对象的三种状态是可以通过一系列的方法进行转换的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGxcNUvn-1609491479898)(images1/img02.png)]
解释:当一个对象通过 new 关键字创建后,该对象处于瞬时态;当对瞬时态对象执行 Session 的 save() 或 saveOrUpdate() 方法后,该对象将被放入 Session 的一级缓存中,此时该对象处于持久态。
当对持久态对象执行 evict()、close() 或 clear() 操作后,对象会进入脱管态。
当直接执行 Session 的 get()、load()、find() 或 iterate() 等方法从数据库中查询出对象时,查询到的对象也会处于持久态。
当对数据库中的纪录进行 update()、saveOrUpdate() 以及 lock() 等操作后,此时脱管态的对象就过渡到持久态;由于瞬时态和脱管态的对象不在 session 的管理范围内,所以会在一段时间后被 JVM 回收。
对象的状态(最核心的在于对象的OID是否为空,如果OID为空,则一定是临时对象)
1.临时对象(瞬时态):
自己New的对象
不存在于数据库中
不存在于Session中
OID为NULL 2.持久化对象(持久态):
通过get等方法从数据库中取出来的对象
3.游离对象(游离、脱管、离线态):
存在于数据库中,但是并未被session管理的对象
4.*删除对象(删除态):
本来存在于数据库中,后来被删除了
5、Hibernate hibernate.cfg.xml文件和C3P0连接池的配置
Hibernate hibernate.cfg.xml文件配置
Hibernate的配置文件包含了数据库连接的相关信息,以及映射文件的基本信息。通常情况下,配置文件默认放在 src 目录下,发布后,该文件会在项目的 WEB-INF/classes 路径下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 指定方言 -->
<property name="dialect">
org.hibernate.dialect.MySQL5Dialect
</property>
<!-- 链接数据库url -->
<property name="connection.url">
<![CDATA[jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=utf-8]]>
</property>
<!-- 连接数据库的用户名 -->
<property name="connection.username">
root
</property>
<!-- 数据库的密码 -->
<property name="connection.password">
1128
</property>
<!-- 数据库驱动 -->
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!-- 其他配置 -->