Hibernate学习(七)

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( "	" + 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( "	" + 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.Finalliboptionalehcache)

    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(); // 第一次开启 session
        Customer c = session.load( Customer.class ,  2 ); // 第一次在缓存中未命中 id = 1 的对象,因此需要查询数据库
        // 当从数据库中获取到数据后,会添加到 缓存中 ( 一级缓存 和 二级缓存 )
        System.out.println( c.getEmail() );
        session.close();  // 第一次 关闭 session ( 关闭后,一级缓存失效 )
        
        System.out.println( "~~~~~~~~~~~~~~~~~~~~~~" );
        
        session = factory.openSession(); // 第二次开启 Session,开启了一个新的session
        Customer x = session.load( Customer.class ,  2 ); // 当启用 二级缓存时,这里不会再查询数据库
        System.out.println( x.getEmail() );
        session.close(); // 第二次关闭 Session
        
        System.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(); // 第一次开启 session
        Customer 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(); // 第二次开启 Session
        Customer 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(); // 第一次开启 session
        Customer 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(); // 第二次开启 Session
        Customer 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(); // 第一次开启 session
        Clazz 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(); // 第二次开启 Session
        Clazz 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.Finalprojectetc目录下的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(); // 第一次开启 session
        Clazz 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(); // 第二次开启 Session
        Clazz 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

原文地址:https://www.cnblogs.com/AmyZheng/p/9332358.html