MyBatis 缓存

缓存

使用缓存的作用也是减少 Java 应用程序与数据库的交互次数,从而提升程序的运行效率。比如第一次查询出某个对象之后,MyBatis 会自动将其存入缓存,当下一次查询同一个对象时,就可以直接从缓存中获取,不必再次访问数据库了。

MyBatis 有两种缓存:一级缓存和二级缓存。

MyBatis 自带一级缓存,并且是无法关闭的,一直存在,一级缓存的数据存储在 SqlSession 中,即它的作用域是同一个 SqlSession,当使用同一个 SqlSession 对象执行查询的时候,第一次的执行结果会自动存入 SqlSession 缓存,第二次查询时可以直接从缓存中获取。

但是如果是两个 SqlSession 查询两次同样的 SQL,一级缓存不会生效,需要访问两次数据库。

同时需要注意,为了保证数据的一致性,如果 SqlSession 执行了增加、删除,修改操作,MyBatis 会自动清空 SqlSession 缓存中存储的数据。

一级缓存不需要进行任何配置,可以直接使用。

MyBatis 二级缓存是比一级缓存作用域更大的缓存机制,它是 Mapper 级别的,只要是同一个 Mapper,无论使用多少个 SqlSession 来操作,数据都是共享的,多个不同的 SqlSession 可以共用二级缓存。

MyBatis 二级缓存默认是关闭的,需要使用时可以通过配置手动开启。

一级缓存

实体类对象

public class User {
    private Integer id;
    private String username;
    private String userEmail;
    private String userCity;
    private Integer age;
}

定义接口方法

public User findById(Integer id);

接口对应的 Mapper.xml 定义如下所示

<select id="findById" resultType="com.example.mybatis.entity.User">
    select * from user where id = #{id}
</select>

测试

public class App {
    public static void main( String[] args ) {
        // 加载 MyBatis 配置文件
        InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
        // SqlSession 和Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 获取实现接口的代理对象
        // UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();
    }
}

结果如下所示

[2020-02-22 15:01:53:387] ==>  Preparing: select * from user where id = ? 
[2020-02-22 15:01:53:455] ==> Parameters: 1(Integer)
[2020-02-22 15:01:53:541] <==      Total: 1
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}

可以看到结果,执行了一次 SQL 语句,查询出两个对象,第一个对象是通过 SQL 查询的,并保存到缓存中,第二个对象是直接从缓存中获取的。

一级缓存是 SqlSession 级别的,所以 SqlSession 一旦关闭,缓存也就不复存在了,修改代码,再次测试。

public class App {
    public static void main( String[] args ) {
        // 加载 MyBatis 配置文件
        InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
        // SqlSession 和Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 获取实现接口的代理对象
        // UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();

        sqlSession = sqlSessionFactory.openSession(true);
        userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();
    }
}

结果如下所示

[2020-02-22 15:06:04:769] ==>  Preparing: select * from user where id = ? 
[2020-02-22 15:06:04:871] ==> Parameters: 1(Integer)
[2020-02-22 15:06:04:971] <==      Total: 1
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}
[2020-02-22 15:06:04:989] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@32464a14]
[2020-02-22 15:06:04:989] Returned connection 843467284 to pool.
[2020-02-22 15:06:04:989] Opening JDBC Connection
[2020-02-22 15:06:04:989] Checked out connection 843467284 from pool.
[2020-02-22 15:06:04:990] ==>  Preparing: select * from user where id = ? 
[2020-02-22 15:06:04:991] ==> Parameters: 1(Integer)
[2020-02-22 15:06:05:000] <==      Total: 1
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}

可以看到,执行了两次 SQL,在关闭 SqlSession、一级缓存失效的情况下,可以启用二级缓存,实现提升效率的需求。

二级缓存

MyBatis 可以使用自带的二级缓存,也可以使用第三方的 ehcache 二级缓存。

先来使用 MyBatis 自带的二级缓存,具体步骤如下所示。

1.配置文件中开启二级缓存

<configuration>
    ...
    <!-- 设置 settings -->
    <settings>
        <!--开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    ...
