1. 概述
与大多数持久层框架一下,Mybatis同样提供了一级缓存和二级缓存的支持。
- 一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session做flush或close之后,该Session中的所有cache就将清空;
- 二级缓存与一级缓存机制相同,默认也是采用PerpetualCache的HashMap存储,不同在于其存储作用域为Mapper(NameSpace),并且可自定义存储源,如Ehcache。
注意:对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存NameSpace)进行了CUD操作后,默认该作用域下所有select中的缓存将被clear。
2. 准备工作
创建maven工程,目录结构如下:
user表结构及数据:
User类:
public class User { private int id; private String user_name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUser_name() { return user_name; } public void setUser_name(String user_name) { this.user_name = user_name; } }
UserMapper类:
public interface UserMapper { User selectUserById(int id); User selectUserByName(String name); }
mybatis-config.xml文件:
<?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> <properties resource="jdbc.properties" /> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${user}"/> <property name="password" value="${pwd}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/mapper/UserMapper.xml"/> </mappers> </configuration>
UserMapper.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="org.mybatis.mapper.UserMapper"> <select id="selectUserById" resultType="org.mybatis.mapper.User"> select * from user where id = #{id} </select> <select id="selectUserByName" parameterType="java.lang.String" resultType="org.mybatis.mapper.User"> select * from user where user_name = #{name}; </select> </mapper>
App类:
public class App { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session = sqlSessionFactory.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1); System.out.println(user.getUser_name()); User u = mapper.selectUserByName("xiaoli"); System.out.println(u.getId()); session.commit(); } finally { session.close(); } } }
pom.xml文件增加如下依赖:
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.36</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.22</version> </dependency> </dependencies>
3. 一级缓存
mybatis会在标示会话的SqlSession对象中建立一个简单的本地缓存(local cache),每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,不需要再进行一次数据库查询了。
一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SqlSession,在对象中有一个内存区域数据结构HashMap用于存储缓存数据。不同SqlSession之间的缓存数据区域时互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个SqlSession中两次执行相同的sql语句,当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了,mybatis默认开启一级缓存。
3.1 实例1:同一个SqlSession中两次查询id=1的记录
public class App { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session = sqlSessionFactory.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); User u1 = mapper.selectUserById(1); System.out.println(u1.getUser_name()); // 利用缓存 User u2 = mapper.selectUserById(1); System.out.println(u2.getUser_name()); } finally { session.close(); } } }
截图:从日志可以看出,第二次相同sql语句的查询没有直接访问数据库,而是从缓存中获取。
3.2 实例2:同一个SqlSession中一次查询id=1,另一次查询id=2的记录
public class App { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session = sqlSessionFactory.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); // id = 1 User u1 = mapper.selectUserById(1); System.out.println(u1.getUser_name()); // id = 2, 两次sql语句不同 User u2 = mapper.selectUserById(2); System.out.println(u2.getUser_name()); } finally { session.close(); } } }
结果截图:
3.3 实例3:同一个SqlSession中两次查询id=1的记录,但是中途清除一级缓存
public class App { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session = sqlSessionFactory.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); // id = 1 User u1 = mapper.selectUserById(1); System.out.println(u1.getUser_name()); // 清除一级缓存 session.clearCache(); User u2 = mapper.selectUserById(1); System.out.println(u2.getUser_name()); } finally { session.close(); } } }
截图:
4. 二级缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同,即最终执行相同的sql语句,第一次执行完会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。mybatis默认没有开启二级缓存需要在settings全局参数中配置开启二级缓存。
4.1 使用二级缓存
4.1.1 实体类User实现接口Serializable,否则报错
Exception in thread "main" org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: org.mybatis.mapper.User
4.1.2 mybatis-config.xml增加配置
<settings> <setting name="cacheEnabled" value="true"/> </settings>
必须开启缓存配置(默认是开启的),才能使用二级缓存。
4.1.3 UserMapper.xml开启缓存
<mapper namespace="org.mybatis.mapper.UserMapper"> <cache/> <select id="selectUserById" resultType="org.mybatis.mapper.User"> select * from user where id = #{id} </select> <select id="selectUserByName" parameterType="java.lang.String" resultType="org.mybatis.mapper.User"> select * from user where user_name = #{name}; </select> </mapper>
增加配置<cache />
4.1.4 App.java和结果
public class App { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream is = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session1 = sqlSessionFactory.openSession(); SqlSession session2 = sqlSessionFactory.openSession(); try { UserMapper mapper1 = session1.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); // id = 1 User u1 = mapper1.selectUserById(1); System.out.println(u1.getUser_name()); // 第一个session必须提交才能使用二级缓存。 session1.commit(); User u2 = mapper2.selectUserById(1); System.out.println(u2.getUser_name()); } finally { session1.close(); session2.close(); } } }
截图:
5. 总结
mybatis一级缓存:默认开启
(1)必须同一个SqlSession,如果SqlSession对象已经close,就不能用了;
(2)查询条件必须一致;
(3)没有执行过session.cleanCache()
(4)没有执行过增删改操作(这些操作都会清理缓存)
mybatis二级缓存:
(1)mybatis-config.xml中默认配置
<settings> <setting name="cacheEnabled" value="true" /> </settings>
(2)手动在Mapper.xml中添加<cache>
<cache/> 有默认的参数值,比如
<cache
eviction="FIFO" // 回收策略
flushInterval="60000" // 自动刷新时间60秒
size="512" // 最多缓存512个引用对象
readOnly="true" // 只读
/>
(3)映射语句文件中的所有select语句将会被缓存
(4)映射语句文件中的所有insert,update和delete语句会刷新缓存
(5)缓存会使用Least Recently Used(LRU,最近最少使用)算法来收回
(6)缓存会根据指定的时间间隔来刷新
(7)缓存会默认存储1024个对象
x. 参考资料
https://my.oschina.net/KingPan/blog/280167
https://www.cnblogs.com/little-fly/p/6251451.html