Hibernate一级缓存 & 二级缓存(转)

Hibernate一级缓存 & 二级缓存(转)

一、一级缓存Session的操作与维护

1.Hibernate对象的三种状态: transient, persistent, detached

1) transient:瞬时状态           

利用new关键字创建的对象,没有与Hibernate实施交互的,也无法保证与数据库中某条记录对应的对象被称为瞬时状态,也就是生命周期非常短的意思,因为没有任何组件管理的对象非常容易被Java虚拟机回收。

例:Customer cus = new Customer();//瞬时状态对象

 

2) persistent:持久化状态

将瞬时状态的对象保存到Hibernate缓存中,受Hibernate管理的对象被称为持久化状态Hibernate在调用flush, close ,clear方法的时候会清理缓存保证持久化对象与底层数据库的同步,所有的持久化对象,Hibernate均会为它设置一个唯一性标识符保证其在缓存中的唯一性,这个标识符可能是hashCode,带主键查询的sql语句或者其他保证唯一的字符。

save(new object),update(new object),saveorupdate(new object),persisnt(new object)

可以将一个瞬时状态转变为持久化对象

 

save(new object), persistent (new object):利用select sql where id 加载一个对象

如果加载成功那么直接显示异常(插入不允许带相同标识符的组件在缓存中存在)

 

update(new object):不执行SQL语句,直接将对象加载入内存做更新准备

如果缓存中已经拥有一个同标识符的组件,那么显示异常,因为update只能做更新处理

无法处理两个完全一致的对象(merge(new object)可以克服这个问题)

 

saveorupdate(new object):利用select sql where id 加载一个对象

如果加载成功那么直接更新,否则插入

 

注意:业务层/表示层/应用层对于持久化状态对象的更改都会引起Hibernate的同步处理(反映到数据库中)

 

3) detached:游离托管状态

缓存中已经失去该对象或者该对象的标识符,虽然对象仍然存在,对象也和数据表中的记录有对应但是由于失去了Hibernate的控制,因此该对象被称为游离托管状态。

 

注意:业务层/表示层/应用层对于托管状态对象的更改不会会引起Hibernate的同步处理

 

游离托管状态的对象是指:已经经过Hibernate管理后,

因为Session.delete(),Session.close(),Session.clear(),Session.evict()

方法的执行而失去标识符的,所以托管状态和瞬时状态的对的区别是是否受过Hibernate的管理,数据表中是否有对应的记录,托管对象只有通过lock(),update(),saveorupdate()被重新加入缓存变成持久化对象才能实施数据同步

 

2、特殊方法:persisnt(),merge()

persisnt和save方法是差不多的,唯一的区别是针对sqlserver的identity字段的处理save为了保证持久化标识符,所以会在save的过程中就直接执行insert into....select identity();以获取最新的主键信息。

persisnt执行的时机是Transaction.commit(),Session.clear(),Session.flush()的时候

 

merge()与update()类似,但是区别是对于瞬时状态的理解。

假设现在有一个瞬时状态的new Customer(1),同时Session利用get(),load()方法

产生一个持久化状态的对象Session.get(Customer.class,1),这个时候使用update方法会抛出异常,而merge会将瞬时状态Customer中的属性复制到持久化状态的Customer

中。

 

3、查询对于一级缓存的使用

1)Session.get, Session.load方法

Session.get:迫切加载

第一查询:查询一级缓存Session缓存,如果没有那么查询二级缓存SessionFactory缓存(如果没有配置,此步省略),如果找不到那么执行Select…..From…..Where id = ?,如果数据被查到那么将查到的数据封装到对象中,对象被分别保存在一级缓存Session缓存中,和二级缓存SessionFactory缓存(如果没有配置,此步省略)。

第二查询:查询一级缓存Session缓存,,如果找不到那么执行Select…..From…..Where id = ?,如果数据被查到那么将查到的数据封装到对象中,对象被保存在一级缓存Session缓存中。

Session.load:延迟加载

Session.load认为加载始终是成功的,所以它始终不会与数据库交互,因为load认为查询一定会成功,因此只有当需要访问被加载实体属性的时候,Session.load才会按照Session.get的查询轨迹实施搜寻,不同的是Session.load始终会查询一,二级缓存再执行SQL语句

