Mybatis:缓存和装饰器模式

简介

mybatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大地提升查询效率。

Mybatis系统中默认定义了两级缓存。一级缓存和二级缓存。

  • 默认情况下,只有一级缓存(SqlSession级别地缓存,也成为本地缓存)开启
  • 二级缓存需要手动开启和配置,它是基于namespace级别地缓存
  • 为了提高扩展性,mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存。

一级缓存

又称本地缓存,与数据库同一次会话期间查询道德数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

测试:

    @Test
    public void test01() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);
            Employee emp2 = employeeMapper.getEmpById(1);
            System.out.println(emp2);
            System.out.println(emp==emp2);
        } finally {
            session.close();
        }
    }

    private SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }

image-20210113205849101

发现只查询了一次数据库。并且第二次查询和第一次查询得到地对象是同一个。

一级缓存失效情况

sqlSession不同

    @Test
    public void test01() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        SqlSession session2 = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);
            EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);
            Employee emp2 = employeeMapper2.getEmpById(1);
            System.out.println(emp2);
            System.out.println(emp==emp2);
        } finally {
            session.close();
        }
    }

image-20210113210201507

查询条件不同

原因:一级缓存中不存在该缓存

    @Test
    public void test01() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);
            Employee emp2 = employeeMapper.getEmpById(2);
            System.out.println(emp2);
            System.out.println(emp==emp2);
        } finally {
            session.close();
        }
    }

image-20210113210439772

增删改

sqlSession相同,但是两次查询之间执行了增删改操作(尽管没有影响到原来缓存的数据)

    @Test
    public void test01() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);

            employeeMapper.delEmpById(5);

            Employee emp2 = employeeMapper.getEmpById(2);
            System.out.println(emp2);
            System.out.println(emp==emp2);
        } finally {
            session.close();
        }
    }

image-20210113210750107

手动清空缓存

    @Test
    public void test01() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper= session.getMapper(EmployeeMapper.class);
            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);

            session.clearCache();

            Employee emp2 = employeeMapper.getEmpById(2);
            System.out.println(emp2);
            System.out.println(emp==emp2);
        } finally {
            session.close();
        }
    }

image-20210113210950259

二级缓存

基于namespace级别的缓存,一个namespace对应一个二级缓存。

工作机制:

  • 一个会话,查询一条数据,这个数据就会被放到当前会话的一级缓存中

  • 只有会话关闭,一级缓存中的数据才会被转移到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容

  • sqlSession=>EmployeeMapper=>Employee

    不同namespace查出的数据会放在自己对应的缓存中(map)

使用:

1.开启全局二级缓存配置,在Mybatis全局配置文件中配置

注意:该配置控制的是二级缓存,对一级缓存没有影响

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.在sql映射文件配置使用二级缓存

<mapper namespace="com.wj.dao.EmployeeMapper">

    <cache eviction="FIFO"
           flushInterval="6000"
           readOnly="false"
           size="1024"/>
    ....
</mapper>

常用配置项说明:

eviction:缓存的回收策略

  • LRU:最近最少使用(默认使用)
  • FIFO:先进先出
  • SOFT:软引用:基于垃圾回收器状态和软引用规则的对象
  • WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushInterval:缓存刷新间隔,默认不清空,设置毫秒值

readOnly:缓存是否只读

  • true:只读,mybatis认为所以从缓存中获取数据的操作都是只读操作,不会修改数据,mybatis为了较快获取速度,直接就会将数据缓存中的引用交给用户,不安全但速度快。
  • false:非只读,mybatis觉得获取的数据可能会被修改,mybatis会利用序列化&反序列化技术克隆一份新的数据给用户,安全(默认)

size:缓存存放多少元素

type:指定自定义缓存的全类型,需要实现Cache接口即可。

3.我们的实体类需要实现序列化接口

@Data
@ToString
public class Employee implements Serializable {
    private Integer id;
    private String lastName;
    private String email;
    private String gender;
}

测试:

    @Test
    public void testSecondLevel() throws IOException {
        //1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();
        SqlSession session2 = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
            EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);

            Employee emp = employeeMapper.getEmpById(1);
            System.out.println(emp);
            session.close();
            System.out.println("===============关闭session================");
            Employee emp2 = employeeMapper2.getEmpById(1);
            System.out.println(emp2);
            session2.close();
        }finally {

        }
    }

image-20210113213851469

注意:

只有之前的sqlSession关闭了,才有二级缓存,否则不会进入二级缓存。

缓存相关属性/设置

1.cacheEnabled=false 二级缓存关闭,一级缓存仍有用

2.select标签的 useCache=false 二级缓存关闭,一级缓存仍有用

3.每个增删改标签的 flushCache=true 增删改完成之后,二级缓存和一级缓存同时清空

查询标签 flushCache=true 每次查询之后都会清空缓存

4.sqlSession.clearCache() 只是清楚当前session的一级缓存

5.localCacheScope:本地缓存作用域

  • SESSION:当前会话的所有数据保存在会话缓存中
  • STATEMENT:可以禁用一级缓存

查询顺序

二级缓存=》一级缓存=》数据库

缓存创建部分源码

跟缓存有关的核心接口:org.apache.ibatis.cache.Cache.java

image-20210113222022424

光从包名decorators,我们就只可以得知,这Cache的设计就是装饰器模式。

查看PerpetualCache代码,平平无奇!!

public class PerpetualCache implements Cache {

  private String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }
  .......

我们点进decorators包下面任意一个Cache实现:例如BlockingCache

image-20210113222503091

典型的装饰器模式的结构。该BlockingCache组合了另一个Cache,我们猜测它是装饰者,而BlockingCache是具体组件。

在XMLMapperBuilder中:会根据sql映射文件中cache标签创建一个Cache,参数是标签的配置属性。

image-20210113223302591

点击进入MapperBuilderAssistant的useNewCache方法:

image-20210113223718498

点击进入CacheBuilder的build()方法

image-20210113224228474

点击进入newCacheDecoratorInstance方法:base是Cache的具体实现,默认是PerpetualCache

image-20210113224436079

既然是通过反射进行装饰的,那我们可以任意点击一个Cache的装饰类,查看它的构造器,还以BlockingCache为例

image-20210113224835872

而mybatis的缓存模块,也是装饰器模式的典型代表,学习楷模!!!

原文地址:https://www.cnblogs.com/wwjj4811/p/14274899.html