Hibernate缓存

1、一级缓存:Session 级别的缓存

2、二级缓存: SessionFactory 级别的缓存

3、查询缓存:需二级缓存的支持,查询缓存依赖二级缓存

一级缓存

1、依赖于 Session 对象的,因此 只要 Session 还有效,它就存在,无法关闭。所有的持久化对象都被 Session 所缓存。

2、get 、find 、load 方法对 一级缓存 的使用

  • 先查找 一级缓存 中是否有 相应的 id 对应的对象,如果有就立即返回(不再查询数据库)
  • 如果在 一级缓存 中 未找到 相应的 id 对应的对象 ( 未命中 ) ,查询数据库
  • 如果数据库中没有相应的 id 对应的记录,get 、find 返回 null , load 则抛出 ObjectNotFoundException
  • 如果在数据库中找到相应的 id 对应的记录,则返回与之对应的 Java 对象 ( load 可能会延迟 )
  • 当 Java 对象被返回后,会将该对象 添加到 一级缓存 ( Session 级别的缓存 )

3、get 、load 、find三种查询方法的比较:

  • get 、load 、find 都是用来从数据库中加载一条记录并包装成指定类型的对象
  • get( Class<?> c , Serializable id )

    a、当 id 对应的数据在 数据库中不存在时,get 返回 null

    b、get 方法会立即查询数据库 并返回数据

  • load( Class<?> c , Serializable id )

    a、当 id 对应的数据在 数据库中不存在时,load 方法会抛出 ObjectNotFoundException
    b、load 方法 "默认" 不会立即查询数据库,而是等到要使用除了id之外的其它数据时才执行查询操作并返回数据
    关闭对 Customer 的延迟加载:

<class name="ecut.session.entity.Customer" table="t_customer"  lazy="false" >

   启用对 Customer 的延迟加载:

<class name="ecut.session.entity.Customer" table="t_customer"  lazy="true" >
  • find( Class<?> c , Serializable id ) 属于 JPA 规范中定义的方法 ( javax.persistence.EntityManager )

   a、当 id 对应的数据在 数据库中不存在时,find 返回 null
          b、find 方法会立即查询数据库 并返回数据

4、对一级缓存 进行管理的方法:

  • void   evict( Object o ) 从 Session 管理的缓存中驱逐单个对象
  • void   clear() 清除 Session 管理的缓存中所有的对象

5、测试案例

package ecut.cache.test;import java.util.Set;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestFirstLevelCache {private SessionFactory factory ;private Session session ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();session = factory.openSession();}public @Test void loadCustomer(){Customer c = session.get( Customer.class ,  1 );System.out.println( c.getEmail() );System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );// session.evict( c ); // 从 session 级别的缓存中驱逐 c 对象// session.clear();Customer x = session.get( Customer.class ,  1 );System.out.println( x.getEmail() );System.out.println(  c == x );//true,没有找数据库而是从session中直接取,这个就是一级缓存}public @Test void loadClazz(){Clazz c = session.get( Clazz.class ,  1 );System.out.println( c.getId() + " : " + c.getName() );Set<Student> students = c.getStudents();for( Student s : students ){System.out.println( "\t" + s.getId() + " : " + s.getName() );}System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );// session.evict( c ); // 从 session 级别的缓存中驱逐 c 对象,班级被驱逐里面的学生对象也被驱逐了// session.clear();Clazz x = session.get( Clazz.class ,  1 );System.out.println( x.getId() + " : " + x.getName() );Set<Student> set = x.getStudents();for( Student s : set ){System.out.println( "\t" + s.getId() + " : " + s.getName() );}}public @After void destory(){session.close();factory.close();}}

session没有关闭,因此在第二次通过load方法去查询时候,是从session中直接取并没有去查询数据库,这就是一级缓存。

二级缓存

1、高于一级缓存,在 Hibernate 中,默认是没有开启 二级缓存的,即使是 Session 关闭了,只要同一个 SessionFactory 对象还在,就有二级缓存可用。在启用二级缓存的时候,同一个session中如果将一个对象驱逐了,再去获取的时候会再查询数据库,只会在一级缓存中查找,并不会查看二级缓存,因此session中没有了缓存会再次发起查询。二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去。