如果SQL语句无法返回对象,那么Session.load直接抛出异常。

 

2)Query.list, Query.iterator方法

Query.list:查询过程中不会读取任何缓存尤其是一级缓存,而是直接执行SQL语句,SQL语句有Query的HQL转换而得,执行结果会被保存在一级缓存中。

我们可以通过ehcache缓存组件为Hibernate配置查询缓存(query cache[hibernate 3.x以上版本]),这Query.list会每次查询的过程中先访问二级缓存中的查询缓存,如果没有再执行SQL语句,查询返回的结果会分别保存在一,二级缓存中。

 

Query.iterator:与Query.list的不同在于,它会每次访问均查询一级缓存,但是

Query.iterator记载数据的方式不是完整的SQL语句,而是N+1条SQL语句

例如:Query.list 对于一张5条记录的表的检索方式是Select ….. From Customer

而Query.iterator的检索方式是:

执行5句Select ….. From Customer where id = ?(单个对象实施记载)

  

二、二级缓存SessionFactory 的配置与测试

1. 利用ehcache配置Second level cache 和 Query cache

在src目录下建立ehcache.xml文件内容如下:

复制代码
1 <?xml version="1.0" encoding="UTF-8" ?>
2 <ehcache>
3 <!4     设置对象钝化目录,二级缓存中长时间不使用或者超时的对象
5     会被保存在当前目录\java\io\tmpdir目录中,这样可以节省空间 
6 -->
7 <diskStore path="java.io.tmpdir"/>
8 
9 </ehcache>
复制代码
复制代码
 1 <!-- 默认二级缓存工作模式
 2          maxElementsInMemory:缓存中最大对象数
 3          eternal:缓存中的对象是否永久保留
 4          timeToIdleSeconds:多少毫秒后可以考虑一个对象的放入钝化目录中
 5          timeToLiveSeconds:多少毫秒后可以考虑一个对象从激活状态-闲置状态
 6          overflowToDisk:是否允许将闲置对象钝化入硬盘
 7          diskPersistent:钝化后该对象是否允许永久无法反钝化
 8          diskExpiryThreadIntervalSeconds:钝化线程间隔处理时间(毫秒)
 9          memoryStoreEvictionPolicy:钝化对象选择模型(LRU:使用最少的先钝化技术)
10     -->
11     <defaultCache
12             maxElementsInMemory="10000"
13             eternal="false"
14             timeToIdleSeconds="120"
15 
16             timeToLiveSeconds="120"
17             overflowToDisk="true"
18             diskPersistent="false"
19             diskExpiryThreadIntervalSeconds="120"
20             memoryStoreEvictionPolicy="LRU"
21 
22             />
复制代码
复制代码
1 <!-- 默认二级查询缓存工作模式-->
2    <cache name="org.hibernate.cache.StandardQueryCache"
3             maxElementsInMemory="10000" 
4             eternal="false" 
5             timeToIdleSeconds="1800" 
6             timeToLiveSeconds="0" 
7             overflowToDisk="true"/>
复制代码
复制代码
 1 <!-- 为时间邮差准备的二级查询缓存工作模式
 2          主要有同步数据方法调用,例如lock(LockMode.READ)
 3     -->         
 4    <cache name="org.hibernate.cache.UpdateTimestampsCache"
 5             maxElementsInMemory="5000"
 6             eternal="true"
 7             timeToIdleSeconds="1800"
 8 
 9             timeToLiveSeconds="0"             
10             overflowToDisk="true"/>
复制代码

2. 配置h 注意:缓存监控方法如果要能够执行,需要在hibernate.cfg.xml中设置以下配置

ibernate.cfg.xml

复制代码
 1 <!-- 使用ehcache第三方缓存技术 -->
 2     <property name="cache.provider_class">
 3         net.sf.ehcache.hibernate.EhCacheProvider
 4     </property>
 5     <!-- 启用运行查询缓存 -->
 6     <property name="cache.use_query_cache">true</property>
 7 
 8     <!-- 启用二级缓存(SessionFactory缓存) -->
 9     <property 
10     name="cache.use_second_level_cache">true</property>
复制代码

3. 为需要二级缓存管理的对象设置标识列(*.hbm.xml)

