当前位置:网站首页>Hibernate 的 Session 缓存相关操作

Hibernate 的 Session 缓存相关操作

2022-08-11 07:05:00 血莲丹

简介

Session 是 Hibernate 中最重要的接口,它提供了基本的保存、更新、删除和加载方法,是加载而不是查询,将对象加载到 session 缓存中。而查询是通过 Query 和 Criteria 两个对象来做的,但是也是通过 session 对象创建的,因此 session 很重要,是 Hibernate 的核心。

Session 缓存

先来看下 session 的缓存。再 Mybatis 中有一级二级缓存,同样的,Hibernate 也有。
先来加载两次相同的记录。

    @Test
    public void test2() {
    
        SessionFactory sessionFactory;
        Configuration configuration = new Configuration().configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties())
                .buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        News news1 = (News) session.get(News.class, 1);
        System.out.println(news1);
        News news2 = (News) session.get(News.class, 1);
        System.out.println(news2);
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

查看日志:
在这里插入图片描述
这就是基于 session 的一级缓存。除了我们创建的 news 变量引用这内存中的对象,session 缓存中也持有一份对象。当再次查询该对象时,会先去 session 缓存中查询有没有,有的话让新引用指向缓存中的对象。二级对象后面再说。

操作 session 缓存

session 操作缓存主要有如下的方法。他们之间的关系如下图所示。
在这里插入图片描述

flush() 方法

在讲 flush 方法之前,先对测试类进行一下改造,不然每次都要打那么多固定的代码,利用 JUnit 的生命周期方法能有效的减少冗余。

package com.zxb.hibernate;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class HibernateTest2 {
    

    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() {
    
        Configuration configuration = new Configuration().configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties())
                .buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }


    @Test
    public void test() {
    
        
    }


    @After
    public void destroy() {
    
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

}

注:以后所有代码都基于上述代码结构。

现在来看如下的代码:

    @Test
    public void test() {
    
        News news = (News) session.get(News.class, 1);
        news.setTitle("Java");
    }

执行上述代码前,先看下数据库中的记录。
在这里插入图片描述
此时再来执行上述代码。
在这里插入图片描述
可以看到,除了发送 SELECT 语句之外,还发送了 UPDATE 语句。数据库中的记录也发生了更改,这是为什么?
在这里插入图片描述
因为在 session 的 commit() 方法中,在调用事务的 commit() 方法前,先调用了 flush() 方法,然后再去 commit 事务的。
在这里插入图片描述
这个 flush() 方法的作用就是使表中的记录和 session 缓存中的记录保持一致,如果不一致,就会发送 SQL 去修改表中的记录。但是 单一的调用 flush() 方法是不会修改表中记录的,因为没有提交事务

flush() 方法是让数据库表中的记录和 session 缓存中的对象保持一致,缓存中的对象是爷。

也可以打断点看看,当执行到 session.commit() 后才会发送 SQL。

但是除了显示的调用 session.flush() 方法和提交事务之外,还有如下场景也会在方法内部调用 flush() 方法。
1、 执行 HQL 或者 QBC 查询时,要求查询的对象必须是最新的状态,因此会先 flush() 一下,让数据库表中记录保持最新。

    @Test
    public void test3() {
    
        News news = (News) session.get(News.class, 1);
        // 在 QBC 查询前将缓存中记录修改
        news.setAuthor("oracle");
        Criteria criteria = session.createCriteria(News.class);
        News news2 = (News) criteria.add(Restrictions.eq("id", 1)).uniqueResult();
        System.out.println(news2);
    }

可以在输出这行打个断点,此时查看控制台,发现已经调用 flush() 方法,发送了 UPDATE 语句,而不是在 commit 时调用。
2、 如果数据库表中的记录使用 native 生成器生成 OID(Object ID),那么当调用 session 的 save() 方法时内部也会调用 flush() 方法,立即发送 INSERT 语句向数据库插入该实体,因为只有这样 Hibernate 才能拿到记录的 ID,后面才能对该记录进行操作。所以说对象 ID 很重要,实体类中一定要有 ID 字段

    @Test
    public void test4() {
    
        News news = new News("C++", "ZXB", new Date());
        session.save(news);
        System.out.println("save...");
    }

可以在输出语句这里打个断点,查看控制台会发现发送了一条插入语句,让数据库中和缓存中保持一致。这里也是调用 save() 方法时就调用了 flush() 方法,并非在 commit 才调用。
小结:

  • flush() :根据缓存中的对象属性变化来同步更新数据库。
  • 默认情况下,Session 在以下时间点 flush 缓存
    – 显示调用 flush() 方法。
    – 事务提交时,先调用 flush() 方法,再提交事务。
    – 执行 HQL、QBC 查询时,如果缓存中的对象发生了变化,会先 flush(),保证查询的结果和缓存中的对象保持一致。
    – 当对象使用 native 策略生成主键时,调用 session.save() 方法时,会立即 flush(),发送一条 INSERT 语句,使得数据库表中和缓存一致。
  • commit() 和 flush() 方法的区别:flush() 方法会执行一系列 sql,但是不会提交事务。commit() 方法内部会先调用 flush() 方法,随后再提交事务。

refresh() 方法

根据 session 缓存的图示来看,refresh() 方法箭头是由数据库指向 session 缓存,所以也能猜到,refresh() 方法就是让缓存中的对象和数据库中的记录保持一致。

refresh() 方法是 session 缓存中的对象和数据库表中的记录保持一致,数据库表中的记录是爷。

举个例子:

    @Test
    public void test5() {
    
        News news = (News) session.get(News.class,1);
        System.out.println(news); // 断点
        session.refresh(news);
        System.out.println(news);
    }

上述代码在第一次输出 news 时打个断点,当程序执行到断点处,我们手动修改数据库表中的记录,然后执行 refresh,看看第二次输出的 news 和第一次是否一致。
在这里插入图片描述
通过控制台的输出发现,两次打印结果是一致的,但是的确是发送了两次 SQL,这是 refresh 起效果了,不然有缓存,只会发送一次。这其实和 MySQL 的隔离级别有关,默认是可重复读,如果想要看到效果,可以在 hibernate.cfg.xml 中修改隔离级别。

        <!-- 修改 Hibernate 的事务隔离级别,1,2,4,8 分别从低到高对应四种隔离界别 -->
        <property name="hibernate.connection.isolation">2</property>

修改过后,再去执行,就能看到效果了。
在这里插入图片描述

clear() 方法

clear 方法就是清除 session 中的缓存,这个没啥好说的。

    @Test
    public void test6() {
    
        News news1 = (News) session.get(News.class, 1);
        session.clear();
        News news2 = (News) session.get(News.class, 1);
    }

此时发送两条查询语句,因为缓存已被清空。
在这里插入图片描述

原网站

版权声明
本文为[血莲丹]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_44061521/article/details/126235048