Mybatis的缓存
1、MyBatis 缓存的基本介绍
缓存是一般的 ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。缓存是存在内存中的临时数据,将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。经常查询并且不经常改变的数据就可以使用缓存。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
2、一级缓存(本地缓存)
一级缓存也叫本地缓存,一级缓存是默认开启的。
同一个 SqlSession
对象, 在参数和 SQL 完全一样的情况下,只执行一次 SQL 语句(如果缓存没有过期)。只有在参数和 SQL 完全一样的情况下, 才会有这种情况。
示例:
下面示例需要开启 mybatis 日志才能看到效果。
假设我们在一个Session中查询两次相同记录,此时除了第一次查询外,后面的查询都会直接使用缓存,而不会再去查询数据库。
@Test public void test01() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); User user = userDao.findUserById(1); System.out.println("=======分隔符======="); User user2 = userDao.findUserById(1); System.out.println(user == user2); }
日志输出如下:
可以看到,第一次查询发送了 SQL 语句, 后返回了结果;第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。两次查询获取到的对象是相同的,这说明第二次查询是直接从缓存拿到的结果。
2.1、一级缓存的特性
一级缓存特性:
- 在同一个 SqlSession 中,相同SQL并且参数相同才会命中缓存。在同一个 SqlSession 中,Mybatis 会把执行的 SQL 和参数通过算法生成缓存的键 key, 将键 key 和 SQL 查询的结果作为 value 值存放在一个 Map 中, 如果后续的键一样,即 sql 和参数意义, 则直接从 Map 中获取缓存数据
- 不同的 SqlSession 之间的缓存是相互隔离的。例如查询一条 SQL 后又重新创建了一个 sqlsession 对象,则这两个对象之间的缓存是不共用的
- 查询不同的 mapper 的 xml 配置文件的 sql 语句,缓存不共用
- 任何的增删改( UPDATE、 INSERT、DELETE)语句都会清空缓存。
- 可以在 mapper 文件的 select 标签中配置 flushCache=“true” ,如 <select id="selectByPrimaryKey" flushCache="true" ...>,这样每次查询都会将这个 sqlsession 中的所有缓存清空。或者手动调用清空缓存的方法 sqlSession.clearCache(); 也可清除缓存。
3、二级缓存(全局缓存)
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。二级缓存存在于 SqlSessionFactory 生命周期中。二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
缓存机制:会先命中二级缓存,二级缓存中没有再看一级缓存,都没有才去查数据库。
3.1、使用二级缓存
在 mybatis 的配置文件 mybatis-config.xml 中添加 <setting name="cacheEnabled" value="true"/> 配置:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--配置日志--> <setting name="logImpl" value="STDOUT_LOGGING" /> <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 --> <setting name="cacheEnabled" value="true"/> </settings> ... </configuration>
在 mapper 的配置文件 *Mapper.xml 中显式地开启二级缓存, 默认是不开启的:
<?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="dao.UserDao"> <!-- 在当前Mapper.xml中使用二级缓存。 也可以不配置属性,直接使用 <cache />。下面配置中的属性说明如下: 1、缓存的清除策略为 LRU(默认值),即最近最少使用:移除最长时间不被使用的对象。 2、每隔 60 秒刷新 3、最多可以存储结果对象或列表的 512 个引用 4、返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <select id="findUserById" resultType="entity.User"> select * from user where id = #{id} </select> </mapper>
注意,如果是直接使用 <cache />,即不配置属性,此时实体类即 User 类需要实现序列化 Serializable 接口。
测试代码:
@Test public void test01() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); User user = userDao.findUserById(1); //需要手动提交事务 session.commit(); session.close(); System.out.println("=======分隔符======="); SqlSession session2 = factory.openSession(); UserDao userDao2 = session2.getMapper(UserDao.class); User user2 = userDao2.findUserById(1); System.out.println(user == user2); }
日志输出如下:
可以看到,在同一个 mapper 配置文件中的 SQL 语句,即使是不同的 sqlsession 对象,仍然可以命中缓存。
只有查询才有缓存,我们可以根据数据是否需要缓存来配置该条查询语句是否缓存:
<select id="getUserById" resultType="user" useCache="true"> select * from user where id = #{id} </select>
3.2、二级缓存的特性
二级缓存的特性:
-
开启了二级缓存后,同一个Mapper中的相同 SQL 相同参数的查询可以在二级缓存中拿到数据。
-
查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。