2、开启 对 二级缓存 的支持 ( EHcache)

  • 导入 支持 二级缓存 的 jar 包(jar包位置:hibernate-release-5.2.10.Final\lib\optional\ehcache)

    ehcache-2.10.3.jar
    hibernate-ehcache-5.2.10.Final.jar
    slf4j-api1.7.7.jar(日志)

  • 在 hibernate.cfg.xml 中开启,启用二级缓存支持

    <!-- 启用二级缓存 ,EhCacheRegionFactory由hibernate提供-->
    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
  • 在映射文件中设置缓存策略

    班级映射配置文件

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="ecut.cache.entity.Clazz" table="t_class"><!-- 指定 Clazz 类型的 对象的 缓存策略  --><cache usage="read-write" /><id name="id" type="integer" column="id" ><generator class="increment" /> </id><property name="name" type="string" column="name" /><!-- 在 set 、list 、map 等标签内部 可以指定 集合的 缓存策略  cascade级联--><set name="students" order-by="id ASC"  cascade="all" ><!-- <cache usage="read-write"/>  --><!-- 指定集合的缓存策略 --><key column="class_id" /><one-to-many class="ecut.cache.entity.Student" /></set></class></hibernate-mapping>

    二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。

    学生映射文件

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="ecut.cache.entity.Student" table="t_student"><!-- 指定 Student  类型的 对象的 缓存策略  --><cache usage="read-write" /><id name="id" type="integer" column="id" ><generator class="increment" /> </id><property name="name" type="string" column="name" /></class></hibernate-mapping>

    客户类映射配置文件

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="ecut.cache.entity.Customer" table="t_customer" lazy="true" ><!-- 指定 Customer 类型的 对象的 缓存策略  --><cache usage="read-write" /><id name="id" type="integer" column="id" ><generator class="increment" /></id><property name="email" type="string" column="email" /><property name="password" type="string" column="password" /><property name="nickname" type="string" column="nickname" /><property name="gender" type="character" column="gender" /><property name="birthdate" type="date"  column="birthdate" /><property name="married" type="yes_no" column="married" /></class></hibernate-mapping>

3、get 、find 、load 方法对 一级缓存 和 二级缓存 的使用

  • 先查找 一级缓存 、二级缓存 中是否有 相应的 id 对应的对象,如果有就立即返回(不再查询数据库)
  • 如果在 一级缓存  、二级缓存 中 未找到 相应的 id 对应的对象 ( 未命中 ) ,查询数据库
  • 如果数据库中没有相应的 id 对应的记录,get 、find 返回 null , load 则抛出 ObjectNotFoundException
  • 如果在数据库中找到相应的 id 对应的记录,则返回与之对应的 Java 对象 ( load 可能会延迟 )
  • 当 Java 对象被返回后,会将该对象 添加到 一级缓存 和 二级缓存

4、 对 二级缓存进行管理的方法

  • cache.evict( Class entityClass ) 从二级缓存中驱逐指定类型的所有对象
  • cache.evict( Class entityClass , Object id ) 从二级缓存中驱逐指定类型的、指定id对应的对象
  • cache.evictAll()  清除二级缓存中的所有对象
  • cache.evictAllRegions() 清除二级缓存中的所有区域
  • cache.evictEntity( Class entityClass , Serializable id ) 从二级缓存中驱逐指定类型的、指定id对应的对象
  • cache.evictEntityRegions()  清除二级缓存中的所有区域
  • cache.evictEntityRegion( Student.class ) 清除 指定类型对应的区域
  • cache.evictEntityRegion( "ecut.cache.entity.Student" ) 清除 指定类型对应的区域 参照ehcache.xml文件
  • cache.evictCollectionRegion( "ecut.cache.entity.Clazz.students" ) 清除指定名称对应的集合缓存区域

5、测试案例

测试一:

package ecut.cache.test;import org.hibernate.Cache;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestSecondLevelCache {private SessionFactory factory ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();}//load 方法当从数据库中获取到数据后,会添加到 缓存 一级缓存 和 二级缓存 中public @Test void loadCustomer1(){Session session = factory.openSession(); // 第一次开启 sessionCustomer c = session.load( Customer.class ,  2 ); // 第一次在缓存中未命中 id = 1 的对象,因此需要查询数据库// 当从数据库中获取到数据后,会添加到 缓存中 ( 一级缓存 和 二级缓存 )System.out.println( c.getEmail() );session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );session = factory.openSession(); // 第二次开启 Session,开启了一个新的sessionCustomer x = session.load( Customer.class ,  2 ); // 当启用 二级缓存时,这里不会再查询数据库System.out.println( x.getEmail() );session.close(); // 第二次关闭 SessionSystem.out.println(  c == x );}
public @After void destory(){factory.close();}}

运行结果:

Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
wudang@qq.com
~~~~~~~~~~~~~~~~~~~~~~
wudang@qq.com
false

