• Hibernate ——二级缓存


    一、Hibernate 二级缓存

    1.Hibernate 二级缓存是 SessionFactory 级别的缓存。

    2.二级缓存分为两类:

    (1)Hibernate内置二级缓存

    (2)外置缓存,可配置的,可插拨的,外置缓存中的数据是数据库数据的复制。

    3.二级缓存的并发访问策略

    (1)两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现并发问题。

    (2)二级缓存可以设定以下 4 中并发访问策略,每一种对应一种事务隔离级别。

    • 非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性。对应 Read UnCommited 事务隔离级别。
    • 读写型(Read-write):提供 Read Commited 级别的事务隔离级别。
    • 事务型(Transactional):对应 Repeatable Read 级别的事务隔离级别。
    • 只读型(Read-Only):提供 Serializable 级别的数据隔离级别。

    4.Hibernate 二级缓存是进程或集群范围内的缓存。是可配置的的插件。这里以 Ehcache 为例。不支持事务型的并发访问策略。

    5.配置 Ehcache

    (1)添加 Jar 包

    (2)在 Hibernate 配置文件中启用二级缓存并指定适配的缓存适配器。

    在 <session-factory> 元素内添加:

    <property name="cache.use_second_level_cache">true</property>
    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

    (3)在 Hibernate 配置文件中配置需要使用二级缓存的持久化类,并设置它的二级缓存的并发访问策略。如:

    <class-cache class="com.solverpeng.hql.Department" usage="read-write"/>

    需要注意的是:<session-factory> 元素内子元素的顺序:property*, mapping*, (class-cache|collection-cache)*, event*, listener*,class-cache 元素必须位于 mapping 节点后。

    (4)或者也可以在 hbm 文件中配置,如:

    <hibernate-mapping>
    
        <class name="com.solverpeng.hql.Employee" table="employee" schema="hibernate">
            <cache usage="read-write"/>
            <id name="empId" column="emp_id"/>
            <property name="empName" column="emp_name"/>
            <property name="salary" column="salary"/>
            <many-to-one name="dept" class="com.solverpeng.hql.Department">
                <column name="dept_id_fk" not-null="true"/>
            </many-to-one>
        </class>
    </hibernate-mapping>

    6. 测试 Hibernate 二级缓存

    (1)查询单个对象缓存

    @Test
    public void testSecondCache() {
        Employee o = (Employee) session.get(Employee.class, 6);
        System.out.println(o);
    
        transaction.commit();
        session.close();
        session = factory.openSession();
        transaction = session.beginTransaction();
    
        Employee o1 = (Employee) session.get(Employee.class, 6);
        System.out.println(o1);
    }

    查询同一个对象两次,中间关闭 Session 再开启。

    没有配置二级缓存:

    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    com.solverpeng.hql.Employee@40dd58f3
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    com.solverpeng.hql.Employee@40dd58f3

    结果:

    发送了2条 SQL 语句。

    配置二级缓存后(可以在Hibernate 配置文件中配置,也可以在 Employee hbm 配置文件中配置):

    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    com.solverpeng.hql.Employee@40dd58f3
    com.solverpeng.hql.Employee@40dd58f3

    结果:

    只发送了1条 SQL 语句。

    (2)集合缓存

    @Test
    public void testCollectionSecondLevelCache() {
        Department department = (Department) session.get(Department.class, 6);
        System.out.println(department.getDeptName());
        System.out.println(department.getEmps().size());
    
        transaction.commit();
        session.close();
        session = factory.openSession();
        transaction = session.beginTransaction();
    
        Department department2 = (Department) session.get(Department.class, 6);
        System.out.println(department2.getDeptName());
        System.out.println(department2.getEmps().size());
    
    }

    在没有配置二级缓存的情况下:

    Hibernate: 
        select
            department0_.dept_id as dept1_0_0_,
            department0_.dept_name as dept2_0_0_ 
        from
            hibernate.department department0_ 
        where
            department0_.dept_id=?
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3
    Hibernate: 
        select
            department0_.dept_id as dept1_0_0_,
            department0_.dept_name as dept2_0_0_ 
        from
            hibernate.department department0_ 
        where
            department0_.dept_id=?
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3

    结果:

    查询了两次 Department,两次Employee

    只配置 Department 二级缓存:

    <class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
    Hibernate: 
        select
            department0_.dept_id as dept1_0_0_,
            department0_.dept_name as dept2_0_0_ 
        from
            hibernate.department department0_ 
        where
            department0_.dept_id=?
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3

    结果:

    只查询了一次 Department,2次Employee。

    说明:

    开启 Department 二级缓存后,会对 Department 进行缓存,而与其关联的 emps 不会进行缓存。

    配置 Department 二级缓存,同时配置关联的 emps 缓存。

    <class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
    <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/>
    Hibernate: 
        select
            department0_.dept_id as dept1_0_0_,
            department0_.dept_name as dept2_0_0_ 
        from
            hibernate.department department0_ 
        where
            department0_.dept_id=?
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3
    -----------------------------
    dept-aa
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_0_,
            employee0_.emp_name as emp2_1_0_,
            employee0_.salary as salary3_1_0_,
            employee0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee employee0_ 
        where
            employee0_.emp_id=?
    3

    结果:

    发送了更多的查询 Employee 的SQL。

    说明:

    开启集合的二级缓存后,此时会缓存集合中对象的 id ,而不会对集合中的对象进行缓存。若想缓存,需要关联的集合中的对象也开启二级缓存。如:

    <class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
    <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/>
    <class-cache class="com.solverpeng.hql.Employee" usage="read-write"/>
    Hibernate: 
        select
            department0_.dept_id as dept1_0_0_,
            department0_.dept_name as dept2_0_0_ 
        from
            hibernate.department department0_ 
        where
            department0_.dept_id=?
    dept-aa
    Hibernate: 
        select
            emps0_.dept_id_fk as dept4_0_1_,
            emps0_.emp_id as emp1_1_1_,
            emps0_.emp_id as emp1_1_0_,
            emps0_.emp_name as emp2_1_0_,
            emps0_.salary as salary3_1_0_,
            emps0_.dept_id_fk as dept4_1_0_ 
        from
            hibernate.employee emps0_ 
        where
            emps0_.dept_id_fk=?
    3
    -----------------------------
    dept-aa
    3

    结果:

    除 Department 外,关联的 Employee 也被缓存了。

    (3)查询缓存(针对 HQL、QBC)

    在二级缓存开启的情况下,HQL、QBC 也不能对查询进行缓存。

    @Test
    public void testQueryCache() {
        Query query = session.createQuery("from Employee ");
    
        List<Employee> list = query.list();
        System.out.println(list.size());
    
        List<Employee> emps2 = query.list();
        System.out.println(emps2.size());
    }
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_,
            employee0_.emp_name as emp2_1_,
            employee0_.salary as salary3_1_,
            employee0_.dept_id_fk as dept4_1_ 
        from
            hibernate.employee employee0_
    12
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_,
            employee0_.emp_name as emp2_1_,
            employee0_.salary as salary3_1_,
            employee0_.dept_id_fk as dept4_1_ 
        from
            hibernate.employee employee0_
    12

    开启查询缓存:

    • 在 Hibernate 配置文件中开启查询缓存
    • 若想启用查询缓存的查询语句,需要调用 Query 或 Criteria 的 setCacheable() 方法。
    • 查询缓存依赖于二级缓存
    <property name="hibernate.cache.use_query_cache">true</property>
    @Test
    public void testQueryCache() {
        Query query = session.createQuery("from Employee ");
        query.setCacheable(true);
        List<Employee> list = query.list();
        System.out.println(list.size());
    
        List<Employee> emps2 = query.list();
        System.out.println(emps2.size());
    }
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_,
            employee0_.emp_name as emp2_1_,
            employee0_.salary as salary3_1_,
            employee0_.dept_id_fk as dept4_1_ 
        from
            hibernate.employee employee0_
    12
    12

    (4)时间戳缓存区域:时间戳缓存区存放了对于查询结果相关的表进行插入、更新或删除操作的时间戳。Hibernate通过时间戳缓存区来判定被缓存的查询结果是否过期。

    @Test
    public void testTimStampCache() {
        Query query = session.createQuery("from Employee ");
        query.setCacheable(true);
        List<Employee> list = query.list();
        System.out.println(list.size());
    
        Employee employee = (Employee) session.get(Employee.class, 6);
        employee.setEmpName("emp@@");
    
        List<Employee> emps2 = query.list();
        System.out.println(emps2.size());
    }
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_,
            employee0_.emp_name as emp2_1_,
            employee0_.salary as salary3_1_,
            employee0_.dept_id_fk as dept4_1_ 
        from
            hibernate.employee employee0_
    12
    Hibernate: 
        update
            hibernate.employee 
        set
            emp_name=?,
            salary=?,
            dept_id_fk=? 
        where
            emp_id=?
    Hibernate: 
        select
            employee0_.emp_id as emp1_1_,
            employee0_.emp_name as emp2_1_,
            employee0_.salary as salary3_1_,
            employee0_.dept_id_fk as dept4_1_ 
        from
            hibernate.employee employee0_
    12

    二、ehcache.xml

    <ehcache>    
        <diskStore path="java.io.tmpdir"/>
    
        <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            />
    
        <cache name="sampleCache1"
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            overflowToDisk="true"
            />
    
        <cache name="sampleCache2"
            maxElementsInMemory="1000"
            eternal="true"
            timeToIdleSeconds="0"
            timeToLiveSeconds="0"
            overflowToDisk="false"
            />
    
    </ehcache>

    1.<diskStore>: 指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.

    2.<defaultCache>: 设置缓存的默认数据过期策略

    3.<cache> 设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域

    4.缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>

    5.Hibernate在不同的缓存区域保存不同的类/集合。

    • 对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
    • 对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders

    6.cache 元素的属性

    (1)name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字

    (2)maxInMemory:设置基于内存的缓存中可存放的对象最大数目

    (3)eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false。

    (4)timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。

    (5)timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值。

    (6)overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中。

    三、管理 Session

    Session 对象的生命周期与本地线程绑定

    –Session 对象的生命周期与 JTA 事务绑定

    –Hibernate 委托程序管理 Session 对象的生命周期

    四、批量处理数据

    建议通过 JDBC 的方式来进行批量操作

    @Test
    public void testBathch() {
        session.doWork(new Work() {
            @Override
            public void execute(Connection connection) throws SQLException {
                // 执行批量操作
            }
        });
    }

    五、总结

    配置 Hibernate 二级缓存的步骤:

    1.配置 Hibernate 配置文件

    (1)配置启用二级缓存

    <property name="cache.use_second_level_cache">true</property>

    (2)配置二级缓存使用的产品

    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

    (3)配置对哪些类使用 hibernate 的二级缓存以及并发策略

    <class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
    <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/>
    <class-cache class="com.solverpeng.hql.Employee" usage="read-write"/>

    (4)在 hbm 文件中配置缓存

    <set name="emps" inverse="true">
        <cache usage="read-write"/>
        <key>
            <column name="dept_id_fk" not-null="true"/>
        </key>
        <one-to-many not-found="ignore" class="com.solverpeng.hql.Employee"/>
    </set>

    2. 对于集合缓存来说,还需要配置集合中的元素对应的持久化类也使用二级缓存! 否则将会多出 n 条 SQL 语句.

    3. 查询缓存

    (1)在 Hibernate 配置文件中开启查询缓存支持:<property name="cache.use_query_cache">true</property>

    (2)调用 Query 或 Criteria 的 setCacheable(true)方法

    (3)查询缓存依赖于二级缓存

  • 相关阅读:
    php7与其他版本共存
    centos源码安装mysql5.7
    禁用composer update命令
    lumen怎么得到当前Uri的控制器、Action、路由规则
    配置lumen的log为daily模式
    laravel如何打印orm封装的sql语句
    nginx 重写URL尾部斜杠
    Laravel的Nginx重写规则--让路由支持末尾加斜线
    laravel redis存数组并设置过期时间
    openresty
  • 原文地址:https://www.cnblogs.com/solverpeng/p/5961935.html
Copyright © 2020-2023  润新知