复制代码
 1 <hibernate-mapping>
 2  <class name="cn.newtouch.myhibernate.po.Customer" schema="HIBERNATE" table="CUSTOMER">
 3 
 4   <!— 表示Customer需要受到缓存管理 -->
 5     <cache usage="read-write"/>
 6   <id name="id" type="java.lang.Long">
 7    <column name="ID" precision="8" scale="0"/>
 8 
 9    <generator class="assigned"/>
10   </id>
复制代码

4. 测试实体查询对于二级缓存的执行模式:

复制代码
 1 public void testSecondLevelCacheForEntityQuery() {
 2         Configuration cfg = new Configuration().configure();
 3         SessionFactory factory = cfg.buildSessionFactory();
 4         Session cnn = factory.openSession();
 5         SessionStatistics ss = cnn.getStatistics();
 6         Statistics s = factory.getStatistics();
 7         cnn.get(Customer.class, 1l);
 8         System.out.println("FIRST GET:" +ss.getEntityCount());
 9         System.out.println("FIRST GET PUT:" +
10                                   s.getSecondLevelCachePutCount());
11         System.out.println("FIRST GET MISS:" + 
12     s.getSecondLevelCacheMissCount());
13         System.out.println("FIRST GET HIT:" + 
14     s.getSecondLevelCacheHitCount());    
15         cnn.get(Customer.class, 1l);
16             
17         System.out.println("SECOND GET:" +ss.getEntityCount());
18         System.out.println("SECOND GET PUT:" + 
19     s.getSecondLevelCachePutCount());
20         System.out.println("SECOND GET MISS:" + 
21     s.getSecondLevelCacheMissCount());
22         System.out.println("HIT:" + 
23     s.getSecondLevelCacheHitCount());
24         cnn.clear();
25         System.out.println("BEFORE CLEAR:" + ss.getEntityCount());
26         System.out.println("BEFORE CLEAR PUT:" + 
27     s.getSecondLevelCachePutCount());
28         System.out.println("BEFORE CLEAR MISS:" + 
29     s.getSecondLevelCacheMissCount());
30         System.out.println("BEFORE CLEAR HIT:" + 
31     s.getSecondLevelCacheHitCount());
32         
33     Session anotherSession = factory.openSession();
34         anotherSession.get(Customer.class, 1l);
35         System.out.println("ANOTHER SESSION:" + 
36     ss.getEntityCount());
37         System.out.println("ANOTHER SESSION PUT:" + 
38     s.getSecondLevelCachePutCount());
39         System.out.println("ANOTHER SESSION MISS:" + 
40     s.getSecondLevelCacheMissCount());
41         System.out.println("ANOTHER SESSION HIT:" + 
42     s.getSecondLevelCacheHitCount());
43         anotherSession.clear();
44     }
复制代码

测试二级缓存的结果是

复制代码
FIRST GET:1
FIRST GET PUT:1
FIRST GET MISS:1
FIRST GET HIT:0
=======================================
SECOND GET:1
SECOND GET PUT:1
SECOND GET MISS:1
SECOND GET HIT:0
=======================================
BEFORE CLEAR:0
BEFORE CLEAR PUT:1
BEFORE CLEAR MISS:1
BEFORE CLEAR HIT:0
=======================================
ANOTHER SESSION:0
ANOTHER SESSION PUT:1
ANOTHER SESSION MISS:1
ANOTHER SESSION HIT:1
复制代码

5. 测试HQL查询对于二级缓存的执行模式