在第一次调用 load方法获取id为2的customer对象时,会从数据库中获取到数据后,并添加到 一级缓存和二级缓存中, 关闭第一个session后,因为一级缓存是依赖于session,session关闭则一级缓存失效 ,然后再次调用load方法获取id为2的customer对象,不会再查询数据库而是从二级缓存中获取对象。

测试二:

package ecut.cache.test;import org.hibernate.Cache;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestSecondLevelCache {private SessionFactory factory ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();}//一级和二级缓存都开启的情况下,session没有关闭,去找一级缓存,如果一级缓存中没有找到不会再查二级缓存,而是再次发起数据库查询public @Test void loadCustomer2(){Session session = factory.openSession(); // 开启回话Customer c = session.load( Customer.class ,  2 ); // 第一次获取对象System.out.println( c.getEmail() );session.evict( c ); // 从一级缓存中驱逐 指定的对象System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );Customer x = session.load( Customer.class ,  2 );// 第二次获取 ( 并没有使用 二级缓存 )System.out.println( x.getEmail() );System.out.println(  c == x );session.close(); // 关闭}
public @After void destory(){factory.close();}}

运行结果:

Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
wudang@qq.com
~~~~~~~~~~~~~~~~~~~~~~
Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
wudang@qq.com
false

在第一次调用 load方法获取id为2的customer对象时,会从数据库中获取到数据后,并添加到 一级缓存和二级缓存中, 然后调用evict方法将customer对象驱逐,则一级缓存中没有这个对象,在session没有关闭的情况下,再次通过load方法获取id为2的customer对象的时,会再次查询数据库,因此可以得出在session没有关闭的时候,如果一级缓存中没有找到不会再查二级缓存,而是再次发起数据库查询。

测试三:

package ecut.cache.test;import org.hibernate.Cache;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestSecondLevelCache {private SessionFactory factory ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();}//二级缓存的管理evict方法public @Test void load3(){Cache  cache = factory.getCache();Session session = factory.openSession(); // 第一次开启 sessionCustomer customer1 = session.find( Customer.class ,  2 );System.out.println( customer1 );Clazz clazz1 = session.find( Clazz.class ,  5 ); System.out.println( clazz1.getName() );//遍历students会调用通过班级号去查询数据库,与Hibernate.initialize( clazz1.getStudents() );等价for(Student s: clazz1.getStudents()){System.out.println(s.getId()+":"+s.getName());}session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )cache.evict( Customer.class ); // 从二级缓存中驱逐指定类型的所有对象cache.evict( Student.class,6 ); // 从二级缓存中驱逐指定类型的id为6的对象System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );session = factory.openSession(); // 第二次开启 SessionCustomer customer2 = session.find( Customer.class ,  2 );System.out.println( customer2 );//二级缓存中不存在客户的对象,因此会在掉用数据库查询Clazz clazz2 = session.find( Clazz.class ,  5 ); //二级缓存中存在班级的对象,因此不会在掉用数据库查询System.out.println(clazz2.getName() );for(Student s: clazz2.getStudents()){System.out.println(s.getId()+":"+s.getName());}session.close(); // 第二次关闭 Session}//二级缓存的管理evictAll方法public @Test void load4(){Cache  cache = factory.getCache();Session session = factory.openSession(); // 第一次开启 sessionCustomer customer1 = session.find( Customer.class ,  2 );System.out.println( customer1 );Clazz clazz1 = session.find( Clazz.class ,  5 ); System.out.println( clazz1.getName() );//遍历students会调用通过班级号去查询数据库,与Hibernate.initialize( clazz1.getStudents() );等价for(Student s: clazz1.getStudents()){System.out.println(s.getId()+":"+s.getName());}session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )cache.evictAll();System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );session = factory.openSession(); // 第二次开启 SessionCustomer customer2 = session.find( Customer.class ,  2 );System.out.println( customer2 );Clazz clazz2 = session.find( Clazz.class ,  5 ); System.out.println(clazz2.getName() );for(Student s: clazz2.getStudents()){System.out.println(s.getId()+":"+s.getName());}        session.close(); // 第二次关闭 Session}
public @After void destory(){factory.close();}}

load3方法运行结果:

Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@2032e725
Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
华山派
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
5:林平之
6:岳灵珊
7:令狐冲
~~~~~~~~~~~~~~~~~~~~~~
Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@782168b7
华山派
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
5:林平之
6:岳灵珊
7:令狐冲

在第一个session开启的时候所有的对象都查询了数据库,将customer对象和id为6的student对象驱逐后,在第二次查询中customer和student都调用了查询,只有clazz对象没有调用查询,但是student是根据班级再次查询了所有的,而我们只驱逐了一个student对象,是不合理的,遍历students会调用通过班级号去查询数据库,此时的student集合应该也会添加到二级缓存中,之所以在第二次查询时并没有 和我们所期望的去只获取一个student对象而是通过班级号去查询数据库获取所有的student对象是因为我们在clazz映射文件中没有在 set 、list 、map 等标签内部指定 集合的 缓存策略,student集合没有缓存。

