zoukankan      html  css  js  c++  java
  • Hibernate 缓存

      我这里使用的是Hibernate5.2.0版本

      Hibernate缓存分为一级缓存(有的也叫Session缓存)和二级缓存。

    一级缓存(Session)

      一级缓存的生命周期和session的生命周期一致,当前sessioin一旦关闭,一级缓存就消失,因此一级缓存也叫session级的缓存或事务级缓存。一级缓存只存实体对象的 ,它不会缓存一般的对象属性(查询缓存可以),即当获得对象后,就将该对象的缓存起来,如果在同一session中如果再去获取这个对象 时,它会先判断缓存中有没有该对象的ID,如果有就直接从缓存中取出,反之则去数据库中取,取的同时将该对象的缓存起来,有以下方法可以 支持一级缓存:

          get() 
          load() 
          iterate(查询实体对象) 
    其 中 Query 和Criteria的list() 只会缓存,但不会使用缓存(除非结合查询缓存)。 

      下面我们进行代码测试:

    测试1:

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 User user=session.get(User.class, 2);
     5 System.out.println(user);
     6 
     7 User user2=session.get(User.class, 2);
     8 System.out.println(user2);
     9 
    10 tran.commit();
    11 session.close();

      输出结果:

    1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    2 User [id=2, name=姓名2]
    3 User [id=2, name=姓名2]

      可以很清楚的看到,这里只执行了一条SQL语句,当使用get方法时,获取到一个对象,并将这个对象缓存到session中,当下次再从这个session中获取Id=2的User对象,那么session首先从缓存中查询是否有一个id=2的User对象,如果就返回,这里刚好有一个,所有就不会执行第二条SQL语句。

    测试2:

      

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 User user=session.get(User.class, 200);
     5 System.out.println(user);
     6 
     7 User user2=session.get(User.class, 200);
     8 System.out.println(user2);
     9 
    10 tran.commit();
    11 session.close();

      输出结果

    1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    2 null
    3 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    4 null

      因为ID=200的User对象在数据库中并不存在,所有第一条语句执行后,输出该对象为null,当再次从使用get方法时,Hibernate先判断session是否有id=200的User对象,这里很明显并没有。所有再次从数据库中查找,执行了第二条语句

    测试3

      

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 User user=session.get(User.class, 2);
     5 System.out.println(user);
     6 
     7 tran.commit();
     8 session.close();
     9         
    10 System.out.println("---------------------");
    11 
    12 session=factory.openSession();
    13 tran=session.beginTransaction();
    14 
    15 user=session.get(User.class, 2);
    16 System.out.println(user);
    17 
    18 tran.commit();
    19 session.close();

      输出结果:

    1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    2 User [id=2, name=姓名2]
    3 ---------------------
    4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    5 User [id=2, name=姓名2]

      这里我们可以看出,执行了两条语句。我们知道一级缓存是基于Session,每个Session缓存的数据并不能共享,当关闭Session时,这个Session中的缓存也会被清楚。上面我们可以看到两个Session并不是同一个Session对象,所有会读取两条操作。那么怎么才可以夸Session读取缓存信息了。这里就要用到我们下面要讲解的二级缓存。

    二级缓存  

      二级缓存也称进程级的缓存或SessionFactory级的缓存,二级缓存可以被所有的session共享,二级缓存的生命周二级缓存的生命周期和 SessionFactory的生命周期一致。 

      二级缓存的工具包也很多,我们这里讲解EhCache

      hibernate.cfg.xml配置

    <!-- 启用二级缓存 -->
    <property name="cache.use_second_level_cache">true</property>
    <!-- 选择二级缓存的工具类 -->
    <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

      第一句代码,是启用二级缓存,要想使用二级缓存,我们必须先启用二级缓存。第二句代码是选择第三方缓存提供类,我们这里使用EhCache。以前版本的Hibernate可能会用cache.provider_class。

      并不是所有的类都需要使用缓存机制,比如财务上的数据,变更比较大,就不能使用缓存。而基本上没有变更或变更比较少的话就可以使用缓存。要想使某个类型的对象使用使用缓存,我们也必须给这个类定义缓存声明

    1 <class-cache usage="read-write" class="com.myproc.domain.User"/>

      属性:class用来指定对那个类使用缓存,这里必须是全限命名(包括包名)

      usage:指定缓存策略

    ☆实体缓存

    测试代码:

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 User user=session.get(User.class, 2);
     5 System.out.println(user);
     6 
     7 tran.commit();
     8 session.close();
     9         
    10 System.out.println("---------------------");
    11 
    12 session=factory.openSession();
    13 tran=session.beginTransaction();
    14 
    15 user=session.get(User.class, 2);
    16 System.out.println(user);
    17 
    18 tran.commit();
    19 session.close();

      输出结果

    1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    2 User [id=2, name=姓名2]
    3 ---------------------
    4 User [id=2, name=姓名2]

      可以看到,虽然是两个不同的Session,但是还是只执行了一条SQL语句,说明二级缓存起了作用。

       【总结】:通过Session.get()方法和Session.load()方法都可以将一个实体对象缓存到一级缓存中。如果开启了二级缓存,那么数据也会缓存到二级缓存中

    ☆集合属性的缓存

      代码测试:

     1 Dept dept=session.get(Dept.class, 5);
     2 System.out.println(dept);
     3 System.out.println(dept.getUsers())
     4 tran.commit();
     5 session.close();
     6         
     7 System.out.println("---------------------");
     8 
     9 session=factory.openSession();
    10 tran=session.beginTransaction();
    11 
    12 dept=session.get(Dept.class, 2);
    13 System.out.println(dept);
    14 System.out.println(dept.getUsers());
    15 tran.commit();
    16 session.close();

      输出结果:

    1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=?
    2 Dept [id=5, name=部门5]
    3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
    4 [User [id=49, name=姓名49], User [id=26, name=姓名26], User [id=35, name=姓名35], User [id=14, name=姓名14]]
    5 ---------------------
    6 Dept [id=5, name=部门5]
    7 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
    8 [User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14], User [id=35, name=姓名35]]

      这里我们可以看到select .... from dept 语句只有一句,说明对应Dept对象的二级缓存是成功了的。但是当我们查看该对象下users集合中的值时,在第一次查询的时候从数据库中读取了一次,第二次查询的时候又读取了一次,显然,对应对象的集合属性并没有缓存到二级缓存中,那么怎么才能够缓存集合属性了?

      方法:在hibernate中添加collection-cache

    <collection-cache usage="read-only" collection="com.myproc.domain.Dept.users"/> 

      collection属性为类的全限命名+集合属性名称

      输出结果

    1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=?
    2 Dept [id=5, name=部门5]
    3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=?
    4 [User [id=35, name=姓名35], User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14]]
    5 ---------------------
    6 Dept [id=5, name=部门5]
    7 [User [id=35, name=姓名35], User [id=14, name=姓名14], User [id=26, name=姓名26], User [id=49, name=姓名49]]

     ☆查询缓存

      首先看一下下面的代码:

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 List list= session.createQuery("FROM User WHERE id<5").list();
     5 System.out.println(list);
     6 
     7 tran.commit();
     8 session.close();
     9         
    10 System.out.println("---------------------");
    11 
    12 session=factory.openSession();
    13 tran=session.beginTransaction();
    14 
    15 User user=session.get(User.class, 1);
    16 System.out.println(user);
    17 
    18 List list2= session.createQuery("FROM User WHERE id<5").list();
    19 System.out.println(list2);
    20 
    21 tran.commit();
    22 session.close();
    23 
    24 tran.commit();
    25 session.close();

      输出结果

    1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
    2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]
    3 ---------------------
    4 User [id=1, name=姓名1]
    5 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
    6 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]

      当我们第一次使用HQL查询出一个id<5的集合。在第一次使用HQL同样查询id<5的集合,还是在数据库中再查询了异常,结论查询语句不会使用缓存。

      当在第二次中我们使用session.get()方式,我们查询id=1的user时,结果没有想数据库中查询,说明缓存中存在对应的数据。

      【综上所诉】:查询语句不会使用缓存,但是会将查询的结果放在缓存中

    Question:对于查询语句难道我们就没有办法进行缓存了吗?

      Answer:答案是我们是有办法的。看看下面的代码

     1 Session session=factory.openSession();
     2 Transaction tran=session.beginTransaction();
     3 
     4 Iterator iterator1= session.createQuery("FROM User WHERE id<5").iterate();
     5 while(iterator1.hasNext()){
     6     System.out.println(iterator1.next());
     7 }
     8 
     9 tran.commit();
    10 session.close();
    11         
    12 System.out.println("---------------------");
    13 
    14 session=factory.openSession();
    15 tran=session.beginTransaction();
    16 
    17 User user=session.get(User.class, 1);
    18 System.out.println(user);
    19 
    20 Iterator iterator2= session.createQuery("FROM User WHERE id<5").iterate();
    21 while(iterator2.hasNext()){
    22     System.out.println(iterator2.next());
    23 }
    24 
    25 tran.commit();
    26 session.close();

      输出结果

     1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5
     2 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     3 User [id=1, name=姓名1]
     4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     5 User [id=2, name=姓名2]
     6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     7 User [id=3, name=姓名3]
     8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     9 User [id=4, name=姓名4]
    10 ---------------------
    11 User [id=1, name=姓名1]
    12 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5
    13 User [id=1, name=姓名1]
    14 User [id=2, name=姓名2]
    15 User [id=3, name=姓名3]
    16 User [id=4, name=姓名4]

      结果说明:在第一次查询的时候,首先查询了满足条件对象的所有id值,然后在通过迭代方式,没迭代一次就查询一次对应id的对象,并将该对象放入缓存中。这种就是N+1的查询。N表示有多少个对象

           在第二次中,使用session.get能够读取到之前缓存的数据,如果在使用HQL语句查询,那么还是先从数据库中查询出满足条件的所有ID,如果对应ID在缓存中存在,则直接从缓存中读取,否则需要再次从数据库中查询出来

      

      上面的例子,刚好第二次查询所有数据都在缓存中存在,如果我们第二次查询id<8,那么我们看到结果将会是怎样

     1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<8
     2 User [id=1, name=姓名1]
     3 User [id=2, name=姓名2]
     4 User [id=3, name=姓名3]
     5 User [id=4, name=姓名4]
     6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     7 User [id=5, name=姓名5]
     8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
     9 User [id=6, name=姓名6]
    10 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=?
    11 User [id=7, name=姓名7]

      

      虽然使用迭代的方式可以HQL语句缓存问题,但是对于N+1条查询,的确非常影响性能,如果数据非常多,反而结果并不理想,所有我们推荐使用下面一种方式:

     1 List list1=session.createQuery("FROM User WHERE id<5")
     2                     .setCacheable(true)
     3                     .list();
     4 System.out.println(list1);
     5 tran.commit();
     6 session.close();
     7         
     8 System.out.println("---------------------");
     9 
    10 session=factory.openSession();
    11 tran=session.beginTransaction();
    12 
    13 List list2=session.createQuery("FROM User WHERE id<5")
    14 .setCacheable(true)
    15 .list();
    16 System.out.println(list2);

      对Query设置能够使用缓存setCacheable(true),同时还需要启用查询缓存

    <!-- 启用查询缓存 -->
    <property name="cache.use_query_cache">true</property>

      输出结果

    1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5
    2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]
    3 ---------------------
    4 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]

      【注意】:使用这种方式,要求条件不行完全一致,不存在包含关系

    ☆缓存策略

      a、只读缓存(read-only):只读策略,表示数据不能够被更改

      b、不严格的读/写缓存(nonstrict-read-write):需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好 

      c、读/写缓存(read-write):允许少量的修改数据

      d、事务缓存(transactional):缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境

      读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。 

      各策略的性能从下往上越来越好

    ☆使用ehcache.xml配置文件

      

     1 <!--
     2   ~ Hibernate, Relational Persistence for Idiomatic Java
     3   ~
     4   ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
     5   ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
     6   -->
     7 <ehcache>
     8 
     9     <!-- Sets the path to the directory where cache .data files are created.
    10 
    11          If the path is a Java System Property it is replaced by
    12          its value in the running VM.
    13 
    14          The following properties are translated:
    15          user.home - User's home directory
    16          user.dir - User's current working directory
    17          java.io.tmpdir - Default temp file path -->
    18     <diskStore path="D:/cache/"/>
    19 
    20 
    21     <!--Default Cache configuration. These will applied to caches programmatically created through
    22         the CacheManager.
    23 
    24         The following attributes are required for defaultCache:
    25 
    26         maxInMemory       - Sets the maximum number of objects that will be created in memory
    27         eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
    28                             is never expired.
    29         timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used
    30                             if the element is not eternal. Idle time is now - last accessed time
    31         timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used
    32                             if the element is not eternal. TTL is now - creation time
    33         overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
    34                             has reached the maxInMemory limit.
    35 
    36         -->
    37     <defaultCache
    38         maxElementsInMemory="10"
    39         eternal="false"
    40         timeToIdleSeconds="120"
    41         timeToLiveSeconds="120"
    42         overflowToDisk="true"
    43         />
    44 </ehcache>

      <diskStore path="D:/cache/"/>:表示当缓存超出了maxElementsInMemory是,存到到硬盘的什么位置

      在hibernate.cfg.xml文件中我们还需要指定ehcache的位置

    1 <!-- 设置ehcache配置文件 -->
    2 <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property>

      

      下面我看一看在硬盘中缓存的文件

    【最后提醒】

      如果对于指定了可读性的策略是,通过HQL语句update或delete操作修改了对象后,hibernate会通知二级缓存,删除对于该对象的缓存信息。当下一次访问该对象时,首先访问二级缓存,此时二级缓存中该对象根本就不存在了,所有会查询数据库,然后将查询来的对象再次保存到二级缓存中。所有我们直观的看到,二级缓存中的信息是被更改了的。

      而对于一级缓存,对于update的操作,不会通知session,所有对象属性还是在没有保存之前的数据,如果需要查看对象的新状态,则使用session.refresh(object);

  • 相关阅读:
    Java集合:HashMap底层实现和原理(源码解析)
    Java获取异常堆栈信息
    win7 64位系统 PB连接oracle数据库出现“oracle library oci.dll could not be loaded”问题的解决方法
    Oralce 使用递归方式获取BOM树显示结构
    Oracle 链接数据库语句
    根据数据窗口某列的值定位行
    pb中数据窗口filter函数和retrieve函数的区别和联系
    用代码保存共享文件夹登录名和密码
    PB 组合数据窗口子窗口数据赋值方法
    PB 导出PDF
  • 原文地址:https://www.cnblogs.com/caoyc/p/5609326.html
Copyright © 2011-2022 走看看