前语

今天突然看到有关MyBatis缓存的讨论引发思考,发现这部分内容有些模糊了,所以来整理一下MyBatis一级缓存和二级缓存的内容,依据代码来加深了解一下MyBatis的缓存。

简介

MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。

MyBatis 是一款优秀的持久层结构,它支撑定制化 SQL、存储进程以及高级映射。MyBatis 防止了简直一切的 JDBC 代码和手动设置参数以及获取成果集。MyBatis 能够运用简略的 XML 或注解来装备和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java目标)映射成数据库中的记载。

MyBatis供给了两种缓存,分别是一级缓存二级缓存

  • 一级缓存是SqlSession等级的缓存。 缓存是存在一个HashMap中的,HashMap是会话目标私有的。一级缓存默许是敞开状况。
  • 二级缓存是namespace等级的缓存。 二级缓存默许完成也是基于本地缓存的,也能够整合到一些缓存中间件中。二级缓存默许不敞开。

实战

一、 SpringBoot中运用MyBatis缓存

代码环境

  • SpringBoot: 2.6.3
  • MyBatis: 2.3.0

一级缓存

一级缓存是默许敞开的,可是项目中大多是用不到的,由于MyBatis不在业务中履行Sql时,每次都会创建新的SqlSession,即便在业务中履行Sql也要在业务中履行两次相同Sql第二条Sql才会用到一级缓存,两次Sql之间还不能有更新等清除缓存的操作,因而缓存简直不会射中。

一级缓存验证

预备一个简略的sql句子,查询回来UUID。

<select id="handleSql" resultType="java.lang.String">
    SELECT UUID()
</select>

预备一个不加业务的办法。

@Service
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final IMyBatisCacheTestMapper iMyBatisCacheTestMapper;
    @Autowired
    public IMyBatisCacheTestServiceImpl(IMyBatisCacheTestMapper iMyBatisCacheTestMapper) {
        this.iMyBatisCacheTestMapper = iMyBatisCacheTestMapper;
    }
    @Override
    public void testCache() {
        System.out.println("第一次回来成果" + iMyBatisCacheTestMapper.handleSql());
        System.out.println("第2次回来成果" + iMyBatisCacheTestMapper.handleSql());
    }
}

看一下履行成果。

这儿敞开了Mybatis的日志。

敞开办法: mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17dad32f] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1886186662 wrapping com.mysql.cj.jdbc.ConnectionImpl@7f6329cb] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: 8f2cbcd5-8da1-11ed-93cf-fa163e2220b5
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17dad32f]
第一次回来成果8f2cbcd5-8da1-11ed-93cf-fa163e2220b5
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e3df614] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1702481339 wrapping com.mysql.cj.jdbc.ConnectionImpl@7f6329cb] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: 8f35e5b9-8da1-11ed-93cf-fa163e2220b5
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e3df614]
第2次回来成果8f35e5b9-8da1-11ed-93cf-fa163e2220b5

依据履行成果看出每次履行时都创建了一个新的SqlSession,每次都履行了Sql句子,回来了不同的成果。

接下来,将咱们的办法加上业务。

@Service
@Transactional
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final IMyBatisCacheTestMapper iMyBatisCacheTestMapper;
    @Autowired
    public IMyBatisCacheTestServiceImpl(IMyBatisCacheTestMapper iMyBatisCacheTestMapper) {
        this.iMyBatisCacheTestMapper = iMyBatisCacheTestMapper;
    }
    @Override
    public void testCache() {
        System.out.println("第一次回来成果" + iMyBatisCacheTestMapper.handleSql());
        System.out.println("第2次回来成果" + iMyBatisCacheTestMapper.handleSql());
    }
}

然后咱们来看一下履行成果。

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]
JDBC Connection [HikariProxyConnection@857732012 wrapping com.mysql.cj.jdbc.ConnectionImpl@2e5e6fc4] will be managed by Spring
==>  Preparing: SELECT UUID()
==> Parameters: 
<==    Columns: UUID()
<==        Row: fdc5b34c-8da2-11ed-93cf-fa163e2220b5
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]
第一次回来成果fdc5b34c-8da2-11ed-93cf-fa163e2220b5
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]
第2次回来成果fdc5b34c-8da2-11ed-93cf-fa163e2220b5
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61bb1e4d]

