10.5 MyBatis缓存机制
在实际项目开发中,通常对数据库査询的性能要求很高,而MyBatis提供了查询缓存来缓存数据,从而达到提高查询性能的要求。MyBatis的查询缓存分为一级缓存和二级缓存。
- 一级缓存是SqlSession级别的缓存,
- 二级缓存是mapper级别的缓存,二级缓存是多个Sqlsession共享的。
MyBatis通过缓存机制减轻数据压力,提高数据库性能。
10.5.1一级缓存(SqlSession级别)
MyBatis的一级缓存是Sqlsession级别的缓存。在操作数据库时需要构造SqlSession对象,在SqlSession对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是SqlSession范围的,当在同一个SqlSession中执行两次相同的SQL语句时,
- 第一次执行完毕会将从数据库中査询的数据写到缓存(内存),
- 第二次査询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
需要注意的是,如果SqlSession执行了DML操作(insert、update和delete),并提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。
当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存,不需要进行任何配置。
缓存机制
MyBatis的缓存机制是基于id进行缓存的,也就是说,MyBatis使用HashMap缓存数据时,是使用对象的id作为key,对象作为vaue保存的.
示例: OneLevelCacheTest
接下来我们测试MyBatis的一级缓存.
环境搭建
创建并初始化数据库表
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | DROP TABLE IF EXISTS `tb_user`;
 CREATE TABLE `tb_user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(18) DEFAULT NULL,
 `sex` char(2) DEFAULT NULL,
 `age` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 INSERT INTO `tb_user` VALUES ('1', '小明', '男', '21');
 INSERT INTO `tb_user` VALUES ('2', '小王', '男', '22');
 INSERT INTO `tb_user` VALUES ('3', '小丽', '女', '18');
 INSERT INTO `tb_user` VALUES ('4', '小芳', '女', '18');
 INSERT INTO `tb_user` VALUES ('5', '小王', '男', '22');
 
 | 
创建持久化对象
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | package domain;public class User {
 private Integer id;
 private String name;
 private String sex;
 private Integer age;
 public User()
 {
 super();
 }
 
 @Override
 public String toString()
 {
 return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
 }
 }
 
 | 
工具类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class SqlSessionFratoryTools {private static SqlSessionFactory sqlSessionFactory = null;
 static
 {
 try
 {
 InputStream mybatisConfigXML = Resources.getResourceAsStream("mybatis-config.xml");
 sqlSessionFactory = new SqlSessionFactoryBuilder().build(mybatisConfigXML);
 } catch (IOException e)
 {
 e.printStackTrace();
 }
 }
 public static SqlSession getSqlSession()
 {
 return sqlSessionFactory.openSession();
 }
 }
 
 | 
Mapper.xml映射文件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | <?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="mapper.UserMapper">
 
 <select
 id="selectUserById"
 parameterType="int"
 resultType="domain.User"> select * from tb_user where id=#{id}
 </select>
 
 <select
 id="selectAllUser"
 resultType="domain.User"> select * from tb_user
 </select>
 <delete
 id="deleteUserById"
 parameterType="int"> delete from tb_user where id=#{id}
 </delete>
 </mapper>
 
 | 
Mapper接口
| 12
 3
 4
 5
 6
 7
 8
 
 | package mapper;import java.util.List;
 import domain.User;
 public interface UserMapper {
 User selectUserById(Integer id);
 List<User> selectAllUser();
 void deleteUserById(Integer id);
 }
 
 | 
测试从一级缓存中查询数据
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | package test;import org.apache.ibatis.session.SqlSession;
 import domain.User;
 import fractory.SqlSessionFratoryTools;
 import mapper.UserMapper;
 public class TestSelectFromCache {
 public static void main(String[] args)
 {
 SqlSession sqlSession = null;
 try
 {
 sqlSession = SqlSessionFratoryTools.getSqlSession();
 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 
 User user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------");
 System.out.println(user);
 
 user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------");
 System.out.println(user);
 sqlSession.commit();
 } catch (Exception e)
 {
 sqlSession.rollback();
 e.printStackTrace();
 } finally
 {
 if (sqlSession != null)
 sqlSession.close();
 }
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 
 | DEBUG [main] ==>  Preparing: select * from tb_user where id=? DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 
 | 
仔细观察MyBatis的执行结果,在第一次查询id为1的User对象时执行了一条select语句,但是第二次获取id为1的User对象时并没有执行select语句。
因为此时一级缓存也就是SqlSession缓存中已经缓存了id为1的User对象,MyBatis直接从缓存中将对象取出来,并没有再次去数据库查询,所以第二次没有再执行select语句。
执行DML语句时会自动清空缓存
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 
 | package test;import org.apache.ibatis.session.SqlSession;
 import domain.User;
 import fractory.SqlSessionFratoryTools;
 import mapper.UserMapper;
 public class TestDelete {
 public static void main(String[] args)
 {
 SqlSession sqlSession = null;
 try
 {
 sqlSession = SqlSessionFratoryTools.getSqlSession();
 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 
 User user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------");
 System.out.println(user);
 
 userMapper.deleteUserById(5);
 sqlSession.commit();
 System.out.println("-------------------------------------------------");
 
 user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------");
 System.out.println(user);
 
 sqlSession.commit();
 } catch (Exception e)
 {
 
 sqlSession.rollback();
 e.printStackTrace();
 } finally
 {
 
 if (sqlSession != null)
 sqlSession.close();
 }
 }
 }
 
 | 
运行结果:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | DEBUG [main] ==>  Preparing: select * from tb_user where id=? DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 DEBUG [main] ==>  Preparing: delete from tb_user where id=?
 DEBUG [main] ==> Parameters: 5(Integer)
 DEBUG [main] <==    Updates: 0
 -------------------------------------------------
 DEBUG [main] ==>  Preparing: select * from tb_user where id=?
 DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 
 | 
仔细观察MyBatis的执行结果,在第一次查询id为1的User对象时执行了一条select语句,接下来执行了一个delete操作,MyBatis为了保证缓存中存储的是最新的信息会清空Sqlsession缓存。
当第二次获取id为1的User对象时.一级缓存也就是Sqlsession缓存中并没有缓存任何对象,所以MyBatis再次执行select语句去查询id为1的User对象。
如果注释delete操作的代码:
由于并没有执行DML操作并将操作提交到数据库,故此时MyBatis不会清空SqlSession缓存,当再次查询id为1的User对象时不会执行select语句。
此时运行效果如下:
| 12
 3
 4
 5
 6
 7
 8
 
 | DEBUG [main] ==>  Preparing: select * from tb_user where id=? DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 -------------------------------------------------
 -------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 
 | 
调用SqlSession的clearCache方法手动清除一级缓存
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | package test;import org.apache.ibatis.session.SqlSession;
 import domain.User;
 import fractory.SqlSessionFratoryTools;
 import mapper.UserMapper;
 public class TestClearCache {
 public static void main(String[] args)
 {
 SqlSession sqlSession = null;
 try
 {
 sqlSession = SqlSessionFratoryTools.getSqlSession();
 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 User user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------------");
 System.out.println(user);
 System.out.println("-------------------------------------------------------");
 
 sqlSession.clearCache();
 
 user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------------");
 System.out.println(user);
 } catch (Exception e)
 {
 
 sqlSession.rollback();
 } finally
 {
 if (sqlSession != null)
 {
 sqlSession.close();
 }
 }
 }
 }
 
 | 
运行效果如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | DEBUG [main] ==>  Preparing: select * from tb_user where id=? DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 -------------------------------------------------------
 DEBUG [main] ==>  Preparing: select * from tb_user where id=?
 DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 
 | 
仔细观察MyBatis的执行结果,在第一次查询id为1的User对象时执行了一条select语句,接下来调用SqlSession的clearCache()方法,该方法会清空SqlSession缓存。当第二次获取id为1的User对象时,一级缓存中并没有缓存任何对象,所以MyBatis再次执行select语句去查询id为1的User对象。
关闭SqlSession时缓存会被清空
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | package test;import org.apache.ibatis.session.SqlSession;
 import domain.User;
 import fractory.SqlSessionFratoryTools;
 import mapper.UserMapper;
 public class TestClose {
 public static void main(String[] args)
 {
 SqlSession sqlSession = null;
 try
 {
 sqlSession = SqlSessionFratoryTools.getSqlSession();
 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
 User user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------------");
 System.out.println(user);
 System.out.println("-------------------------------------------------------");
 
 sqlSession.close();
 
 sqlSession = SqlSessionFratoryTools.getSqlSession();
 
 userMapper = sqlSession.getMapper(UserMapper.class);
 
 user = userMapper.selectUserById(1);
 System.out.println("-------------------------------------------------------");
 System.out.println(user);
 } catch (Exception e)
 {
 
 sqlSession.rollback();
 e.printStackTrace();
 } finally
 {
 if (sqlSession != null)
 {
 sqlSession.close();
 }
 }
 }
 }
 
 | 
运行结果:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | DEBUG [main] ==>  Preparing: select * from tb_user where id=? DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 -------------------------------------------------------
 DEBUG [main] ==>  Preparing: select * from tb_user where id=?
 DEBUG [main] ==> Parameters: 1(Integer)
 DEBUG [main] <==      Total: 1
 -------------------------------------------------------
 User [id=1, name=小明, sex=男, age=21]
 
 | 
仔细观察MyBatis的执行结果,在第一次查询id为1的User对象时执行了一条select语句,接下来调用Sqlsession的close()方法,该方法会关闭SqlSession缓存。当第二次获取id为1的User对象时一级缓存也就是SqlSession缓存是一个全新的对象,一级缓存中并没有缓存任何对象,所以MyBatis再次执行select语句去查询id为1的User对象。
原文链接: 10.5 MyBatis缓存机制 10.5.1一级缓存(SqlSession级别)