复制代码
 1 public void testSecondLevelCacheForQueries() {
 2         
 3         Configuration cfg = new Configuration().configure();
 4         SessionFactory factory = cfg.buildSessionFactory();
 5         Session cnn = factory.openSession();
 6         
 7     SessionStatistics ss = cnn.getStatistics();
 8         Statistics s = factory.getStatistics();
 9         
10     Query query = 
11             cnn.createQuery("From Customer A");
12         query.setCacheable(true);
13     query.setCacheRegion(
14     "cn.newtouch.myhibernate.po.Customer");
15         query.list();
16         System.out.println("FIRST Query:" +ss.getEntityCount());
17 System.out.println("Level's PUT:" + 
18     s.getSecondLevelCachePutCount());
19 System.out.println("Level's MISS:" + 
20     s.getSecondLevelCacheMissCount());
21 System.out.println("Level's HIT:" +    
22     s.getSecondLevelCacheHitCount());
23 System.out.println("Queries's PUT:" + 
24     s.getQueryCachePutCount());
25 System.out.println("Queries's MISS:" + 
26     s.getQueryCacheMissCount());
27 System.out.println("Queries's HIT:" + 
28     s.getQueryCacheHitCount());
29 System.out.println("=======================================");
30 
31         query = cnn.createQuery("From Customer A");
32         query.setCacheable(true);
33     query.setCacheRegion(
34     "cn.newtouch.myhibernate.po.Customer");
35         query.list();
36             
37 System.out.println("SECOND Query:" +ss.getEntityCount());
38 System.out.println("Level's PUT:" + 
39     s.getSecondLevelCachePutCount());
40 System.out.println("Level's MISS:" + 
41     s.getSecondLevelCacheMissCount());
42 System.out.println("Level's HIT:" +    
43     s.getSecondLevelCacheHitCount());
44 System.out.println("Queries's PUT:" + 
45     s.getQueryCachePutCount());
46 System.out.println("Queries's MISS:" + 
47     s.getQueryCacheMissCount());
48 System.out.println("Queries's HIT:" + 
49     s.getQueryCacheHitCount());
50 System.out.println("=======================================");
51         
52         cnn.clear();
53         System.out.println("BEFORE CLEAR:" +ss.getEntityCount());
54 System.out.println("Level's PUT:" + 
55     s.getSecondLevelCachePutCount());
56 System.out.println("Level's MISS:" + 
57     s.getSecondLevelCacheMissCount());
58 System.out.println("Level's HIT:" +    
59     s.getSecondLevelCacheHitCount());
60 System.out.println("Queries's PUT:" + 
61     s.getQueryCachePutCount());
62 System.out.println("Queries's MISS:" + 
63     s.getQueryCacheMissCount());
64 System.out.println("Queries's HIT:" + 
65     s.getQueryCacheHitCount());
66 System.out.println("=======================================");
67     
68         Session anotherSession = factory.openSession();
69         
70         query = 
71             anotherSession.createQuery("From Customer A");
72         query.setCacheable(true);
73     query.setCacheRegion(
74     "cn.newtouch.myhibernate.po.Customer");
75         query.list();
76         
77 System.out.println("ANOTHER SESSION:" +ss.getEntityCount());
78 System.out.println("Level's PUT:" + 
79     s.getSecondLevelCachePutCount());
80 System.out.println("Level's MISS:" + 
81     s.getSecondLevelCacheMissCount());
82 System.out.println("Level's HIT:" +    
83     s.getSecondLevelCacheHitCount());
84 System.out.println("Queries's PUT:" + 
85     s.getQueryCachePutCount());
86 System.out.println("Queries's MISS:" + 
87     s.getQueryCacheMissCount());
88 System.out.println("Queries's HIT:" + 
89     s.getQueryCacheHitCount());
90 System.out.println("=======================================");
91         
92         anotherSession.clear();
93
复制代码

测试二级缓存的结果是:

复制代码
 1 FIRST Query:2
 2 Level's PUT:2
 3 Level's MISS:0
 4 Level's HIT:0
 5 Queries's PUT:1
 6 Queries's MISS:1
 7 Queries's HIT:0
 8 =======================================
 9 SECOND Query:2
10 Level's PUT:2
11 Level's MISS:0
12 Level's HIT:0
13 Queries's PUT:1
14 Queries's MISS:1
15 Queries's HIT:1
16 =======================================
17 BEFORE CLEAR:0
18 Level's PUT:2
19 Level's MISS:0
20 Level's HIT:0
21 Queries's PUT:1
22 Queries's MISS:1
23 Queries's HIT:1
24 =======================================
25 ANOTHER SESSION:0
26 Level's PUT:2
27 Level's MISS:0
28 Level's HIT:2
29 Queries's PUT:1
30 Queries's MISS:1
31 Queries's HIT:2
复制代码

注意:缓存监控方法如果要能够执行,需要在hibernate.cfg.xml中设置以下配置

 

1 <property name="generate_statistics">true</property>

 

以下情况适合使用二级缓存:

1很少被修改的数据

2不是很重要的数据,允许出现偶尔并发的数据 

3不会被并发访问的数据

4参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。



Hibernate二级缓存的并发策略你了解吗:

1 只读缓存 read only 
不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。

    如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略 。

2 读写缓存 read write 
对缓存的更新发生在数据库事务完成后。缓存需要支持锁。
在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。 
锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库。
缓存必须支持锁,事务支持则不是必须的。如果缓存是一个集群,“更新缓存”的调用会将新值推送给所有副本,这通常被称为“推(push)”更新策略。

    如果你的数据是又读又写的,那么使用“read-write”策略。这通常是性能第三的策略,因为它要求有缓存锁,缓存集群中使用重量级的“推”更新策略。

3 非严格读写缓存 nonstrict read write 
在一个事务中更新数据库,在这个事务完成前就清除缓存,为了安全起见,无论事务成功与否,在事务完成后再次清除缓存。
既不需要支持缓存锁,也不需要支持事务。如果是缓存集群,“清除缓存”调用会让所有副本都失效,这通常被称为“拉(pull)”更新策略。

    如果你的数据读很多或者很少有并发缓存访问和更新,那么可以使用“nonstrict-read-write”策略。感谢它的轻量级“拉”更新策略,它通常是性能第二好的策略。

4 事务缓存 transactional (一定要在JTA环境中)
对缓存和数据库的更新被包装在同一个JTA事务中,这样缓存与数据库总是保持同步的。数据库和缓存都必须支持JTA。

    除非你真的想将缓存更新和数据库更新放在一个JTA事务里,否则不要使用“transactional”策略,因为JTA需要漫长的两阶段提交处理,这导致它基本是性能最差的策略。


五、缓存锁的性能也要了解,知道加了锁后性能会下降: 
    为了保证数据的安全性,不发生脏数据,各个缓存通常使用锁来保证 
在本地方式运行时,缓存最大的开销就是使用锁来在保证共享数据完整性。 
在集群环境中,RPC调用,锁,是性能上大开销。

 

下面以JBoss Cache为例说一说锁:
JBoss Cache1.* 和 2.* 时代,提供乐观锁,悲观锁,但是性能不高。 
JBoss Cache3.0 MVCC锁方案性能很高。

悲观锁:这些锁的隔离级别和数据库实施的隔离级别相同,这种方案简单而且健壮,允许多用户同时读取数据。读操作阻塞写操作,悲观锁的读写是互斥的,无法同时进行的,写的性能不好。

乐观锁:这个方式则牵涉到数据版本,可以获得高度并发性。那些请求读取数据的用户不会因为并发数据库写入操作而受到阻塞。而且,乐观锁定方式还可以避免悲观锁定中有可能发生的死锁。但它仍然有两个主要的缺点:一是性能问题。因为不断的将结点的状态拷贝到每个并发线程所造成的内存和 CPU 开销是不容忽略的。二是尽管并发时允许了写操作,但是一旦发现数据的版本不对,事务提交时不可避免的还是会失败。也就是说,此时写事务虽然可以不受限制的进行大量处理和写操作,但是这样在事务结束的时候容易出现提交失败。

多版本并发控制(MVCC):在数据访问速度上较之前者也胜出百倍。MVCC 提供了非阻塞 (non-blocking) 读操作 ( 它并不会去阻塞 wirter threads) ,在避免死锁的同时也提供了更高级的并发机制。更棒的是,我们的 MVCC 实现甚至可以对 reader threads 完全不采用任何锁 ( 对于像缓存这样频繁读取的系统来说,意义太大了 ) ,

 

六、批量处理时请不要使用二级缓存
当你执行大量的 添加与修改时,并且这个实体对象被配置为启用二级缓存,你考虑过二级缓存会怎么样吗?请看下面代码:

Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); //如果你的 hibernate.cache.use_second_level_cache 是 true, 请在会话级别上关闭他 //向(任何一级)缓存中加载大量数据通常也意味着它们很快会被清除出去,这会增加GC开销。 session.setCacheMode(CacheMode.IGNORE); session.save(customer); if ( i % 50 == 0 ) { //将本批插入的对象立即写入数据库并释放内存 session.flush(); session.clear(); } } tx.commit(); session.close();

 
   

 


 

你可能感兴趣的:(Hibernate一级缓存 & 二级缓存(转))