写在前面: hibernate缓存分为一级缓存(session级别)和二级缓存(sessionfactory级别)。其中一级缓存是强制开启的,二级缓存需要手动配置。
推荐博文 hibernate缓存机制详细分析
一级缓存(session级别)
首先我们创建一个 session对象,查询一个列表,5条数据。此时,这些数据已经被存到了session缓存中,当我们继续去根据id查找 某一个对象时,因为已经存在了缓存中,所以直接拿出来使用,并没有查询数据库。 所以整个过程我们查询了两次,但是和数据库只操作了一次。
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
System.out.println(from_userEntity);
System.out.println(currentSession.get(UserEntity.class, 10));
运行结果
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
[UserEntity{uid=9, uname='李洪伟0', uage=19}, UserEntity{uid=10, uname='李洪伟0', uage=19}, UserEntity{uid=11, uname='李洪伟1', uage=20}, UserEntity{uid=12, uname='李洪伟2', uage=21}, UserEntity{uid=13, uname='李洪伟3', uage=22}]
Disconnected from the target VM, address: '127.0.0.1:50239', transport: 'socket'
UserEntity{uid=10, uname='李洪伟0', uage=19}
如果我们在第一次查询完之后关闭该session,会怎么样?
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
currentSession.close();
currentSession = sessionFactory.openSession();
System.out.println(from_userEntity);
System.out.println(currentSession.get(UserEntity.class, 10));
运行结果:
可以看到,我们关闭session之后,一级缓存也关闭了,再次查询某个对象时,会再次和数据库交互。
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
[UserEntity{uid=9, uname='李洪伟0', uage=19}, UserEntity{uid=10, uname='李洪伟0', uage=19}, UserEntity{uid=11, uname='李洪伟1', uage=20}, UserEntity{uid=12, uname='李洪伟2', uage=21}, UserEntity{uid=13, uname='李洪伟3', uage=22}]
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=10, uname='李洪伟0', uage=19}
二级缓存(sessionFactory级别)
-
二级缓存配置
- 增加相应的jar包
- 在
hibernate.cfg.xml
文件中开启二级缓存<!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类--> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory</property> <!-- 二级缓存配置文件的位置 --> <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
- 配置
ehcache.xml
文件(每个实体类对应的属性)<ehcache> <!--指定二级缓存存放在磁盘上的位置--> <diskStore path="user.dir"/> <!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置--> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> <!-- 1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长; 2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长; 3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A; 4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B; 5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。 --> <!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名--> <cache name="domain.UserEntity" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /> </ehcache>
- 在对应实体类的配置文件中设置 二级缓存的级别
<?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-mapping> <class name="domain.UserEntity" table="user" schema="test"> <!--设置二级缓存的级别--> <cache usage="read-only" /> <id name="uid" column="uid"/> <property name="uname" column="uname"/> <property name="uage" column="uage"/> </class> </hibernate-mapping>
这样二级缓存的配置就完成了,下面看看效果
-
缓存对象
注意: 二级缓存缓存的是对象,如果我们查询某个对象的某几个属性,那么则不会走二级缓存
我们查询一个列表,关闭session之后,再次查询上次查询过的某个对象
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
currentSession.close();
currentSession = sessionFactory.openSession();
System.out.println(from_userEntity);
System.out.println(currentSession.get(UserEntity.class, 10));
运行结果:
可以看到,我们虽然在第一次查完之后关闭了session对象,但是第二次查询的时候还是没有查询数据库,直接走的二级缓存。
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
[UserEntity{uid=9, uname='李洪伟0', uage=19}, UserEntity{uid=10, uname='李洪伟0', uage=19}, UserEntity{uid=11, uname='李洪伟1', uage=20}, UserEntity{uid=12, uname='李洪伟2', uage=21}, UserEntity{uid=13, uname='李洪伟3', uage=22}]
Disconnected from the target VM, address: '127.0.0.1:50483', transport: 'socket'
UserEntity{uid=10, uname='李洪伟0', uage=19}
- 查询某几个属性
如果我们只是查询某几个属性的话是不会走二级缓存的。
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("select user.uid,user.uname from UserEntity user").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
currentSession.close();
currentSession = sessionFactory.openSession();
System.out.println(from_userEntity);
System.out.println(currentSession.get(UserEntity.class, 10));
运行结果:
我们虽然开启了二级缓存,但是我们第一次查询的不是对象,而是某几个属性,所以不会加入二级缓存。 和数据库交互了两次
Hibernate:
select
userentity0_.uid as col_0_0_,
userentity0_.uname as col_1_0_
from
user userentity0_ limit ?
[[Ljava.lang.Object;@60b616c8, [Ljava.lang.Object;@3962ec84, [Ljava.lang.Object;@147e0734, [Ljava.lang.Object;@2bdab835, [Ljava.lang.Object;@7b8aebd0]
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
Disconnected from the target VM, address: '127.0.0.1:50528', transport: 'socket'
UserEntity{uid=10, uname='李洪伟0', uage=19}
- 利用二级缓存解决 N+1的问题
当我们使用 currentSession.createQuery("from UserEntity ").setFirstResult(0).setMaxResults(5).iterate();
这种迭代器返回查询的数据时,hibernate会先 查询所有的id,然后再利用id查询不同的对象。
Session currentSession = sessionFactory.openSession();
currentSession = sessionFactory.openSession();
Iterator<UserEntity> from_userEntity2 = currentSession.createQuery("from UserEntity ").setFirstResult(0).setMaxResults(5).iterate();
for (;from_userEntity2.hasNext();) {
System.out.println(from_userEntity2.next());
}
运行结果
hibernate先把所有的id 查询出来,再利用select语句根据id一个个的把对象查出来,这就造成了 n+ 1的问题
Hibernate:
select
userentity0_.uid as col_0_0_
from
user userentity0_ limit ?
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=9, uname='李洪伟0', uage=19}
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=10, uname='李洪伟0', uage=19}
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=11, uname='李洪伟1', uage=20}
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=12, uname='李洪伟2', uage=21}
Hibernate:
select
userentity0_.uid as uid1_0_0_,
userentity0_.uname as uname2_0_0_,
userentity0_.uage as uage3_0_0_
from
user userentity0_
where
userentity0_.uid=?
UserEntity{uid=13, uname='李洪伟3', uage=22}
- 解决方案(利用二级缓存)
当第一次查询列表时,会把查到的所有对象存在二级缓存中,当使用iterate去查询时,先去数据库中查询一下所有的id,再根据每个id查询数据时,我们发现并没有和数据库交互,都是走的 二级缓存。,这样就解决了 n+ 1的问题
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
currentSession.close();
System.out.println("------------------------");
currentSession = sessionFactory.openSession();
Iterator<UserEntity> from_userEntity2 = currentSession.createQuery("from UserEntity ").setFirstResult(0).setMaxResults(5).iterate();
for (;from_userEntity2.hasNext();) {
System.out.println(from_userEntity2.next());
}
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
------------------------
Hibernate:
select
userentity0_.uid as col_0_0_
from
user userentity0_ limit ?
UserEntity{uid=9, uname='李洪伟0', uage=19}
UserEntity{uid=10, uname='李洪伟0', uage=19}
UserEntity{uid=11, uname='李洪伟1', uage=20}
UserEntity{uid=12, uname='李洪伟2', uage=21}
UserEntity{uid=13, uname='李洪伟3', uage=22}
利用二级缓存缓存hql语句
当我们如果通过 list() 去查询两次对象时,二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句
- 问题示例代码
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
transaction.commit();
currentSession.close();
System.out.println("------------------------");
currentSession = sessionFactory.openSession();
currentSession.createQuery("from UserEntity").setFirstResult(0).setMaxResults(5).list();
运行结果
我们可以看到,虽然两次查询的list完全相同,还是和数据库交互了两遍
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
------------------------
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
-
配置:
- 在hibernate.cfg.xml中开启
<!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property>
- 然后我们如果在查询hql语句时要使用查询缓存,就需要在查询语句后面设置这样一个方法:.setCacheable(true)
List from_userEntity = currentSession.createQuery("from UserEntity") .setCacheable(true) .setFirstResult(0) .setMaxResults(5) .list();
-
示例代码
虽然我们查询了两个list列表,但是只和数据库交互了一次。
Session currentSession = sessionFactory.openSession();
Transaction transaction = currentSession.beginTransaction();
List from_userEntity = currentSession.createQuery("from UserEntity")
.setCacheable(true)
.setFirstResult(0)
.setMaxResults(5)
.list();
transaction.commit();
currentSession.close();
System.out.println("------------------------");
currentSession = sessionFactory.openSession();
currentSession.createQuery("from UserEntity")
.setCacheable(true)
.setFirstResult(0)
.setMaxResults(5)
.list();
结果:
Hibernate:
select
userentity0_.uid as uid1_0_,
userentity0_.uname as uname2_0_,
userentity0_.uage as uage3_0_
from
user userentity0_ limit ?
------------------------
注意: 查询缓存也能引起 N+1 的问题, 因为查询缓存缓存的也仅仅是对象的id,所以第一条 sql 也是将对象的id都查询出来,但是当我们后面如果要得到每个对象的信息的时候,此时又会发sql语句去查询,所以,如果要使用查询缓存,我们一定也要开启我们的二级缓存
谢谢观看,有问题可以一起探讨学习。