1. 1.缓存概述  
  2.  
  3. 缓存(cache)在java应用程序中是一组内存中的集合示例,它保存着永久性存储源(如硬盘上的文件或数据库)中数据的备份,它的读写速度比读写硬盘的速度快。应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质  
  4.  
  5.   缓存的作用就是降低应用程序直接读写永久性数据存储源的频率,从而增强应用的运行性能  
  6.  
  7.   缓存的实现不仅需要作为物理介质的硬件(内存),同时还需要用于管理缓存的并发访问和过期等策略的软件  
  8.  
  9. 2.缓存范围分类  
  10.  
  11.  缓存的范围决定了缓存的声明周期以及可以被谁访问。总共分三类  
  12.  
  13. 1)事务范围   
  14.  
  15.  
  16. 事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围.   
  17.  
  18.  
  19. 2)应用范围(也叫进程范围)   
  20.  
  21.  
  22. 应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围.   
  23.  
  24.  
  25. 3)集群范围   
  26.  
  27.  
  28. 在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.   
  29.  
  30.  
  31. 对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定回避直接访问数据库数据的速度快很多  
  32.  
  33.  
  34. 3.缓存的并发访问策略  
  35.  
  36. 当多个并发的失误同时访问持久化层的缓存的相同数据时,会引发起并发问题,必须采用必要的失误隔离措施  
  37.  
  38.   在进程范围或集群范围的缓存,会出现并发问题,因此可以设定一下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低  
  39.  
  40.  
  41.  1)事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。  
  42.  
  43.  
  44.  2)读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。  
  45.  
  46.  
  47.  3)非严格读写型:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。     
  48.  
  49.  
  50.   4)只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。  
  51.  
  52.  
  53. Hibernate中的缓存  
  54.  
  55. hibernate中提供两级缓存,第一级别是Session级别的缓存,它是属于事务范围的缓存,第二级别的缓存是SessionFactory级别的缓存,它是属于进出呢个范围或集群范围的缓存。这一级别的缓存可以进行配置和更改,并且可以进行动态的加载和卸载。Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存  
  56.  
  57.  
  58. 一级缓存的管理:  
  59.  
  60. Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,当程序调用save(),update(),saveorupdate()等方法 及调用查询接口list,filter,iterate时,如session缓存中还不存在相应的对象,Hibernate会把该对象加入到一级缓存中,  
  61. 当Session关闭的时候该Session所管理的一级缓存也会立即被清除  
  62. Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置  
  63.  
  64. 一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。所以说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID  
  65. 虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过Session中所提供的方法来对一级缓存的管理进行手工干预。Session中所提供的干预方法包括以下两种  
  66. ●evict() :用于将某个对象从Session的一级缓存中清除  
  67. evict()方法适用于以下二种情况:  
  68. 1)不需要该对象进行同步的数据更新  
  69. 2)在批量进行更新与删除时,当更新删除每一个对象后,要释对此对象所占用的内存.  
  70.  
  71. ●clear() :用于将一级缓存中的所有对象全部清除。</p>  
  72.  
  73. <p class=MsoNormal>       在进行大批量数据一次性更新的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。  
  74. Hibernate大批量更新时缓存的处理方法:  
  75. (假设我们user表的age有5000条大于0的记录,)  
  76. Session session =SessionFactory.openSession();  
  77. Transaction tx =session.beginTransaction();  
  78. Itertaor users=session.find("from User u where u.age>0").itertaor();//HSL语句就不做解释了  
  79. while(user.hasNext()){  
  80. User user =(User)users.next();  
  81. user.setAge(user.getAge()+1);  
  82. //将本批插入的对象立即写入数据库并释放内存  
  83. session.flush();  
  84. session.clear();  
  85. }  
  86. tx.commit();  
  87. session.close();  
  88. 用Hibernate处理大批数据时..都必须先执行5000次的update语句,然后才能更新5000个user 对象..  
  89. 这样就影响到了操作上的性能....在项目当我们遇到性能与空间的问题时,,,要以性能为主..这也就是说要牺牲空间  
  90.  
  91. 所以程序最好跳过Hibernate API  而直接通过JDBC API来执来...  
  92.  
  93. 我们改一下上面的代码:  
  94. Session session=SessionFactory.openSession();  
  95. Transaction tx =session.beginTransaction();  
  96. Connection conn =session.connection();  
  97. PreparedStatement  pstmt = conn.prepareStatement("update users set age=age+1 "+"where age >0");  
  98. pstmt.executeUpdate();  
  99. tx.commit();  
  100. 虽说这是通过JDBC API搞作的..但本质上还是通过Hibernater Transaction的事务这个接口来声明事务的边界的...  
  101.  
  102. 其实最好的解决方法就是以创建存储过程,,用底层的数据库运行..这样性能好,速度快....  
  103.  
  104. 我就简单的以Oracle数据库为例子.创建一个名为UserUpdate的存储过程...然后在程序中进行调用...  
  105.      UserUpdate的存储过程代码:  
  106.                       create or  replace procadure UserUpdate(u_age in number) as 
  107.                       begin 
  108.                                    update users set age=age+1 where age>u_age;  
  109.                         end;     
  110.   下面的是在程序中如何调用我们命名的存储过程  
  111.                       Session session =SessionFactory.openSession();  
  112.                      Transaction tx =session.beginTransaction();  
  113.                      Connection conn=session.connection();  
  114.                    String str="{call UserUpdate(?)}";  
  115.                    CallableStatement cstmt= conn.prepareCall(str);  
  116.                    cstmt.setInt(1,0);  
  117.                   cstmt.executeUpdate();  
  118.                   tx.commit();   注意.开源的MySQL中不支持存储过程的..  
  119. 用JDBC API的好处是这样的..  
  120. 它不用把大批量的数据事先加载到内存中,然后再进行更新与修改..所以不会消耗大量内存....  
  121. (小程序中是看不出什么差别的..当数据的记录达到一定的数据量的时候自然会发现用Hibernate API 与JDBC API的差别)  
  122. 在一个就是只能一条记录进行批量更新..不像Hibernate中更新每一条的..  
  123.  
  124. 第一级是Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法比卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。  
  125.  
  126.  
  127.  
  128. 二级缓存管理  
  129.  
  130.    
  131.  
  132.   第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。这个缓存中存放的对象的松散数据。第二级对象有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。缓存适配器用于把具体的缓存实现软件与Hibernate集成。第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。  
  133.  
  134.  
  135.   Hibernate的二级缓存策略的一般过程如下:  
  136.  
  137.  
  138.   1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。  
  139.  
  140.  
  141.   2) 把获得的所有数据对象根据ID放入到第二级缓存中。  
  142.  
  143.  
  144.   3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。  
  145.  
  146.  
  147.   4) 删除、更新、增加数据的时候,同时更新缓存。  
  148.  
  149.  
  150.   Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。  
  151.  
  152.  
  153.   Hibernate的Query缓存策略的过程如下:  
  154.  
  155.  
  156.   1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。  
  157.  
  158.  
  159.   2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。  
  160.  
  161.  
  162.   3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。  
  163.  
  164.    
  165.  
  166.  
  167.  适合存放到二级缓存中的数据有以下四种:  
  168.  
  169. 1)很少被修改的数据  
  170.  
  171. 2)不是很重要的数据,允许偶尔并发的数据  
  172.  
  173. 3)不会被并发反问的数据  
  174.  
  175. 4)参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用。它的实例极少或从来不会被修改  
  176.  
  177. 对于那些常被修改的数据,如财务数据(绝对不允许出现并发)和其他应用共享的数据,这些都不能放到第二级缓存中  
  178.  
  179.  
  180. 常用的缓存插件  
  181.  
  182. Hibernate的二级缓存是一个插件,下面是几种常用的缓存插件  
  183.  
  184. 1)EhCache:可作为进程访问的缓存,存放的物理介质可以是内存或硬盘,对hibernate的查询缓存提供了支持  
  185.  
  186. 2)OSCache:可作为进程范围的缓存,存放数据的物理介质可以使内存或硬盘,体统丰富的缓存数据过期策略,hibernate的查询缓存提供了支持  
  187.  
  188. 3)SwarmCache:可作为集群范围内的缓存,但不支持hibernate查询缓存  
  189.  
  190. 4)TreeCache:可作为集群范围内的缓存,支持事务性并发访问策略,对hibernate的查询缓存提供了支持  
  191.  
  192.  
  193. 二级缓存示例  
  194.  
  195. 配置一:  
  196.  
  197.    
  198.  
  199. hibernate.cfg.xml文件中增加  
  200.  
  201. Java代码  收藏代码  
  202.  
  203.     <span style="font-size: large;"><!--开启二级缓存-->    
  204.     <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>    
  205.     <!--启用查询缓存-->    
  206.     <property name="hibernate.cache.use_query_cache">true</property></span>    
  207.  
  208.    
  209.  
  210.  配置二:  
  211.  
  212. 工程项目src文件下新建一个ehcache.xml文件,其内容为  
  213.  
  214. Java代码  收藏代码  
  215.  
  216.     <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8"?>    
  217.     <ehcache>    
  218.     <diskStore path="java.io.tmpdir" />    
  219.     <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />    
  220.         
  221.     </ehcache></span>    
  222.  
  223.  配置三:  
  224.  
  225. 为了缓存某类的对象,其hbm文件中需添加<cache usage="read-only"/>属性例如:  
  226.  
  227. Xml代码  收藏代码  
  228.  
  229.     <span style="font-size: large;"><?xml version="1.0"?>    
  230.     <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"    
  231.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">    
  232.     <!--    
  233.         Mapping file autogenerated by MyEclipse - Hibernate Tools   
  234.     -->    
  235.     <hibernate-mapping>    
  236.         
  237.        <class name="com.vogue.bbsphoto.entity.Forum"     
  238.        table="cdb_forums">    
  239.             <cache usage="read-only"/>    
  240.             <id name="ID" column="fid" unsaved-value="null">    
  241.                 <generator class="increment" />    
  242.            </id>    
  243.         
  244.            <property name="name" column="name" type="string" />    
  245.            <property name="type" column="type" type="string" />    
  246.         </class>    
  247.     </hibernate-mapping>    
  248.     </span>    
  249.  
  250.  配置四:  
  251.  
  252. 为了使用查询缓存,Query必须设置cacheable为true,query.setCacheable(true);  
  253.  
  254. 例如dao父类中用于hql查询的方法修改后为:  
  255.  
  256. Java代码  收藏代码  
  257.  
  258.     <span style="font-size: large;">/**   
  259.          * 执行hql语句的查询   
  260.        
  261.          * @param sql   
  262.          * @return   
  263.          */    
  264.        public List executeQuery(String hql){    
  265.             List list = new ArrayList();    
  266.             Session session = HibernateSessionFactory.currentSession();    
  267.            Transaction tx = null;    
  268.             Query query = session.createQuery(hql);    
  269.           query.setCacheable(true);    
  270.            try {    
  271.                 tx = session.beginTransaction();    
  272.                 list = query.list();    
  273.                tx.commit();    
  274.             } catch (Exception ex) {    
  275.                ex.printStackTrace();    
  276.                 HibernateSessionFactory.rollbackTransaction(tx);    
  277.                     
  278.             } finally {    
  279.         
  280.                 HibernateSessionFactory.closeSession();    
  281.            }    
  282.             return list;    
  283.        }    
  284.     </span>    
  285.  
  286.  补充一下:当要缓存的对象处于级联关系中时。如果和他存在级联关系的对象都有属性 <cache usage="read-only"/>那么,在第一次get后该对象所处的对象图中的所有对象都会保存到hibernate的二级缓存中,在第二次get该对象时,直接从二级缓存中找到所有级联的对象;如果其中某个级联对象没有<cache usage="read-only"/>属性,则不会被保存到二级缓存中,以后每次get时仍然会执行sql去数据库中找该级联对象