依据履行成果看出创建了一次SqlSession且只履行了一次Sql,第2次查询时一级缓存收效,两次回来了相同的成果。

二级缓存

二级缓存默许不敞开,需要咱们自己来启用。

敞开二级缓存:mybatis.configuration.cache-enabled=true

敞开二级缓存后,还要在XML中标签<cache/>标签,这个标签有多个参数来设置二级缓存

二级缓存验证

预备上文中的简略Sql句子。
运用上文中不加业务的办法。
履行办法检查履行成果。

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3520963d] was not registered for synchronization because synchronization is not active
Cache Hit Ratio [com.shuaijie.dao.mybatisCache.IMyBatisCacheTestMapper]: 0.0
JDBC Connection [HikariProxyConnection@137685382 wrapping com.mysql.cj.jdbc.ConnectionImpl@6cae2e4d] will not be managed by Spring
==>  Preparing: SELECT UUID()
==> Parameters: 
<==    Columns: UUID()
<==        Row: 45c584eb-8da5-11ed-93cf-fa163e2220b5
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3520963d]
第一次回来成果45c584eb-8da5-11ed-93cf-fa163e2220b5
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f0bab7e] was not registered for synchronization because synchronization is not active
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.shuaijie.dao.mybatisCache.IMyBatisCacheTestMapper]: 0.5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f0bab7e]
第2次回来成果45c584eb-8da5-11ed-93cf-fa163e2220b5

能够看到创建了两个SqlSession,可是第二个句子并没有去数据库履行,二级缓存收效。

二 运用SqlSession目标来了解MyBatis缓存

一级缓存

仍是用那个简略Sql句子。

1、运用同一个SqlSession履行
@Service
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final SqlSessionFactory sqlSessionFactory;
    @Autowired
    public IMyBatisCacheTestServiceImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Override
    public void testCache() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IMyBatisCacheTestMapper mapper = sqlSession.getMapper(IMyBatisCacheTestMapper.class);
        System.out.println("第一次回来成果" + mapper.handleSql());
        System.out.println("第2次回来成果" + mapper.handleSql());
    }
}

检查回来成果


JDBC Connection [HikariProxyConnection@1904652802 wrapping com.mysql.cj.jdbc.ConnectionImpl@6b649efa] will not be managed by Spring
==>  Preparing: SELECT UUID()
==> Parameters: 
<==    Columns: UUID()
<==        Row: e7981e9a-8e2e-11ed-93cf-fa163e2220b5
<==      Total: 1
第一次回来成果e7981e9a-8e2e-11ed-93cf-fa163e2220b5
第一次回来成果e7981e9a-8e2e-11ed-93cf-fa163e2220b5

依据回来成果能够看出一级缓存收效。

2、运用同一个SqlSession,两个查询之间清除缓存
@Service
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final SqlSessionFactory sqlSessionFactory;
    @Autowired
    public IMyBatisCacheTestServiceImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Override
    public void testCache() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IMyBatisCacheTestMapper mapper = sqlSession.getMapper(IMyBatisCacheTestMapper.class);
        System.out.println("第一次回来成果" + mapper.handleSql());
        // 清除sqlSession本地缓存
        sqlSession.clearCache();
        System.out.println("第2次回来成果" + mapper.handleSql());
    }
}

检查回来成果

JDBC Connection [HikariProxyConnection@2139431292 wrapping com.mysql.cj.jdbc.ConnectionImpl@1fd7a37] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: e59cdfd0-8e2f-11ed-93cf-fa163e2220b5
<== Total: 1
第一次回来成果e59cdfd0-8e2f-11ed-93cf-fa163e2220b5
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: e5a80513-8e2f-11ed-93cf-fa163e2220b5
<== Total: 1
第一次回来成果e5a80513-8e2f-11ed-93cf-fa163e2220b5

