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
,对象作为value
保存的.
示例: OneLevelCacheTest
接下来我们测试MyBatis
的一级缓存.
项目结构
展开/折叠
G:\workspace_web2\MyOneLeveldureTest
├─src\
│ ├─db.properties
│ ├─domain\
│ │ └─User.java
│ ├─fractory\
│ │ └─SqlSessionFactoryTools.java
│ ├─log4j.xml
│ ├─mapper\
│ │ ├─UserMapper.java
│ │ └─UserMapper.xml
│ ├─mybatis-config.xml
│ ├─tb_user.sql
│ └─test\
│ ├─SelectAllUserTest.java
│ ├─TestClearCache.java
│ ├─TestClose.java
│ ├─TestDelete.java
│ └─TestSelectFromCache.java
└─WebContent\
├─META-INF\
│ └─MANIFEST.MF
└─WEB-INF\
└─lib\
├─ant-1.9.6.jar
├─ant-launcher-1.9.6.jar
├─asm-5.2.jar
├─cglib-3.2.5.jar
├─commons-logging-1.2.jar
├─javassist-3.22.0-CR2.jar
├─log4j-1.2.17.jar
├─log4j-api-2.3.jar
├─log4j-core-2.3.jar
├─mybatis-3.4.5.jar
├─mysql-connector-java-5.1.44-bin.jar
├─ognl-3.1.15.jar
├─slf4j-api-1.7.25.jar
└─slf4j-log4j12-1.7.25.jar
环境搭建
创建并初始化数据库表
/MyOneLeveldureTest/src/tb_user.sql1 2 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');
|
创建持久化对象
/MyOneLeveldureTest/src/domain/User.java1 2 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 + "]"; } }
|
工具类
/MyOneLeveldureTest/src/fractory/SqlSessionFactoryTools.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class SqlSessionFactoryTools { 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映射文件
/MyOneLeveldureTest/src/mapper/UserMapper.xml1 2 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接口
/MyOneLeveldureTest/src/mapper/UserMapper.java1 2 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); }
|
测试从一级缓存中查询数据
/MyOneLeveldureTest/src/test/TestSelectFromCache.java1 2 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(); } } }
|
1 2 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语句时会自动清空缓存
/MyOneLeveldureTest/src/test/TestDelete.java1 2 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(); } } }
|
运行结果:
1 2 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
语句。
此时运行效果如下:
1 2 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方法
/MyOneLeveldureTest/src/test/TestClearCache.java1 2 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(); } } } }
|
运行效果如下:
1 2 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时缓存会被清空
/MyOneLeveldureTest/src/test/TestClose.java1 2 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(); } } } }
|
运行结果:
1 2 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
对象。