Hibernate:Hibernate缓存策略详解
一:为什么使用Hibernate缓存:
Hibernate是一个持久层框架,经常访问物理数据库。
为了降低应用程序访问物理数据库的频次,从而提高应用程序的性能。
缓存内的数据是对物理数据源的复制,应用程序在运行时从缓存中读取数据,在特定时间或事件会同步缓存和物理数据源的数据
二:什么是Hibernate缓存:
Hibernate缓存分为两种:一级缓存,二级缓存。
1.一级缓存:又称为Session缓存,
Session缓存是Hibernate内置的缓存,不能被卸载,生命周期也就是在open和close之间,也就是每打开一个Session,内存中就会创建一块Session缓存区。
Session缓存中,每个持久化类都拥有一个唯一的OID。(在Hibernate中,类对象分为四种状态:持久化,游离,临时,销毁)。
①.对于刚创建的一个对象,如果session中和数据库中都不存在该对象,那么该对象就是临时
对象(Transient)
②.临时对象调用save方法,或者游离对象调用update方法可以使该对象变成持久化对象,如果
对象是持久化对象时,那么对该对象的任何修改,都会在提交事务时才会与之进行比较,
如果不同,则发送一条update语句,否则就不会发送语句
③.游离对象就是,数据库存在该对象,但是该对象又没有被session所托管
④.销毁状态顾名思义,就是被Delete的数据,只有临时状态和游离状态才能转换为销毁状态
通过OID查询到数据都会存放在Session缓存(一级缓存)中。
2.二级缓存:又称为SessionFactory缓存。
SessionFactory缓存是用户可选的,默认情况下不会开启,可以选择不同的缓存提供商来进行配置。
SessionFactory缓存的声明周期是在应用程序运行到程序结束之间,就是说每一个程序只会拥有一个SessionFactory缓存,因为二级缓存是在进程范围或者
说集群范围,所以有可能出现并发问题,因此需要采用适当的并发缓存策略,该策略为被缓存的数据提供了事务隔离级别。
Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。
什么样的数据适合存放在二级缓存中
1>不会经常被修改的数据
2>常量数据
3>不是很重要的数据,允许偶尔出现并发的数据
4>不能被并发访问的数据,例如:银行账户
3.延迟加载
延迟加载是在用户查询某个实体时,加载这个实体的同时,并不会加载该实体所关联的其它对象,而是会产生一个代理对象,在真正使用到这个对象的时候,
才会通过在缓存中存放的该对象的OID去查询数据库,并返回查询结果,如果查询条件是除了主键OID外,都会直接去查询数据库,通过延迟加载也是大大
提高了应用程序的运行效率。
4.应用程序查询数据
在应用程序通过OID查询数据的时候,会先从一级缓存中查询,如果查不到就会从二级缓存中查询,都查不到才会从数据库中查找。
一级缓存和二级缓存的对比:
一级缓存 | 二级缓存 | |
存放数据的形式 | 相互关联的持久化对象 | 对象的散装数据 |
缓存的范围 |
由于每个事务都拥有单独的一级缓存 不会出现并发问题,因此无须提供并发访问策略 |
由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别
|
数据过期策略 |
处于一级缓存中的对象永远不会过期,除非应用程 序显示清空或者清空特定对象 |
必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间
|
物理介质 | 内存 |
内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中
|
缓存软件实现 | 在Hibernate的Session的实现中包含 |
由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中
|
启用缓存的方式 |
只要通过Session接口来执行保存,更新,删除, 加载,查询,Hibernate就会启用一级缓存,对 于批量操作,如不希望启用一级缓存,直接通过 JDBCAPI来执行 |
用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中
|
用户管理缓存的方式 |
一级缓存的物理介质为内存,由于内存的容量有限 ,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐 |
二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择 需要使用第二级缓存的持久化类,设置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不推荐
|
并发访问策略 | 由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略 |
由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别
|
接下来我们通过例子来讲解一下缓存的具体使用
1. 一级缓存的讲解
>>一级缓存主要分为两种:使用HQL语句操作和不使用HQL操作,两者差别主要在于是否是通过OID来操作物理数据库
先贴上代码:添加一个对象实体,以下Session对象都已经通过SessionFactory静态获取
public void addUser(){ Session session = null; Transaction tran = null; try{ session = sf.openSession(); // 创建一个Session tran = session.beginTransaction(); //开启事务 User user = new User(); user.setName("王五"); session.save(user); User user2 = (User) session.get(User.class, 4); System.out.println(user2); tran.commit();//事务提交 }catch(Exception e){ tran.rollback(); //事务回滚 throw(e); }finally{ session.close(); //关闭session } }
结果:
Hibernate: insert into user_1 (name) values (?)
User [id=4, name=王五]
结果很明显,数据库只有插入的操作,并没有查询的操作,之前有讲过,只要是通过OID操作的数据,都会保存在Session缓存中(即一级缓存),
既然缓存中有这条数据,那就没必要在数据库中找了
获得一个实体对象:获得对象Session中有get()和load()两个方法,load()方法比较特殊,会有延迟加载的特效(延迟加载在上面有讲)
@Test public void getUser(){ Session session = null; Transaction tran = null; try{ session = sf.openSession(); // 创建一个Session tran = session.beginTransaction(); //开启事务 /*这里指明你要获得哪个类型,Hibernate会根据类名查询映射配置文件到数据库查询哪张表,根据指定 * id查询实体,通过反射机制创建实体对象 */ User user1 = (User) session.get(User.class, 1); //执行查询,get System.out.println(user1); User user2 = (User) session.get(User.class, 1); System.out.println(user2); tran.commit();//事务提交 }catch(Exception e){ tran.rollback(); //事务回滚 throw(e); }finally{ session.close(); //关闭session } }
>>结果:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=1, name=李四]
User [id=1, name=李四]
从结果可以看出:session通过OID查询只查询了一次,因为在第一次查询查询到的数据已经存放在了Session缓存中(一级缓存)中了,所以再次
获得该实体对象,应用程序会先查询Session缓存,既然查询到了,就不会再到数据库中查找,所以这里只有一条查询语句。
----以上的代码,我在获取两个User中间加上一行代码:
user1.setName("测试_2");
结果:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=1, name=测试_1]
User [id=1, name=测试_2]
Hibernate: update user_1 set name=? where id=?
从结果看出:在更新一条数据的时候,并不会立即去数据库进行更新操作,而是先更新Session缓存中的数据,在事务提交
或Session关闭的时候应用程序会发现Session缓存中的数据有改变,然后才进行更新数据库操作,在Session类中还有一个
flush()这个方法,和IO流相似,立即刷新数据到目的地,也就是立即把数据更新到数据库中,在这里我并没有使用。
更新操作和删除操作是一样的,就不多说了。。
>>使用Hibernate的HQL操作数据
HQL和SQL基本上一样的,区别:SQL是针对数据库表查询,而HQL是针对类查询;SQL不区分大小写,HQL对类名区分大小写。
使用HQL操作数据会把查询到的数据保存在缓存区中,但是不会从缓存中查找,而是直接到数据库查找
接下来使用HQL操作一下
List<User> list1 = session.createQuery("FROM User WHERE id=3").list(); System.out.println(list1); List<User> list2 = session.createQuery("FROM User WHERE id=3").list(); System.out.println(list2);
结果:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
[User [id=3, name=张三]]
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
[User [id=3, name=张三]]
结果可以看出:这里进行了两次查询,说明第一次查询到的数据并没有进缓存,即使限定了id。
使用迭代器来查询
Iterator<User> it1 = session.createQuery("FROM User").iterate(); while(it1.hasNext()){ System.out.println(it1.next()); } Iterator<User> it2 = session.createQuery("FROM User").iterate(); while(it2.hasNext()){ System.out.println(it2.next()); }
结果:
Hibernate: select user0_.id as col_0_0_ from user_1 user0_
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=2, name=张三]
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=3, name=张三]
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
User [id=4, name=王五]
Hibernate: select user0_.id as col_0_0_ from user_1 user0_
User [id=2, name=张三]
User [id=3, name=张三]
User [id=4, name=王五]
这里第一次是先查询到所有User的id,然后再根据id去查到所有实体对象,使用迭代器能够迫使程序能够通过id去查询
,只有通过OID操作的数据才会进缓存,尽管如此,通过这种方式提高的效率还是有限的,还是会产生大量的查询语句。
2. 二级缓存
二级缓存在Hibernate中默认是关闭的,需要在Hibernate.hbm.xml中配置开启,并配置缓存的提供商,除此之外还
要配置需要添加到缓存的类或集合(class-cache | collection-cache)
在hibernate.cfg.xml中的配置
<!-- 默认情况下二级缓存是关闭的 --> <!-- 选择使用二级缓存的提供商 --> <property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property> <!-- 默认是false,这里选择的值是 查询缓存 --> <property name="cache.use_query_cache">true</property>
前面我们使用HQL进行查询获得一个List集合,虽然也能缓存,但是又有局限性,接下来我们使用二级缓存进行查询
虽然配置了二级缓存,但是并没有指定要缓存的类,所以还要添加缓存类的配置,具体usage的值可以查询API文档
<!-- 在这里要指定缓存类的全限定名 --> <class-cache usage="read-write" class="com.a_helloworld.User"/>
Session session1 = sf.openSession(); // 创建一个Session Transaction tran1 = session1.beginTransaction(); //开启事务 List<User> list1 = session1.createQuery("FROM User WHERE id<10") .setCacheable(true) .list(); System.out.println(list1); tran1.commit();//事务提交 session1.close(); //关闭session
//第二个Session Session session2 = sf.openSession(); // 创建一个Session Transaction tran2 = session2.beginTransaction(); //开启事务 List<User> list2 = session2.createQuery("FROM User WHERE id<10") .setCacheable(true) .list(); System.out.println(list2); tran2.commit();//事务提交 session2.close(); //关闭session
结果:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id<10
[User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]
[User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]
结果看出只有一条查询语句,第二次查询并没有通过数据库查询,而是从缓存区直接拿到了数据。但是,如果
把第二次查询的条件修改一下,就需要从数据库查询,说明这里存储的只是HQL语句。
接下来就不贴代码了,说一下我自己经过测试的问题,大家可以自己亲身测试一下。
>>只要是经过配置的类,所有查询到的数据都会更新二级缓存中
>>进行更新或者删除操作,程序会通知缓存进行更新
>>现在很晚了,一点多了,有时间再改进吧。。。
如需转载,请说明出处:http://www.cnblogs.com/gudu1/p/6882155.html