hibernate缓存之二级缓存分析:
1:二级缓存介绍:
1:一级缓存是Session级别的,二级缓存是SessionFactory级别的,一级缓存不可以跨Session,二级缓存可以跨Session。二级缓存是一个进程级别的缓存,这种级别的缓存可以被所有的Session共享。其生命周期和SessionFactory的生命周期一样,SessionFactory关闭,二级缓存随着消失。
2:我们在的项目,SessionFactory一般是不会关闭的,一般是服务器停止,SessionFactory关闭。
3:二级缓存也是只能缓存实体对象,不能对普通属性做缓存,这里和一级缓存是一致的。
缓存的原理,其实是针对实体对象的id的,根据id,就实体对象缓存到一级缓存或者二级缓存中。具体我们不需要太过于关心。
二级缓存开启之后,我进行一次请求的时候,会先到一级缓存中寻找相应的数据,在一级缓存中如果没有找到相应的数据,就会去二级缓存中寻找相应的数据,二级缓存中没有相应的数据,此时才会去数据库中寻找。在数据库中找到相应数据之后,一级缓存和二级缓存中会分别保存我此次查询的相应的数据。如果我一级缓存中,没有数据,二级缓存中有数据的话,二级缓存中数据获得之后,也是可以保存一份相应的数据到一级缓存中的。但是,我们可以进行设置,不允许保存一些数据。
2:二级缓存应用场景:
适合使用二级缓存的数据:
数据基本不需要被修改,但是可以被经常查询。
数据不是特别重要,允许偶尔出现并发;
参考数据;
不适合使用二级缓存的数据:
经常需要修改的数据,经常修改的数据,意味着可能会出现并发,二级缓存需要不断的控制并发,并且需要不断的同步数据库和缓存中的数据,这样性能的消耗是很大的。
财务数据,绝对不允许并发问题出现的。二级缓存是在内存之中,对并发的控制肯定不如数据库端锁的控制。
和其他环境的数据做共享的数据。主要也是并发问题的出现。
3:第三方的二级缓存产品:
Hibernate本身提供了二级缓存,但是没有第三方提供的二级缓存强大,我们需要使用第三方的二级缓存和hibernate做一个整合。
我们最常用的第三方二级缓存产品是:EHcache,支持在内存和硬盘上做缓存。由于内存的空间有限,我们可以将数据缓存到硬盘上。经常使用的数据就缓存到内存中,使用的不多的,放到硬盘之中。后续如果硬盘上的数据经常使用,我们还可以将其放到内存中,做硬盘和内存的交互,这里是一个虚拟内存的概念。
我们数据库中的数据本身就是存储在硬盘上,那从硬盘上查询数据和从数据库中查询数据是否是一样的呢?
数据库中,允许存储的数据是无限的,在硬盘上做缓存存储的数量是有限的,
EHcache:能支持集群以及查询缓存。
4:Ehcache的配置:
第一步:引入依赖包:
第二步:引入配置文件:
第三步:在hibernate的核心配置文件中注册Ehcache二级缓存的配置文件:
<!-- 注册Ehcache二级缓存的配置文件:
-->
<!-- 开启二级缓存: -->
<property name="hibernate.cache.use_second_level_cache">true</property>、
<!-- 开启二级缓存之后,还不可以使用,因为默认使用的是hibernate的二级缓存,
所以需要整合Ehcache二级缓存和hibernate
-->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
第四步:配置Ehcache二级缓存的配置文件中的内容:
属性 说明
maxElementsInMemory 缓存中对象的个数(缓存的大小)
eternal 缓存中对象(Element)是否是永久有效,一般不会设置为永久有效
timeToIdleSeconds
在对象失效前允许闲置的时间,当我设置成一定的时间,如果一个对象在设置的时间之内,没有应用来使用这个对象,这个对象就会被回收
timeToLiveSeconds 对象在缓存中存在的最大时间,即使对象在最大时间类频繁被使用,到了最大时间之后,同样会被回收,说白了就是其生命周期
overflowToDisk 但内存已经被缓存占满,就可以将缓存写到硬盘上的。
MaxElementsOnDisk 硬盘上的对象的最大数量
DiskPersisent 是否在硬盘上永久有效
MemoryStoreEvctionPolicy 内存中对象逐出的策略:
LRC:最近最少使用,经常使用的不多的就会被清理
FIFO:先进先出,先被保存的先逐出
5:测试二级缓存:
1:测试二级缓存是否跨Session:
使用二级缓存的方法:get,load,iterate,不使用二级缓存的方法是list,和一级缓存是一样的。
/*
* get和load方法都支持二级缓存:
*
*/
@org.junit.Test
public void test2() {
Session session = HibernateUtils.getSession();
try {
//第一次查询:
User user = (User) session.load(User.class, 1);
//第一次查询发出sql
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭Session
HibernateUtils.closeSession(session);
}
//关闭Session后重新开启另一个Session
Session session1 = HibernateUtils.getSession();
try {
//第二次查询,和第一次的查询的数据是相同的
User user = (User) session1.get(User.class, 1);
//第二次查询没有发送sql语句,从缓存中获得查询结果,这就说明二级缓存是跨Session的。
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session1);
}
第一次查询,发出sql语句
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=LBJ, password=6660]
第二次直接获得结果,没有向数据库中发出sql语句
User [userId=1, username=LBJ, password=6660]
/*
* 使用hql语句来查询对象,list方法是不使用二级缓存的
*/
@org.junit.Test
public void test3() {
Session session = HibernateUtils.getSession();
try {
String hql = "from User";
Query query = session.createQuery(hql);
List<User> userList = query.list();
//第一次查询,发出sql
for(User user : userList){
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭Session
HibernateUtils.closeSession(session);
}
//关闭Session后重新开启另一个Session
Session session1 = HibernateUtils.getSession();
try {
String hql = "from User";
Query query = session1.createQuery(hql);
List<User> userList = query.list();
//第二次查询,发出sql
for(User user : userList){
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session1);
}
第一次查询会发出sql
Hibernate: select user0_.user_id as user_id1_0_, user0_.username as username2_0_, user0_.password as password3_0_ from t_user user0_
User [userId=1, username=LBJ, password=6660]
User [userId=2, username=KB1, password=6661]
User [userId=3, username=KB2, password=6662]
User [userId=4, username=KB3, password=6663]
User [userId=5, username=KB4, password=6664]
User [userId=6, username=KB5, password=6665]
User [userId=7, username=KB6, password=6666]
User [userId=8, username=KB7, password=6667]
User [userId=9, username=KB8, password=6668]
User [userId=10, username=KB9, password=6669]
User [userId=11, username=科比, password=666]
第二次查询同样会发出sql
Hibernate: select user0_.user_id as user_id1_0_, user0_.username as username2_0_, user0_.password as password3_0_ from t_user user0_
User [userId=1, username=LBJ, password=6660]
User [userId=2, username=KB1, password=6661]
User [userId=3, username=KB2, password=6662]
User [userId=4, username=KB3, password=6663]
User [userId=5, username=KB4, password=6664]
User [userId=6, username=KB5, password=6665]
User [userId=7, username=KB6, password=6666]
User [userId=8, username=KB7, password=6667]
User [userId=9, username=KB8, password=6668]
User [userId=10, username=KB9, password=6669]
User [userId=11, username=科比, password=666]
/*
* 使用hql语句来查询对象,iterate方法是使用二级缓存的
*/
@org.junit.Test
public void test4() {
Session session = HibernateUtils.getSession();
try {
String hql = "from User where userId = 1";
Query query = session.createQuery(hql);
Iterator<User> iter = query.iterate();
//第一次查询发出sql
while(iter.hasNext()){
System.out.println(iter.next());
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭Session
HibernateUtils.closeSession(session);
}
//关闭Session后重新开启另一个Session
Session session1 = HibernateUtils.getSession();
try {
String hql ="from User where userId = 1";
Query query = session1.createQuery(hql);
Iterator<User> iter = query.iterate();
第二次查询不发sql
while(iter.hasNext()){
System.out.println(iter.next());
}
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session1);
}
发出sql,第一次查询:
Hibernate: select user0_.user_id as col_0_0_ from t_user user0_ where user0_.user_id=1
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=LBJ, password=6660]
Hibernate: select user0_.user_id as col_0_0_ from t_user user0_ where user0_.user_id=1
第二次没有发出sql,直接获得结果:
User [userId=1, username=LBJ, password=6660]
6:二级缓存的策略:
1:我们可以对二级缓存配置,可以配置为只读的,也可以是可读写的。
2:我们知道二级缓存对应用场景的要求,存储尽量可以不用修改的数据,二级缓存中的数据我们主要是从中读取,提高我们读取性能。
3:我们在映射文件中使用cache标签中的usage属性来进行策略的配置
A:read-only,默认情况下使用这个,表示是一个只读的二级缓存,只可以读取二级缓存中的数据,不可以修改二级缓存中的数据。我对二级缓存数据修改的话,会导致后续读到的二级缓存中的数据和数据库中的数据不一致,那二级缓存中我修改的数据就是脏数据我们一般情况下,对于二级缓存都设置为只读。
B:Nonstrict-read-write:不严格读写缓存,允许对数据库中的数据进行更新,一旦数据库中的数据被更新,相应的二级缓存中的数据就过期了,就会被清理。我们再次做同样的查询的时候,就只能从数据库中获取数据,也就是肯定会发出sql语句。
C:read-write:可读写,允许更新数据库,一旦数据库更新,那么二级缓存也会同步更新,这种方式会带来一些并发的问题,二级缓存的这种并发的问题结合数据库一些处理策略,使其不产生脏数据。会将脏数据做一些标志,对脏数据进行清理,重新查询的时候,就没有问题了。
二级缓存是允许新增的,新增是不会对二级缓存进行清理的,如:我现在查询整张表,表中有十条数据,查询之后,二级缓存中也会保存着十条数据,然后我向表中新添加十条数据,
在我新添加的时候,二级缓存并不会被清理,而是在二级缓存中也会新添加十条数据。当我再次查询的时候,是不会发出sql来向数据库中查询数据的,而是向缓存中查询数据,此时查询的数据就是20条数据。
D:transactional:二级缓存是支持事务的,在二级缓存中,我修改数据的时候,出现了错误,可以为事务做回滚,是操作同时成功或者同时失败,这里做了解就可以了,其他三种方式都有使用,我们都要做了解,使用较多的是第二种方式。
/*
* 现在我的二级缓存设置为只读,也就是usage=read-only
*
*/
@org.junit.Test
public void test2() {
Session session = HibernateUtils.getSession();
try {
User user = (User) session.load(User.class, 1);
System.out.println(user);
/*
* 当我这里做了第一次查询的时候,会将数据放一份到二级缓存之中
*/
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
Session session1 = HibernateUtils.getSession();
Transaction t = session1.beginTransaction();
try {
User user = (User) session1.get(User.class, 1);
//我从二级缓存中获得user之后,对二级缓存的数据user进行修改
user.setUsername("FMVP");
session1.update(user);
//由于二级缓存是只读的,所以无法修改,修改是会出现错误的
t.commit();
} catch (Exception e) {
e.printStackTrace();
t.rollback();
}finally{
HibernateUtils.closeSession(session1);
}
Session session2 = HibernateUtils.getSession();
try {
User user = (User) session2.get(User.class, 1);
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session2);
}
}
/*
* 现在我的二级缓存设置为不严格读写缓存,也就是usage="nonstrict-read-write"
*
*/
@org.junit.Test
public void test3() {
Session session = HibernateUtils.getSession();
try {
User user = (User) session.load(User.class, 1);
//发出sql语句,到数据库中进行查询:
System.out.println(user);
/*
* 当我这里做了第一次查询的时候,会将数据放一份到二级缓存之中
*/
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
Session session1 = HibernateUtils.getSession();
Transaction t = session1.beginTransaction();
try {
//不会发出sql语句,直接从二级缓存中获得相关数据
User user = (User) session1.load(User.class, 1);
//我从二级缓存中获得user之后,对二级缓存的数据user进行修改
user.setUsername("FMVP");
session1.update(user);
//由于二级缓存是不严格读写缓存,是可以对数据库中的数据修改的,但是我二级缓存中相应的数据就过期了,会被清理。
t.commit();
} catch (Exception e) {
e.printStackTrace();
t.rollback();
}finally{
HibernateUtils.closeSession(session1);
}
Session session2 = HibernateUtils.getSession();
try {
User user = (User) session2.load(User.class, 1);
//由于二级缓存中的相应数据已经被清理,所以此时只能发送sql重新到数据库中查询相应的数据
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session2);
}
}
第一次查询发出sql:
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=KOBE, password=6660]
第二次不发查询sql,从二级缓存中获得数据后,对数据库中的数据,二级缓存中的数据会清除
Hibernate: update t_user set username=?, password=? where user_id=?
我再次查询相应的数据的时候,只能重新发出sql,去数据库中查询:
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=FMVP, password=6660]
/*
* 现在我的二级缓存设置为可读写缓存,也就是usage="read-write"
*
*/
@org.junit.Test
public void test4() {
Session session = HibernateUtils.getSession();
try {
User user = (User) session.load(User.class, 1);
//发出sql语句,到数据库中进行查询:
System.out.println(user);
/*
* 当我这里做了第一次查询的时候,会将数据放一份到二级缓存之中
*/
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
Session session1 = HibernateUtils.getSession();
Transaction t = session1.beginTransaction();
try {
//不会发出sql语句,直接从二级缓存中获得相关数据
User user = (User) session1.load(User.class, 1);
//我从二级缓存中获得user之后,对二级缓存的数据user进行修改
user.setUsername("CCC");
session1.update(user);
//由于二级缓存是可读写缓存,对数据库中的数据进行修改的同时也会将二级缓存中的数据做相应的更新
t.commit();
} catch (Exception e) {
e.printStackTrace();
t.rollback();
}finally{
HibernateUtils.closeSession(session1);
}
Session session2 = HibernateUtils.getSession();
try {
User user = (User) session2.load(User.class, 1);
//不发sql,直接从二级缓存中获取
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session2);
}
}
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=KOBE, password=6660]
Hibernate: update t_user set username=?, password=? where user_id=?
User [userId=1, username=CCC, password=6660]
7:二级缓存的手动管理:
如一个对象,我们可以自己手动的将这个对象从二级缓存中逐出:
@org.junit.Test
public void test5() {
Session session = HibernateUtils.getSession();
try {
User user = (User) session.load(User.class, 1);
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
/*
* 在这里我来指定的将二级缓存中的某个对象逐出:
*/
//由于二级缓存是SessionFactory级别的,所以我先要来获得获得SessionFactory
SessionFactory sf = HibernateUtils.getSessionFactory();
//通过Session工厂来获得二级缓存
Cache c = sf.getCache();
//将指定的对象从二级缓存中逐出:
c.evictEntity(User.class, 1);
Session session2 = HibernateUtils.getSession();
try {
User user = (User) session2.load(User.class, 1);
//由于此对象从二级缓存被逐出,所以我再次查询时,需要发送sql到数据库中查询
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally{
HibernateUtils.closeResource(session2);
}
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=CCC, password=6660]
Hibernate: select user0_.user_id as user_id1_0_0_, user0_.username as username2_0_0_, user0_.password as password3_0_0_ from t_user user0_ where user0_.user_id=?
User [userId=1, username=CCC, password=6660]