依据回来成果能够看出履行了两次Sql,原因是一级缓存被清除。

3、运用两个SqlSession
@Service
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final SqlSessionFactory sqlSessionFactory;
    @Autowired
    public IMyBatisCacheTestServiceImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Override
    public void testCache() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        IMyBatisCacheTestMapper mapper1 = sqlSession1.getMapper(IMyBatisCacheTestMapper.class);
        IMyBatisCacheTestMapper mapper2 = sqlSession2.getMapper(IMyBatisCacheTestMapper.class);
        System.out.println("第一次回来成果" + mapper1.handleSql());
        System.out.println("第2次回来成果" + mapper2.handleSql());
    }
}

检查回来成果

JDBC Connection [HikariProxyConnection@95552255 wrapping com.mysql.cj.jdbc.ConnectionImpl@58a84a12] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: 098cfeeb-8e31-11ed-93cf-fa163e2220b5
<== Total: 1
第一次会话回来成果098cfeeb-8e31-11ed-93cf-fa163e2220b5
JDBC Connection [HikariProxyConnection@1229143192 wrapping com.mysql.cj.jdbc.ConnectionImpl@b5c6a30] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: 09acdd70-8e31-11ed-93cf-fa163e2220b5
<== Total: 1
第一次会话回来成果09acdd70-8e31-11ed-93cf-fa163e2220b5

依据成果能够看出跨SqlSession一级缓存不收效。

二级缓存

如果想禁用某个查询的二级缓存的时候,能够在这个查询的<Select>标签加上
userCache=”false”特点, <Select userCache=”false”/>

1、运用两个SqlSession

按照上文办法敞开二级缓存。
同样是用那个简略Sql句子。

@Service
public class IMyBatisCacheTestServiceImpl implements IMyBatisCacheTestService {
    private final SqlSessionFactory sqlSessionFactory;
    @Autowired
    public IMyBatisCacheTestServiceImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    @Override
    public void testCache() {
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        IMyBatisCacheTestMapper mapper = sqlSession.getMapper(IMyBatisCacheTestMapper.class);
        System.out.println("第一次回来成果" + mapper.handleSql());
        sqlSession.close();
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        IMyBatisCacheTestMapper mapper2 = sqlSession2.getMapper(IMyBatisCacheTestMapper.class);
        System.out.println("第2次回来成果" + mapper2.handleSql());
    }
}

这儿强调一下sqlSession.close()这一行代码。它的作用是封闭SqlSession,一起会将二级缓存添加到Map。如果没有这一行代码,示例代码的二级缓存是不会收效的。

检查回来成果

JDBC Connection [HikariProxyConnection@876945112 wrapping com.mysql.cj.jdbc.ConnectionImpl@c1050f2] will not be managed by Spring
==> Preparing: SELECT UUID()
==> Parameters:
<== Columns: UUID()
<== Row: b66f4fe2-8e3c-11ed-93cf-fa163e2220b5
<== Total: 1
第一次会话回来成果b66f4fe2-8e3c-11ed-93cf-fa163e2220b5
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.shuaijie.dao.mybatisCache.IMyBatisCacheTestMapper]: 0.5
第一次会话回来成果b66f4fe2-8e3c-11ed-93cf-fa163e2220b5

依据成果能够看出,MyBatis二级缓存收效。

总结

  • MyBatis一级缓存是SqlSession等级的缓存,二级缓存是namespace等级的缓存。
  • 一级缓存是默许敞开的,二级缓存需要手动装备敞开。
  • 一级缓存不能射中场景:sqlSession不同;查询条件不同;两次查询之间有一级缓存清除操作等等。
  • 二级缓存不能射中场景:sqlSession未封闭(二级缓存还未添加);查询条件不同;namespace不同;两次查询之间有二级缓存清除操作等等。
  • 缓存会出问题场景:单表产生缓存,数据被另一个当地修正(跨JVM,跨namespace,或者衔接工具修正等等);多表操作产生缓存某个表被另一个当地修正等等。
  • 尽量防止运用二级缓存;一级缓存某些特殊场景下也要封闭。