主要是分析的一下Mybatis的一级缓存和二级缓存的原理和源码的操作。每一次在和数据库进行会话的过程中,MyBatis 都会创建一个SqlSession对象。同一次会话期间,只要是查询过的数据,都会保存在当前SqlSession对象的一个Map中。所以在一次会话的过程中,如果我们对一条查询SQL 语句进行了多次执行,经过判断后和之前执行的查询语句相同,并且在缓存中存在该SQL 语句的执行结果,那么这次查询就不会再从数据库中获取数据,而是直接从缓存中获取数据。因为一级缓存是建立在SqlSession对象中的,所以也可以说一级缓存是一个SqlSession级别的缓存。在默认的情况下,一级缓存是开启的。MyBatis 提供了二级缓存机制,二级缓存是一个namespace
级别的缓存,相对于一级缓存而言,二级缓存允许跨SqlSession
工作,因此二级缓存的作用范围更大。每个SqlSession在执行查询操作的时候,都会将查询的结果放在当前会话的一级缓存中。如果当前会话关闭,一级缓存中的数据会被保存在二级缓存中。因此二级缓存是在一级缓存的基础上进行扩展的,不同的namespace查出的数据都会将数据存在自己对应的缓存中,这些缓存信息使用Map存储。
mapper 接口:
Employee getEmpById(Integer id);
SQL 映射文件:
SELECT * FROM t_employee WHERE id = #{id}
测试代码:
// 用于创建SqlSession 对象
private SqlSession getSqlSession() throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
@Test
public void testCache() throws IOException {
SqlSession sqlSession = getSqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 从数据库中获得id 为1 的员工信息
Employee employee1 = employeeMapper.getEmpById(1);
System.out.println(employee1);
// 再一次从数据库中获得id 为1 的员工信息
Employee employee2 = employeeMapper.getEmpById(1);
System.out.println(employee2);
sqlSession.close();
}
上面的测试过程中一共对id
为1 的员工信息进行了两次查询,并且在中间过程中没有进行其他的操作,下面我们通过SQL 日志来看看执行的过程。
虽然我们执行了两次查询,但是从执行结果来看,其实只发送了一条SQL 语句,在第二次获得员工信息的时候并不是从数据库中获取数据,而是直接在一级缓存中获得数据,这就是一级缓存起到的作用。
一级缓存失效我们知道,一级缓存是一个SqlSession级别的缓存,所以一级缓存在工作的过程中会有一定的局限。一级缓存失效的情况:
- 1.只对当前SqlSession对象生效,对于其他的SqlSession来说一级缓存是不起作用的。
- 2.是同一个SqlSession对象,但是执行的是不同条件的SQL 查询语句。
- 3.同一个SqlSession对象执行同一条件的查询语句,但中间执行了增删改的操作,之前一级缓存中的数据会失效。如果执行的是不同条件的查询语句,一级缓存仍然生效,并且会把新的查询数据也加入到一级缓存中。
- 4.多次相同查询语句在执行期间通过sqlSession.clearCache();方法清除了缓存。
- 5.SqlSession 对象调用了close()方法,当前会话结束,一级缓存不再生效。
对于上面的几种情况,一级缓存都是不起作用的。所以在处理业务的时候要根据需求,具体分析能不能使用一级缓存。
一级缓存的生命周期- MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
- 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
- SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
MyBatis 提供了二级缓存机制,二级缓存是一个namespace
级别的缓存,相对于一级缓存而言,二级缓存允许跨SqlSession
工作,因此二级缓存的作用范围更大。每个SqlSession在执行查询操作的时候,都会将查询的结果放在当前会话的一级缓存中。如果当前会话关闭,一级缓存中的数据会被保存在二级缓存中。因此二级缓存是在一级缓存的基础上进行扩展的,不同的namespace查出的数据都会将数据存在自己对应的缓存中,这些缓存信息使用Map存储。需要注意的地方是:MyBatis 在开启二级缓存的情况下,如果发出了一条SQL 查询语句,会先向二级缓存中查询是否有对应的缓存数据,如果没有再接着查询一级缓存中的数据,如果一级缓存中也没有对应的缓存数据,才会向数据库发送SQL。下面附一张蹩脚的二级缓存运行机制图。
- 1.在MyBatis 全局配置文件中开启二级缓存的配置,默认也是开启的,建议自己设置为开启的。
- 2.在对应的SQL 映射文件中加入
标签。
- 3.将返回的JavaBean 数据类型的类实现
Serializable
(序列化)接口。
MyBatis 全局配置文件:
POJO 实现序列化接口:
package com.jas.mybatis.bean;
import java.io.Serializable;
public class Department implements Serializable{
private Integer id;
private String departmentName;
//省略get、set 与toString 方法
}
mapper 接口:
Department getDeptById(Integer id);
SQL 映射文件:
SELECT id, dept_name departmentName FROM t_dept WHERE id = #{id}
测试代码:
// 用于返回SqlSessionFactory 对象
private SqlSessionFactory getSqlSession() throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(is);
}
@Test
public void secondLevelTest() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSession();
//创建两个不同的会话
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
DepatmentMapper depatmentMapper1 = sqlSession1.getMapper(DepatmentMapper.class);
DepatmentMapper depatmentMapper2 = sqlSession2.getMapper(DepatmentMapper.class);
Department department1 = depatmentMapper1.getDeptById(1);
System.out.println(department1);
// 关闭当前的会话1, 否则二级缓存不起作用
sqlSession1.close();
Department department2 = depatmentMapper2.getDeptById(1);
System.out.println(department2);
// 关闭会话2
sqlSession2.close();
}
当会话1 查询结束并关闭后,我们使用另一个会话执行与会话1 相同的SQL 查询语句,发现并没有发送SQL 语句,因为缓存中已经保存了要查询的数据,这次直接从缓存中获取数据,这就是MyBatis 提供的跨会话级别(SqlSession
)的二级缓存机制。
1.可以在MyBatis 全局配置文件中的setting标签中配置cacheEnabled属性,表示是否启用二级缓存机制,不过不影响一级缓存。
2.select查询标签中可以设置useCache属性,配置是否使用二级缓存,默认一级缓存是一直生效的。
3.sqlSession.clearCache();方法默认只清除一级缓存中的数据。
4.可以在每个增删改的标签中设置flushCache属性,设置为true后,如果执行了该增删改操作,则一级缓存与二级缓存中的数据都会清空。
二级缓存的缺点MyBatis 提供的二级缓存机制好像并没有获得开发人员的青睐。原因是二级缓存是一个namespace级别的缓存,如果在不同的namespace下操作同一SQL 语句,可能导致缓存中的数据不正确。在进行多表联查的时候,也可能会导致二级缓存中的数据不正确。所以在实际的开发过程中要慎用二级缓存。(权限名+id 或实现的SQL语句的唯一操作,但是如果是的namespace级别的话会导致的是缓存数据查询不是我们想要的表中的数据。)