10.5 MyBatis缓存机制 10.5.1一级缓存(SqlSession级别)

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操作(insertupdatedelete),并提交到数据库,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.sql
1
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.java
1
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();
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}

工具类

/MyOneLeveldureTest/src/fractory/SqlSessionFactoryTools.java
1
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.xml
1
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">
<!-- namespace指用户自定义的命名空间。 -->
<mapper namespace="mapper.UserMapper">
<!-- 根据ID查询用户 -->
<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.java
1
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.java
1
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);
//第一次查询id为1的用户,将会执行SQL语句,从数据库中查询.
User user = userMapper.selectUserById(1);
System.out.println("-------------------------------------------------");
System.out.println(user);
//再次查询id为1的用户,该用户已经缓存了,直接在一级缓存中查找,不需要再次执行SQL查找
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的执行结果,在第一次查询id1User对象时执行了一条select语句,但是第二次获取id1User对象时并没有执行select语句。
因为此时一级缓存也就是SqlSession缓存中已经缓存了id1User对象,MyBatis直接从缓存中将对象取出来,并没有再次去数据库查询,所以第二次没有再执行select语句。

执行DML语句时会自动清空缓存

/MyOneLeveldureTest/src/test/TestDelete.java
1
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);
// 第一次查询id为1的用户,将会执行SQL语句,从数据库中查询.
User user = userMapper.selectUserById(1);
System.out.println("-------------------------------------------------");
System.out.println(user);
// 删除掉一个用户,DML(insert,upate,delete)操作会清空一级缓存.
userMapper.deleteUserById(5);
sqlSession.commit();
System.out.println("-------------------------------------------------");
// 再次查询id为1的用户,因为缓存被清空了,将会再次执行select语句.
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的执行结果,在第一次查询id1User对象时执行了一条select语句,接下来执行了一个delete操作,MyBatis为了保证缓存中存储的是最新的信息,会清空原来的SqlSession缓存。
当第二次获取id1User对象时.一级缓存也就是SqlSession缓存中并没有缓存任何对象,所以MyBatis再次执行select语句去查询id1User对象。
如果注释delete操作的代码:

1
2
//            userMapper.deleteUserById(5);
// sqlSession.commit();

由于并没有执行DML操作并将操作提交到数据库,故此时MyBatis不会清空SqlSession缓存,当再次查询id1User对象时不会执行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.java
1
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();
// 因为缓存被清除,将再次执行select语句从数据库中获取信息
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的执行结果,在第一次查询id1User对象时执行了一条select语句,接下来调用SqlSessionclearCache()方法,该方法会清空SqlSession缓存。
当第二次获取id1User对象时,因为一级缓存中并没有缓存任何对象,所以MyBatis会再次执行select语句去查询id1User对象。

关闭SqlSession时缓存会被清空

/MyOneLeveldureTest/src/test/TestClose.java
1
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();
// 重新获取mapper接口的代理对象
userMapper = sqlSession.getMapper(UserMapper.class);
// 新的会话中还没有缓存数据,将执行select语句查询数据
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的执行结果,在第一次查询id1User对象时执行了一条select语句,接下来调用SqlSessionclose()方法,该方法会关闭SqlSession缓存。
当第二次获取id1User对象时,一级缓存也就是SqlSession缓存是一个全新的对象,因为此时一级缓存中并没有缓存任何对象,所以MyBatis会再次执行select语句去查询id1User对象。