Hibernate缓存一直比较难掌握,网上也有很多资料,但不是很全面,正好做项目也排除了一些问题,终于忍不住全面的整理和写写自己的感觉。。。
hibernate一级缓存(Session 级别的缓存)
hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。
hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了。
体验一级缓存:
//同一个session中,发出两次load方法查询
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
//不会发出查询语句,load使用缓存
student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
第二次查询第一次相同的数据,第二次load方法就是从缓存里取数据,不会发出sql语句到数据库里查询。
缓存主要是用于查询 ,hibernate的很多方法都是首先从缓存中取数据如果没有在从数据库中获取,以提升查询效率如:get()/load()、iterate(),而且持久态的对象是会存储在缓存中的。例如:先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存
一级缓存特征及其应用:
1.Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;
2.两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;
3.Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用, 如果
使用HQL和SQL是查询属性级别的,是不使用一级缓存的!切记!!!!(我之前遇到过这样的问题,一直疑惑为啥不从缓存中取数据)
4.iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,
它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。
5. List()和iterate 查询区别 :(面试)
使用iterate,list查询实体对象*N+1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题
所谓的N+1是在查询的时候发出了N+1条sql语句1:首先发出一条查询对象id列表的sqlN:
根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句list和iterate的区别?
list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据
iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题
6.Get()和load(),iterate方法都会使用一级缓存,
Get与load的区别?(面试)
1. 对于get方法, hibernate会确认一下该id对应的数据是否存在,首先在 session缓存中查找,然后在二级缓存中查找,还没有就查询 数据库,数据库中没有就返回null。
2. load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true),分情况讨论:
(1)若为true,则首先在 Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。 等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
(2)若为false,就跟get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
小结:
1、get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;而load方法首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库
2、如果未能发现符合条件的记录,get方法返回null,而 load方法会抛出一个ObjectNotFoundException。
3、load使用代理延迟加载数据,而get方法往往返回有实体数据的对象
使用:
1、如果想对一个对象进行增删改查之类,该使用load方法,性能提高,可以使用代理对象,省去了一次和数据库交互的机会,当真正用到该对象的属性时,才跟数据库交互
2、如果你想加载一个对象使用它的属性,该使用get方法
7.hiberate3 session 存储过程如下:
例如 object 对象
Session.save(object);
这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQL和HQL语句,数据库中有了相应记录,
但是数据库用select查不到,这是跟数据库事物级别有关系。
Session.beginTrransaction()。commit();
事物提交后可以查询到了。
Session.flush()语句但是为什么不写呢,因为commit()会默认调用flush();
管理一级缓存
无论何时,当你给save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。当随后flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。
如若要把所有的对象从session缓存中彻底清除,则需要调用Session.clear()。
Hibernate二级缓存(sessionFactory级别缓存)
二级缓存需要sessionFactory来管理,它是进初级的缓存,所有人都可以使用,它是共享的。
Hibernate二级缓存支持对象缓存、集合缓存、查询结果集缓存,对于查询结果集缓存可选。
二级缓存比较复杂,一般用第三方产品。hibernate提供了一个简单实现,用Hashtable做的,只能作为我们的测试使用,商用还是需要第三方产品。几种优秀缓存方案:1、Memcached 分布式缓存系统 2、JBOSS CACHE 3 、EhCache Ehcache 2.1起提供了针对Hibernate的JTA支持。 4、Infinispan 开源数据网格平台
使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。
hibernate做了一些优化,和一些第三方的缓存产品做了集成。这里采用EHCache缓存产品。
和EHCache二级缓存产品集成:EHCache的jar文件在hibernate的lib里,我们还需要设置一系列的缓存使用策略,需要一个配置文件ehcache.xml来配置。
hibernate.cfg.xml 配置
- <!-- 开启二级缓存 -->
- <property name="hibernate.cache.use_second_level_cache">true</property>
- <!-- 开启查询缓存 -->
- <property name="hibernate.cache.use_query_cache">true</property>
- <!-- 二级缓存区域名的前缀 -->
- <!--<property name="hibernate.cache.region_prefix">h3test</property>-->
- <!-- 高速缓存提供程序 第三方产品-->
- <property name="hibernate.cache.region.factory_class">
- net.sf.ehcache.hibernate.EhCacheRegionFactory
- </property>
- <!-- 指定缓存配置文件位置 -->
- <property name="hibernate.cache.provider_configuration_file_resource_path">
- ehcache.xml
- </property>
- <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->
- <property name="hibernate.cache.use_structured_entries">true</property>
- <!-- Hibernate将收集有助于性能调节的统计数据 -->
- <property name="hibernate.generate_statistics">true</property>
ehcache配置(ehcache.xml)
- <?xml version="1.0" encoding="UTF-8"?>
- <ehcache name="h3test"> <!--指定区域名-->
- <defaultCache
- maxElementsInMemory="100" <!--缓存在内存中的最大数目-->
- eternal="false" <!--缓存是否持久-->
- timeToIdleSeconds="1200" <!--当缓存条目闲置n秒后销毁-->
- timeToLiveSeconds="1200" <!--当缓存条目存活n秒后销毁-->
- overflowToDisk="false"> <!--硬盘溢出-->
- </defaultCache>
- </ehcache>
只读缓存 read only
不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略
实体只读缓存
- <hibernate-mapping>
- <class name="com.ljb.entity.Voucher" table="Voucher">
- <cache usage="read-only"/>
- ……
- </hibernate-mapping>
二级缓存测试代码
Session session1 = sf.openSession();
Transaction t1 = session1.beginTransaction();
//确保数据库中有标识符为1的Voucher
Voucher voucher = (Vocher) session1.get(Vocher.class, 1);
//如果修改将报错,只读缓存不允许修改
//voucher.setName("aaa");
t1.commit();
session1.close();
Session session2 = sf.openSession();
Transaction t2 = session2.beginTransaction();
voucher = (Vocher) session2.get(Vocher.class, 1); //在二级缓存中查找结果,不会产出sql语句,不操作数据库
t2.commit();
session2.close();
sf.close(); -
只读缓存不允许更新,将报错Can't write to a readonly object。允许新增,( 新增直接添加到二级缓存)
读写缓存 read write
对缓存的更新发生在数据库事务完成后。缓存需要支持锁。 在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。 锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库
实体读/写缓存
|