在班级映射文件集合中添加缓存策略后load3运行结果:

Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@2032e725
Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
华山派
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
5:林平之
6:岳灵珊
7:令狐冲
~~~~~~~~~~~~~~~~~~~~~~
Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@23e44287
华山派
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
5:林平之
6:岳灵珊
7:令狐冲

此时在二次查询的时候已经是根据student的id去查询其中一个对象即被我们所驱逐的6号学生。而不是通过班级号回去所有对象,因为我们在班级映射文件中已指定了集合的缓存策略,学生对象会自动添加到二级缓存中。需要通过在在 set 、list 、map 等标签内部指定 集合的 缓存策略,对象中的集合才可以添加到二级缓存中,通过evictEntity( Class entityClass , Serializable id ) 从二级缓存中驱逐集合中指定类型的、指定id对应的对象,id依然会被缓存起来,再次查询时候回通过这个id去查询数据库。

load4运行结果:

Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@963176
Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
华山派
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
5:林平之
6:岳灵珊
7:令狐冲
~~~~~~~~~~~~~~~~~~~~~~
Hibernate: select customer0_.id as id1_1_0_, customer0_.email as email2_1_0_, customer0_.password as password3_1_0_, customer0_.nickname as nickname4_1_0_, customer0_.gender as gender5_1_0_, customer0_.birthdate as birthdat6_1_0_, customer0_.married as married7_1_0_ from t_customer customer0_ where customer0_.id=?
ecut.cache.entity.Customer@736f3e9e
Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
华山派
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
5:林平之
6:岳灵珊
7:令狐冲

调用evictAll方法驱逐所有对象后,随班级对象一起缓存的学生对象集合也一起被清除掉了,但是学生集合的id会被保留,再次获取学生对象时候会通过学生id去查询数据库。

测试四:

package ecut.cache.test;import org.hibernate.Cache;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestSecondLevelCache {private SessionFactory factory ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();}//使用initialize方法将student集合缓存起来public @Test void loadClazz5(){Session session = factory.openSession(); // 第一次开启 sessionClazz clazz1 = session.find( Clazz.class ,  6 ); System.out.println( clazz1.getName() );//强制初始化集合,执行查询并把数据封装到Student对象中Hibernate.initialize( clazz1.getStudents() );session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );session = factory.openSession(); // 第二次开启 SessionClazz clazz2 = session.find( Clazz.class ,  6 ); System.out.println( clazz2.getName() );Hibernate.initialize( clazz2.getStudents() );//不仅需要在班级的映射配置文件中指定集合的缓存策略还需要指定学生对象的缓存策略,不然只缓存了学生对象的id,获取名称的时候还会执行查询语句for(Student s: clazz2.getStudents()){System.out.println(s.getId()+":"+s.getName());}session.close(); // 第二次关闭 Session}public @After void destory(){factory.close();}}

运行结果:

Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
挖煤工程1班
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
~~~~~~~~~~~~~~~~~~~~~~
挖煤工程1班
10:丽丽

将学生映射文件中的缓存策略注释掉后的运行结果如下:

Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
挖煤工程1班
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
~~~~~~~~~~~~~~~~~~~~~~
挖煤工程1班
Hibernate: select student0_.id as id1_2_0_, student0_.name as name2_2_0_ from t_student student0_ where student0_.id=?
10:丽丽

initialize方法将强制初始化集合,执行查询并把数据封装到Student对象中,二级缓存中已经有Student,在第二次获取学生对象时候,会通过学生对象的id去查询数据库,由此可见若不在学生映射文件中指定缓存策略只会保留学生的id。

缓存班级对象并将其中的学生集合也一起添加到二级缓存中的步骤:

  • 在班级映射文件中的set 、list 、map 等标签内部 指定集合的缓存策略
  • 在学生映射文件中指定缓存策略(不然只会缓存id,即使调用了驱逐了所有对象,也会保留学生集合中的id)

测试五:

