思维导图:
MyBatis 提供了一级缓存和二级缓存机制,用于提高数据库查询的性能,减少对数据库的访问次数。(本质上是减少IO次数)。
一级缓存也称为会话缓存,它是基于 SqlSession 的缓存。在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会优先从一级缓存中获取结果,而不是再次访问数据库。
2.1缓存结构:
在 SqlSession 内部,一级缓存是一个 PerpetualCache 对象,它本质上是一个 HashMap,键是根据查询的 SQL 语句、参数、环境等信息生成的唯一标识,值是查询结果。
查询流程:当调用 SqlSession 的查询方法时,MyBatis 会先将查询的 SQL 语句、参数等信息组合成一个唯一的缓存键。然后在 PerpetualCache 这个 HashMap 中查找该键对应的值。如果找到了,就直接返回该值;如果没找到,就会执行 SQL 查询,将查询结果存入 PerpetualCache 中,下次再执行相同查询时就可以直接从缓存中获取结果。
2.2 缓存命中的条件
相同的 SqlSession:必须是在同一个 SqlSession 实例中执行相同的查询,一级缓存才会生效。
相同的 SQL 语句:查询的 SQL 语句必须完全相同,包括 SQL 中的参数占位符和参数值。
相同的环境:查询的环境(如数据库连接、事务等)也必须相同。
代码示例:
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class FirstLevelCacheDetailExample {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建第一个 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
// 第一次查询
User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第一次查询结果: " + user1);
// 第二次查询,使用相同的 SqlSession
User user2 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第二次查询结果: " + user2);
// 两次查询结果相同,说明使用了一级缓存
System.out.println("两次查询结果是否相同: " + (user1 == user2));
// 执行更新操作
sqlSession1.update("com.example.UserMapper.updateUser", new User(1, "New Name"));
// 第三次查询
User user3 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第三次查询结果: " + user3);
// 由于执行了更新操作,一级缓存已清空,user3 是重新查询数据库得到的结果
System.out.println("第一次查询结果和第三次查询结果是否相同: " + (user1 == user3));
}
}
}
在上述代码中,前两次查询使用相同的 SqlSession 和相同的查询条件,所以第二次查询会从一级缓存中获取结果。而执行更新操作后,一级缓存被清空,第三次查询会重新访问数据库。
优点:
缺点:
缓存结构:
二级缓存也是基于 PerpetualCache 实现的,但它是基于 SqlSessionFactory 的。每个 Mapper 可以有自己独立的二级缓存,也可以多个 Mapper 共享同一个二级缓存。
查询流程:当一个 SqlSession 执行查询操作时,MyBatis 会先检查该 Mapper 对应的二级缓存中是否存在该查询结果。如果存在,则直接从二级缓存中获取结果;如果不存在,则执行 SQL 查询,并将查询结果存入二级缓存中。在多个 SqlSession 之间,只要它们是由同一个 SqlSessionFactory 创建的,就可以共享二级缓存。
全局配置:在 mybatis-config.xml 中开启二级缓存的全局开关。
Mapper 配置:在 Mapper 映射文件中配置缓存。
flushInterval="60000"
size="512"
readOnly="true" />
3. 示例代码及分析
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
public class SecondLevelCacheDetailExample {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第一个 SqlSession
try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) {
User user1 = sqlSession1.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第一个 SqlSession 查询结果: " + user1);
}
// 第二个 SqlSession
try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
User user2 = sqlSession2.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第二个 SqlSession 查询结果: " + user2);
// 两次查询结果相同,说明使用了二级缓存
System.out.println("两次查询结果是否相同: " + (user1 == user2));
}
// 执行更新操作
try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) {
sqlSession3.update("com.example.UserMapper.updateUser", new User(1, "New Name"));
sqlSession3.commit(); // 提交事务,清空二级缓存
}
// 第三个 SqlSession
try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) {
User user3 = sqlSession4.selectOne("com.example.UserMapper.selectUserById", 1);
System.out.println("第三个 SqlSession 查询结果: " + user3);
// 由于执行了更新操作,二级缓存已清空,user3 是重新查询数据库得到的结果
System.out.println("第一个查询结果和第三个查询结果是否相同: " + (user1 == user3));
}
}
}
在上述代码中,第一个 SqlSession 执行查询后,结果会存入二级缓存。第二个 SqlSession 执行相同查询时,会从二级缓存中获取结果。执行更新操作并提交事务后,二级缓存会被清空,第三个 SqlSession 执行查询时会重新访问数据库。
优点
缺点
作用范围:一级缓存是基于 SqlSession 的,作用范围较小;二级缓存是基于 SqlSessionFactory 的,作用范围较大。
缓存共享:一级缓存不共享,每个 SqlSession 有自己独立的缓存;二级缓存可以在多个 SqlSession 之间共享。
开启方式:一级缓存默认开启;二级缓存需要手动配置开启。
通过合理使用一级缓存和二级缓存,可以有效提高 MyBatis 应用的性能。但在使用缓存时,需要注意数据的一致性问题,避免出现脏数据。
总结
MyBatis 的一级缓存和二级缓存各有优缺点,在实际应用中需要根据具体的业务场景合理使用。一级缓存适用于在同一个 SqlSession 中多次执行相同查询的场景,而二级缓存适用于多个 SqlSession 之间共享缓存的场景。同时,需要注意缓存的使用可能会导致数据一致性问题,需要在业务逻辑中进行相应的处理。