</configuration>

2.在Mapper文件中配置<cache></cache>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatis.mapper.UserMapper">
    <cache></cache>
    <!--
        可以设置useCache="false"来关闭该查询语句的缓存
    -->
    <select id="findById" resultType="com.example.mybatis.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>

3.实体类实现 Serializable 接口

public class User implements Serializable{
    private Integer id;
    private String username;
    private String userEmail;
    private String userCity;
    private Integer age;
}

4.测试

public class App {
    public static void main( String[] args ) {
        // 加载 MyBatis 配置文件
        InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
        // SqlSession 和Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 获取实现接口的代理对象
        // UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();

        sqlSession = sqlSessionFactory.openSession(true);
        userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();
    }
}

结果如下所示

[2020-02-22 15:16:12:887] ==>  Preparing: select * from user where id = ? 
[2020-02-22 15:16:13:023] ==> Parameters: 1(Integer)
[2020-02-22 15:16:13:095] <==      Total: 1
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}
[2020-02-22 15:16:13:122] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2bbaf4f0]
[2020-02-22 15:16:13:122] Returned connection 733672688 to pool.
[2020-02-22 15:16:13:131] Cache Hit Ratio [com.example.mybatis.mapper.UserMapper]: 0.5
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}

可以看到,执行了一次 SQL,查询出两个对象,二级缓存生效

ehcache 二级缓存

1.添加 ehcache 相关依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.0</version>
</dependency>

2.在 resources 路径下创建 ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--指定磁盘路径-->
    <diskStore path="G:\test" />
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

3.配置文件开启二级缓存

<configuration>

    <!-- 设置settings -->
    <settings>
        <!-- 打印SQL -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>

4.在Mapper文件中配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatis.mapper.UserMapper">
    <!--
        collection: 指定要遍历的集合
            如果为Collection类型的,key为collection;
            如果为List类型的,key为list
            如果是数组类型,key为array
        item: 将当前遍历的元素赋值给指定的变量
        open: 给遍历的结果添加一个开始字符
        close: 给遍历的结果添加一个结束字符
        separator: 每个元素之间的分隔符
    -->
    <select id="getUsersByIds"
            resultType="com.example.mybatis.entity.User">
        select * from user
        where id in
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>
    <!-- 开启二级缓存 -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache" >
        <!-- 缓存创建以后,最后一次访问缓存的时间至失效的时间间隔 -->
        <property name="timeToIdleSeconds" value="3600"/>
        <!-- 缓存自创建时间起至失效的时间间隔-->
        <property name="timeToLiveSeconds" value="3600"/>
        <!-- 缓存回收策略,LRU 移除近期最少使用的对象 -->
        <property name="memoryStoreEvictionPolicy" value="LRU"/>
    </cache>
    <!--
        可以设置useCache="false"来关闭该查询语句的缓存
    -->
    <select useCache="true" id="findById" resultType="com.example.mybatis.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>

5.实体类不需要实现 Serializable 接口

6.测试

public class App {
    public static void main( String[] args ) {
        // 加载 MyBatis 配置文件
        InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
        // SqlSession 和Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 获取实现接口的代理对象
        // UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();

        sqlSession = sqlSessionFactory.openSession(true);
        userMapper = sqlSession.getMapper(UserMapper.class);
        System.out.println(userMapper.findById(1).toString());
        sqlSession.close();
    }
}

结果如下所示

==>  Preparing: select * from user where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, username, user_email, user_city, age
<==        Row: 1, zhangsan, zhangsan@qq.com, shenzheng, 20
<==      Total: 1
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@59662a0b]
Returned connection 1499867659 to pool.
Cache Hit Ratio [com.example.mybatis.mapper.UserMapper]: 0.5
User{id=1, username='zhangsan', userEmail='zhangsan@qq.com', userCity='shenzheng', age=20}

同样执行一次 SQL,查询出两个对象,ehcache 二级缓存生效。

总结

MyBatis 的缓存分两种:一级缓存和二级缓存,一级缓存是 SqlSession 级别的,二级缓存是 Mapper 级别的。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!