在上面几次测试中都会打印这个警告WARN: HHH020003: Could not find a specific ehcache configuration for cache named [ecut.cache.entity.Clazz.students]; using defaults.找不到一个特定的Ecache配置,用于缓存名为[ ECUT.Cache .Stase.CalZ.Sue];使用默认值。需要解决这个警告需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE ehcache ><ehcache><!-- 存储位置 --><diskStore path="java.io.tmpdir"/><!-- 默认的cache配置 --><defaultCache maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true" /><!-- 指定 缓存区域(Region)的名称 并配置  --><cache name="ecut.cache.entity.Student"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true" /><cache name="ecut.cache.entity.Customer"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true" /><cache name="ecut.cache.entity.Clazz"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true" />    <cache name="ecut.cache.entity.Clazz.students"maxElementsInMemory="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true" />   </ehcache>

这个文件的编写可以参照hibernate-release-5.2.10.Final\project\etc目录下的ehcache.xml来编写。

package ecut.cache.test;import org.hibernate.Cache;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import ecut.cache.entity.Clazz;
import ecut.cache.entity.Customer;
import ecut.cache.entity.Student;public class TestSecondLevelCache {private SessionFactory factory ;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml"); factory = config.buildSessionFactory();}//使用initialize方法将student集合缓存起来,在用evictCollectionRegion将students清理掉public @Test void loadClazz6(){Cache cache = factory.getCache();Session session = factory.openSession(); // 第一次开启 sessionClazz clazz1 = session.find( Clazz.class ,  6 ); System.out.println( clazz1 );Hibernate.initialize( clazz1.getStudents() );session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )//只能清理集合cache.evictCollectionRegion( "ecut.cache.entity.Clazz.students" );//cache.evictEntityRegion( "ecut.cache.entity.Student" );//清除 指定类型对应的区域 参照ehcache.xml文件System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );session = factory.openSession(); // 第二次开启 SessionClazz clazz2 = session.find( Clazz.class ,  6 ); System.out.println( clazz2 );Hibernate.initialize( clazz2.getStudents() );session.close(); // 第二次关闭 Session}public @After void destory(){factory.close();}}

运行结果:

Hibernate: select clazz0_.id as id1_0_0_, clazz0_.name as name2_0_0_ from t_class clazz0_ where clazz0_.id=?
挖煤工程1班
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
~~~~~~~~~~~~~~~~~~~~~~
挖煤工程1班
Hibernate: select students0_.class_id as class_id3_2_0_, students0_.id as id1_2_0_, students0_.id as id1_2_1_, students0_.name as name2_2_1_ from t_student students0_ where students0_.class_id=? order by students0_.id asc
10:丽丽

第二次获取学生对象时候是通过班级号去获取,evictEntityRegion和evictAll方法不同的是,evictAll方法会将学生对象保留而evictEentityRegion会将学生对象的id也清理掉,学生对象完全的被清除了。

cache.evictAll()  清除二级缓存中的所有对象,班级对象中的学生集合的id会被保留,再次获取学生对象时是通过id去查询数据库的

cache.evictEntityRegion( "ecut.cache.entity.Student" ) 清除 指定类型对应的区域, 参照ehcache.xml文件,学生对象被完全的清理掉,再次获取学生对象时是通过班级号去查询数据库的。

查询缓存

1、专门用来保存查询结果的缓存,需二级缓存的支持,查询缓存依赖二级缓存。

2、开启对查询缓存的支持

  • 在 hibernate.cfg.xml 中开启,启用查询缓存 和二级缓存的支持

    <!-- 启用二级缓存 ,EhCacheRegionFactory由hibernate提供-->
    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
    <!-- 启用 "查询缓存" -->
    <property name="hibernate.cache.use_query_cache" >true</property>

    查询缓存需要二级缓存的支持因此在开启查询缓存时候要先开启对二次缓存的支持。

  • 在程序中通过 查询器 设置是否使用 "查询缓存"
    Session session = factory.openSession();
    final String HQL = "FROM Customer" ;
    Query<Customer> queryer1 = session.createQuery( HQL , Customer.class );
    queryer1.setCacheable( true );

    如果没有在查询器中启用查询缓存,查询缓存依然是没有用的,就好比有个WiFi,你不连接也是无用的。

3、list 方法对 缓存的使用:

  • 执行 list 方法将导致执行查询操作,先查找 "查询缓存"
  • 如果在 "查询缓存" 命中了需要查找的数据,则直接返回 "查询缓存" 中的数据
  • 如果在 "查询缓存" 未命中,则查询数据库并返回数据,之后将查询到的数据 放入到 "查询缓存" 中

4、测试案例

测试一:

package ecut.cache.test;import java.util.Iterator;
import java.util.List;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ecut.cache.entity.Customer;public class TestQueryCache {private SessionFactory factory;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml");factory = config.buildSessionFactory();}// session 没有关闭,list()方法不会一级缓存中去查询,查询语句执行了两次public @Test void query1() {Session session1 = factory.openSession();final String HQL = "FROM Customer";Query<Customer> queryer1 = session1.createQuery(HQL, Customer.class);List<Customer> customers1 = queryer1.list();// 第一次执行查询for (Customer c : customers1) {System.out.println(c.getId() + ":" + c.getNickname());}System.out.println("~~~~~~~~~~~~~~~~~~");Query<Customer> queryer2 = session1.createQuery(HQL, Customer.class);List<Customer> customers2 = queryer2.list();// 第二次执行查询for (Customer c : customers2) {System.out.println(c.getId() + ":" + c.getNickname());}}// session关闭,list()方法不会二级缓存中去查询,查询语句执行了两次public @Test void query2() {Session session1 = factory.openSession();final String HQL = "FROM Customer";Query<Customer> queryer1 = session1.createQuery(HQL, Customer.class);List<Customer> customers1 = queryer1.list();// 第一次执行查询for (Customer c : customers1) {System.out.println(c.getId() + ":" + c.getNickname());}System.out.println("~~~~~~~~~~~~~~~~~~");session1.close();Session session2 = factory.openSession();Query<Customer> queryer2 = session2.createQuery(HQL, Customer.class);List<Customer> customers2 = queryer2.list();// 第二次执行查询for (Customer c : customers2) {System.out.println(c.getId() + ":" + c.getNickname());}}// session不 关闭,iterate()方法会去一级缓存中去查询,查询id的语句执行了两次,查询根据id获取客户对象的执行了一次@SuppressWarnings("deprecation")public @Test void query3() {Session session1 = factory.openSession();final String HQL = "FROM Customer";Query<Customer> queryer1 = session1.createQuery(HQL, Customer.class);Iterator<Customer> it1 = queryer1.iterate(); // 执行查询操作 ( 但是 只获取 每个对象对应的// 对象标识符 )// 每循环一次 就查询一条记录 ( N )while (it1.hasNext()) {Customer c = it1.next();System.out.println(c.getId() + " : " + c.getNickname());//session1.evict(c);//从一级缓存中驱逐掉,去找一级缓存,如果一级缓存中没有找到不会再查二级缓存,而是再次发起数据库查询}System.out.println("~~~~~~~~~~~~~~~~~~");Query<Customer> queryer2 = session1.createQuery(HQL, Customer.class);Iterator<Customer> it2 = queryer2.iterate(); // 执行查询操作 ( 但是 只获取 每个对象对应的// 对象标识符 )// 每循环一次 就查询一条记录 ( N )while (it2.hasNext()) {Customer c = it2.next();System.out.println(c.getId() + " : " + c.getNickname());}}// session 关闭,iterate()方法会去二级缓存中去查询,查询id的语句执行了两次,查询根据id获取客户对象的执行了一次@SuppressWarnings("deprecation")public @Test void query4() {Session session1 = factory.openSession();final String HQL = "FROM Customer";Query<Customer> queryer1 = session1.createQuery(HQL, Customer.class);Iterator<Customer> it1 = queryer1.iterate(); // 执行查询操作 ( 但是 只获取 每个对象对应的对象标识符 )// 每循环一次 就查询一条记录 ( N )while (it1.hasNext()) {Customer c = it1.next();System.out.println(c.getId() + " : " + c.getNickname());}System.out.println("~~~~~~~~~~~~~~~~~~");session1.close();Session session2 = factory.openSession();Query<Customer> queryer2 = session2.createQuery(HQL, Customer.class);Iterator<Customer> it2 = queryer2.iterate(); // 执行查询操作 ( 但是 只获取 每个对象对应的对象标识符 )// 每循环一次 就查询一条记录 ( N )while (it2.hasNext()) {Customer c = it2.next();System.out.println(c.getId() + " : " + c.getNickname());}}public @After void destory(){factory.close();}}

无论session是否关闭,list()方法查询语句都执行了两次,即List方法不会去一级缓存和二级缓存中查询。  session不关闭,iterate()方法会去一级缓存中去查询,查询id的语句执行了两次,查询根据id获取客户对象的执行了一次;session 关闭,iterate()方法会去二级缓存中去查询,查询id的语句执行了两次,查询根据id获取客户对象的执行了一次。但是如果session不关闭,并通过session1.evict(c)方法将对象从一级缓存中移除,一级缓存中没有找到对象不会再查询二级缓存。
测试二:

package ecut.cache.test;import java.util.Iterator;
import java.util.List;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ecut.cache.entity.Customer;public class TestQueryCache {private SessionFactory factory;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml");factory = config.buildSessionFactory();}
public @Test void query5(){Session session = factory.openSession();final String HQL = "FROM Customer" ;Query<Customer> queryer = session.createQuery( HQL , Customer.class );queryer.setCacheable( true );queryer.list();List<Customer> customers1 = queryer.list();// 第一次执行查询for (Customer c : customers1) {System.out.println(c.getId() + ":" + c.getNickname());}System.out.println( "~~~~~~~~~~~~~~~~~~" );List<Customer> customers2 = queryer.list();// 第二次执行查询for (Customer c : customers2) {System.out.println(c.getId() + ":" + c.getNickname());}}public @After void destory(){factory.close();}}

运行结果:

Hibernate: select customer0_.id as id1_1_, customer0_.email as email2_1_, customer0_.password as password3_1_, customer0_.nickname as nickname4_1_, customer0_.gender as gender5_1_, customer0_.birthdate as birthdat6_1_, customer0_.married as married7_1_ from t_customer customer0_
1:武当
2:君宝
3:张三丰
4:素素
~~~~~~~~~~~~~~~~~~
1:武当
2:君宝
3:张三丰
4:素素

一级缓存和二级缓存不会缓存List方法,可以使用查询缓存。

测试三:

package ecut.cache.test;import java.util.Iterator;
import java.util.List;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ecut.cache.entity.Customer;public class TestQueryCache {private SessionFactory factory;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml");factory = config.buildSessionFactory();}
public @Test void query6(){Session session = factory.openSession();final String HQL = "FROM Customer" ;Query<Customer> queryer1 = session.createQuery( HQL , Customer.class );queryer1.setCacheable( true );List<Customer> customers1 = queryer1.list();// 第一次执行查询for (Customer c : customers1) {System.out.println(c.getId() + ":" + c.getNickname());}  System.out.println( "~~~~~~~~~~~~~~~~~~" );Query<Customer> queryer2 = session.createQuery( HQL , Customer.class );queryer2.setCacheable( true );List<Customer> customers2 = queryer2.list();// 第二次执行查询for (Customer c : customers2) {System.out.println(c.getId() + ":" + c.getNickname());} session.close();}public @After void destory(){factory.close();}}

运行结果:

Hibernate: select customer0_.id as id1_1_, customer0_.email as email2_1_, customer0_.password as password3_1_, customer0_.nickname as nickname4_1_, customer0_.gender as gender5_1_, customer0_.birthdate as birthdat6_1_, customer0_.married as married7_1_ from t_customer customer0_
1:武当
2:君宝
3:张三丰
4:素素
~~~~~~~~~~~~~~~~~~
1:武当
2:君宝
3:张三丰
4:素素

即使是两个查询器也是会先在查询缓存中获取,如果存在就直接输出,不存在再查询数据库。

测试四:

package ecut.cache.test;import java.util.Iterator;
import java.util.List;import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ecut.cache.entity.Customer;public class TestQueryCache {private SessionFactory factory;public @Before void init() {Configuration config = new Configuration();config.configure("ecut/cache/hibernate.cfg.xml");factory = config.buildSessionFactory();}
public @Test void query7(){Session session = factory.openSession();final String HQL = "FROM Customer" ;Query<Customer> queryer1 = session.createQuery( HQL , Customer.class );queryer1.setCacheable( true );List<Customer> customers1 = queryer1.list();// 第一次执行查询for (Customer c : customers1) {System.out.println(c.getId() + ":" + c.getNickname());}          session.close();System.out.println( "~~~~~~~~~~~~~~~~~~" );session = factory.openSession();Query<Customer> queryer2 = session.createQuery( HQL , Customer.class );queryer2.setCacheable( true );List<Customer> customers2 = queryer2.list();// 第二次执行查询for (Customer c : customers2) {System.out.println(c.getId() + ":" + c.getNickname());}         session.close();}public @After void destory(){factory.close();}}

运行结果:

Hibernate: select customer0_.id as id1_1_, customer0_.email as email2_1_, customer0_.password as password3_1_, customer0_.nickname as nickname4_1_, customer0_.gender as gender5_1_, customer0_.birthdate as birthdat6_1_, customer0_.married as married7_1_ from t_customer customer0_
1:武当
2:君宝
3:张三丰
4:素素
~~~~~~~~~~~~~~~~~~
1:武当
2:君宝
3:张三丰
4:素素

session关闭,重新开启一个session并没有使查询缓存中的对象丢失,因为查询缓存是sessionFactory级别的。

转载请于明显处标明出处:

https://www.cnblogs.com/AmyZheng/p/9332358.html

Hibernate学习(七)相关推荐

  1. Hibernate学习总结【比较与Mybatis框架之间的区别】

    经过一周的Hibernate学习,我理解了作为以JPA为核心的持久层查询标准.JPA所涉及的思想为ORM(object relational mapping),它解释了为什么我们的实体关系要和数据库一 ...

  2. STL源码剖析学习七:stack和queue

    STL源码剖析学习七:stack和queue stack是一种先进后出的数据结构,只有一个出口. 允许新增.删除.获取最顶端的元素,没有任何办法可以存取其他元素,不允许有遍历行为. 缺省情况下用deq ...

  3. hibernate学习笔记二

    上一篇关于hibernate学习笔记一,主要是作为hibernate的入门知识.没有和spring发生任何关系,这一篇我将把spring集成进去,看spring如何管理hibernate,还有和未使用 ...

  4. hibernate学习和各种关系总结

    2019独角兽企业重金招聘Python工程师标准>>> 原文地址 http://yifanxiang.blog.163.com/blog/static/500008342010527 ...

  5. Hibernate学习之Hibernate注解总结

    Hibernate学习之Hibernate注解总结http://www.bieryun.com/3269.html 一.类级别的注解 @Entity name:表的名字(可选)一般表名和类名相同 必须 ...

  6. OpenCV与图像处理学习七——传统图像分割之阈值法(固定阈值、自适应阈值、大津阈值)

    OpenCV与图像处理学习七--传统图像分割之阈值法(固定阈值.自适应阈值.大津阈值) 一.固定阈值图像分割 1.1 直方图双峰法 1.2 OpenCV中的固定阈值分割 二.自动阈值图像分割 2.1 ...

  7. PyTorch框架学习七——自定义transforms方法

    PyTorch框架学习七--自定义transforms方法 一.自定义transforms注意要素 二.自定义transforms步骤 三.自定义transforms实例:椒盐噪声 虽然前面的笔记介绍 ...

  8. Docker学习七:使用docker搭建Hadoop集群

    本博客简单分享了如何在Docker上搭建Hadoop集群,我的电脑是Ubuntu20,听同学说wsl2有些命令不对,所以建议在虚拟机里按照Ubuntu或者直接安装双系统吧 Docker学习一:Dock ...

  9. (转)MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码

    http://blog.csdn.net/yerenyuan_pku/article/details/71909325 什么是逆向工程 MyBatis的一个主要的特点就是需要程序员自己编写sql,那么 ...

  10. RTKLIB专题学习(七)---精密单点定位实现初识(三)

    RTKLIB专题学习(七)-精密单点定位实现初识(三) 上两篇我们介绍了RTKLIB中精密单点定位的大致流程,今天我们对照RTKLIB学习手册,来学习相应改正公式和误差源 1.在PPP模式中 RTKL ...

最新文章

  1. java多线程-sleep()和wait()对比
  2. 来吧学学.Net Core之项目文件简介及配置文件与IOC的使用
  3. Oracle入门(十四.7)之良好的编程习惯
  4. JavaScript学习随记——Function
  5. Github 下载项目的某一分支版本
  6. 穷人的孩子真的早当家吗?
  7. fio 磁盘IO性能测试
  8. JS实现查找数组中对象的属性值是否存在
  9. SuperMap iClient3D for WebGL 示范案例(一)倾斜模型加载
  10. 【数据结构】树状数组效果讲解
  11. 图纸管理软件保证图纸最新版本正确方法
  12. UML简单介绍(五)——用例图的使用案例与分析
  13. HJ82 将真分数分解为埃及分数 —— 华为机考练习题
  14. 【Python数据分析与处理 实训01】 ---- 菜品订单信息分析(数据了解及简单统计)
  15. python tkinter怎么读_Python初学 Tkinter
  16. 20V,30V,40V输入的LDO稳压芯片
  17. Go语言圣经 - 第11章 测试 - 11.1 go test 11.2 测试函数
  18. 用户行为分析的指标及其意义
  19. php preg_match 漏洞,PHP preg_match()函数信息泄露漏洞
  20. 金蝶采购模块采购赠品业务处理逻辑

热门文章

  1. led灯条维修_LED灯坏了别着急,三种故障的解决方案在这里
  2. ijk的那些事(一)编译项目
  3. Failed with exception java.io.IOException....FileFormatException: Malforme... Invalid postscript.
  4. 浙大计算机学院多厉害,一张图,就能告诉你浙大到底有多牛!
  5. tkinter简明教程
  6. Voice meter 下载与安装
  7. 2021年中国程序员薪资和生活现状调查报告
  8. 手动扎捆机的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  9. turfjs前端地理空间分析类库
  10. 犹太人:赚钱能培养孩子独立责任奋进多种能力!