mybatis源码简单阅读
环境准备
配置文件
数据库的不是关键,随便配一个就好
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="cn.cimoc.mapper"/>
</mappers>
</configuration>
db.properties
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&rewriteBatchedStatements=true&useSsl=false
username=root
password=root
driver=com.mysql.cj.jdbc.Driver
log4j.properties
# 设置根 #
log4j.rootLogger = debug, stdout
# 输出信息到控制台 #
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%p] %d{HH:mm:ss} %l: %n%m%n
Maven依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
测试类
@Slf4j
public class MyBatisTest {
SqlSession session1;
SqlSession session2;
@Before
public void sessionInit() {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
session1 = factory.openSession();
session2 = factory.openSession();
log.debug("sqlSession创建成功");
} catch (IOException e) {
log.error("加载配置文件出错:", e);
}
}
}
@Mapper
public interface UserInfoMapper {
UserInfo selectById(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.cimoc.mapper.UserInfoMapper">
<!--cache标签开启二级缓存-->
<cache/>
<select id="selectById" resultType="cn.cimoc.pojo.UserInfo">
SELECT * FROM user_info WHERE id = #{id}
</select>
</mapper>
构造 Factory
在测试类的 @Before
方法中,我们先读取了配置文件,并用这个配置创建了一个工厂,核心方法是 build
(12行)
点进去走到深处,可以看到以下代码,对配置文件进行了解析
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 首先解析XML配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser解析后获得Configuration对象,并创建一个DefaultSqlSessionFactory
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
创建 SqlSession
SqlSession 可以通过 SqlSessionFactory 的 openSession 方法创建
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
其中有非常多的参数类型,先从最简单的无参开始
在 构造 Factory中,创建的是一个 DefaultSqlSessionFactory,所以我们进入这个工厂类查看源码
1. 无参
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
继续进入下一个方法
该方法有三个参数,分别为执行器类型、事务隔离级别、是否自动提交,而由于上面我们使用的是无参 openSessioin,所以这三个参数都使用了默认值
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上面的两段代码都出现了一个 configuration 属性
这个 configuration 在 构造 Factory 中已经提到过了,是解析XML配置文件后生成的配置类
接下来开始看这一大段代码
- 第 4 行,从配置中获取了
Environment
对象,这个对象与建议数据库连接紧密相关,其内部包含一个 DataSource 对象 - 第 5 行,根据环境获取到事务工厂
- 第 6 行,使用工厂创建事务对象
- 第 7 行,创建一个执行器对象,用来执行 Sql
- 第 8 行,构造一个默认的 SqlSession 实现类并返回
这里研究的是无参 openSession,所以那些方法参数先不做解释。不过可以很清楚的看出,一个 Sqlsession 需要四样东西:
- 配置——Configuration
- 事务——Transaction
- 执行器——Executor
- 是否自动提交
其中,事务被装入了执行器,交由执行器管理
2. 开启自动提交
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
仅需要传入一个 true,就可以开启事务的自动提交
熟悉 JDBC 的话,你应该知道 Connection 中可以设置是否自动提交
void setAutoCommit(boolean autoCommit) throws SQLException;
mybatis 也只是起一个中介的作用,并不是自己来提交,而是帮我们开启 Connection 的自动提交:
SqlSession 的创建过程
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务,传入了autoCommit参数
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
事务的创建过程
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// 创建事务
return new JdbcTransaction(ds, level, autoCommit);
}
}
JdbcTransaction
我们看看里面是怎么创建数据库连接 Connection 的
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
// 从数据源对象获取连接
connection = dataSource.getConnection();
if (level != null) {
// 设置事务等级
connection.setTransactionIsolation(level.getLevel());
}
// 设置自动提交
setDesiredAutoCommit(autoCommit);
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
// 这里就成功设置了 JDBC 的自动提交
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
3. 指定事务等级
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
TransactionIsolationLevel
是一个枚举类,包含了数据库的事务等级
值 | 含义 |
---|---|
NONE | 表示不开启事务 |
READ_COMMITTED | 读已提交 |
READ_UNCOMMITTED | 读未提交 |
REPEATABLE_READ | 重复读 |
SERIALIZABLE | 序列化 |
SQL_SERVER_SNAPSHOT | SQL Server的非标准级别 |
4. 指定执行器
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
ExecutorType
是一个枚举类,包含了三种执行器:SIMPLE
、REUSE
、BATCH
执行器的创建是在下面这一行代码
final Executor executor = configuration.newExecutor(tx, execType);
点进去可以看到,内部是根据我们传递的枚举类型,创建对应的执行器,通常我们用的都是 SimpleExecutor
所以如果我们不显式指定的话,默认就是这个执行器,详见 Configuration
类的 getDefaultExecutorType
方法
如果在配置中开启了二级缓存,那么执行器还会被包装进一个 CachingExecutor
,关于二级缓存,我们后面再讲
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 创建执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 自定义插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
获取 Mapper
创建完 SqlSession 后,就可以通过它获取 Mapper 对象
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
往代码深处走,会进入一个 MapperRegistry
,而其中是通过代理工厂获取到示例的。
Mybatis 之所以可以只写接口就能调用方法,正是因为其使用了动态代理
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过代理工厂获取到示例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
继续往深处走,可以找到代理类 MapperProxy
学过动态代理相关知识的话,应该知道动态代理的核心方法就是 invoke
所以我们直接去看代理类的 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是继承自Object的方法,那么不进行干预
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 否则进入cachedInvoker方法,获取MapperMethodInvoker对象后再执行invoke方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
// 如果是接口的默认方法,那么依旧不干预
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 否则就是需要代理的方法了,接下来去查看PlainMethodInvoker类
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
PlainMethodInvoker
的逻辑很简单,这里直接就开始执行我们写在 Mapper 里的方法了
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
执行方法
execute 方法比较长,这里就不贴代码了,里面的逻辑清晰,根据方法的类型(增、删、改、查)执行 sqlSession 里对应的方法
其中增、删、改非常简短,毕竟这三类业务本身就不复杂。如果你继续深究,会发现 sqlSession 里面的 insert、delete 最终调用的都是 update
那么废话不多说,我们来看查询的逻辑
查询就复杂多了,毕竟查询的返回类型可以有很多种,所以 execute 中进行了大量的 if 判断,好吧其实也没几句
不同的返回类型,就有不同的查询代码,但其实最终都需要用到 sqlSession 的 selectList 方法,哪怕你只查一条数据,selectOne 最终也是调用 selectList
selectList
进过一堆调用链,找到最底下的代码,发现核心方法是执行器的 query
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 核心方法
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
到这里你会发现,有两个类实现了 query 方法,分别是 BaseExecutor
和 CachingExecutor
BaseExecutor
是一个抽象类,上面提到过的 SimpleExecutor
、ReuseExecutor
、BatchExecutor
都继承自它
而 CachingExecutor
则是他们的包装类
所以我们先看 BaseExecutor
里的 query 方法
1. BaseExecutor#query
在此之前,先简单介绍一下 MappedStatement
,简称 ms :这个类对应 xml 中的一整个 sql 标签(例如 <select>里面是你写的sql</select>
),每一个 Mapper 的 MappedStatement 都是单例的
继续进入 query 方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取sql信息
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// isFlushCacheRequired用来控制缓存的刷新,例如增删改导致了数据不同步
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// localCache是BaseExecutor的内置缓存,也就是mybatis的一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// list不为null,说明有缓存,处理输出参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 否则从数据库进行查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
2. CachingExecutor#query
CachingExecutor
是 BaseExecutor
的包装类,内部包含了真实的执行器对象:
private final Executor delegate;
在执行 query 方法时,会先从 CachingExecutor
的缓存中查询,没有结果才会去调用 delegate 的 query 方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取 ms 的Cache,上面说过 ms 是单例的,所以即便是不同的 sqlSession,这一行获取到的都是同一个 Cache,也就是mybatis的二级缓存
Cache cache = ms.getCache();
// 如果获取到了缓存对象,这里还没开始获取缓存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// tcm 是对 Cache 的一层包装,这里从tcm 中查询是否有缓存
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 没有缓存则调用 BaseExecutor 的 query 进行查询
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 查询完写入缓存,tcm 虽然是对 Cache 的包装,但是最终也会写入 Cache
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 没获取到缓存对象则调用 BaseExecutor 的 query
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上面提到了 Cache 的包装 tcm
我们进去看一看
TransactionalCacheManager
public class TransactionalCacheManager {
/**
* TransactionalCache 是 Cache 的实现类(也可以说是包装类),在这里都是临时的缓存,与 Cache 内部的数据无关
* 可以理解最底下的方法 getTransactionalCache
* 所以 tcm 的 get、put 都是在对临时缓存做操作
* 既然是临时缓存,那么是怎么实现共享的呢,这就要看 commit 方法了,这个 commit 可不是提交到数据库,而是提交到缓存
*/
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
/**
* commit 方法调用了所有 TransactionalCache 实例的 commit
* 接下来就去看 TransactionalCache
*/
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
TransactionalCache
先看构造方法
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
构造方法将一个 Cache 包装进来,从上面的 tcm 我们也可以看到,TransactionalCache 在 new 的时候传入的就是 ms 的 Cache 对象
那么接下来你应该能猜到数据是怎么写回 Cache 了吧
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
/**
* 这个方法将所有需要提交的缓存写入 Cache,这个 Cache 就是 ms 中的 Cache
* 而 ms 是单例的,所以不同 SqlSession 都能共享到这个二级缓存 Cache
*/
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
看到这里,你可能有个疑问,二级缓存需要我们手动 commit 吗,开启 autoCommit 是否会自动提交?
回想一下,数据库的自动提交是怎么实现的?其实是交由 jdbc 来实现的,mybatis 只是进行了一个判断而已
显然 jdbc 和缓存搭不上边,那么缓存怎么能自动提交呢,除非 mybatis 来实现这个机制,那么我们就去源码看看吧
我们来看 DefaultSqlSession 的 commit 方法
@Override
public void commit(boolean force) {
try {
// 调用执行器的 commit,并且传入了一个 boolean 参数,我们看看这个参数是怎么获取的
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
/**
* 满足以下任意条件
* 1. 没有开启自动提交并且进行过增/删/改
* 2. 手动强制提交
* 那么返回 true
* 否则返回 false
* 这么看起来可能比较抽象,结合 commit 可以知道,不考虑强制提交的情况:
* 如果开启了 autocommit,不强制提交的话,永远是 false,也就是不给提交
* 没开的话,即使我们想提交,如果没进行过增/删/改,也是 false
*/
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
那么这个 boolean 值在后面能起到什么作用呢
在 BaseExecutor#commit 中
// 如果为true才让jdbc进行提交
if (required) {
transaction.commit();
}
在 CachingExecutor#commit 中,委托给 BaseExecutor#commit
delegate.commit(required);
关键的地方来了
CachingExecutor#commit 总共两行代码,一行是上面那句代码,提交数据库,另一行就是关于缓存的提交了
tcm.commit();
注意:tcm 的提交并没有用到 require,require 是唯一和 autoCommit 有关的值
看到这,即使不看下去你也能知道了,autoCommit 都与 tcm 无关了,自然不能自动提交
不信的话,继续点进这个 commit 方法,就回到上面的 tcm 源码了,里面并没有任何与自动这个概念相关的代码
3. 总结
在进行查询的时候,如果开启了二级缓存,那么会先调用 CachingExecutor
如果二级缓存里没查到数据,才会调用 BaseExecutor,查一级缓存
如果一级缓存也没数据,才会对数据库进行查询,并且写入一级和二级缓存
flowchart TB A[查询]-->B["二级缓存(如果开启)"] B-->C[一级缓存] C-->D[数据库]
一级和二级代表里数据库的距离,距离越远,才会越先用到
而不是字面意义上的一级大于二级
测试
同个 SqlSession
不提交
@Test
public void test1() {
try {
UserInfoMapper mapper = session1.getMapper(UserInfoMapper.class);
mapper.selectById(1);
mapper.selectById(1);
} catch (Exception e) {
log.error("", e);
}
}
[DEBUG] 09:19:25 MyBatisTest.sessionInit(MyBatisTest.java:32):
sqlSession创建成功
[DEBUG] 09:19:25 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.0
[DEBUG] 09:19:25 org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137):
Opening JDBC Connection
[DEBUG] 09:19:27 org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434):
Created connection 1116094714.
[DEBUG] 09:19:27 org.apache.ibatis.transaction.jdbc.JdbcTransaction.setDesiredAutoCommit(JdbcTransaction.java:101):
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@428640fa]
[DEBUG] 09:19:27 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Preparing: SELECT * FROM user_info WHERE id = ?
[DEBUG] 09:19:27 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Parameters: 1(Integer)
[DEBUG] 09:19:27 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
<== Total: 1
[DEBUG] 09:19:27 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.0
Process finished with exit code 0
虽然测试代码中我们调用了两次 selectById ,但是只对数据库进行了一次查询
在最下面有一句
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.0
这是二级缓存的命中率,0 就代表二级缓存中没有这条数据
所以这里走的是一级缓存
提交
@Test
public void test2() {
try {
UserInfoMapper mapper = session1.getMapper(UserInfoMapper.class);
mapper.selectById(1);
session1.commit();
mapper.selectById(1);
} catch (Exception e) {
log.error("", e);
}
}
[DEBUG] 09:22:32 MyBatisTest.sessionInit(MyBatisTest.java:32):
sqlSession创建成功
[DEBUG] 09:22:32 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.0
[DEBUG] 09:22:32 org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137):
Opening JDBC Connection
[DEBUG] 09:22:34 org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434):
Created connection 1116094714.
[DEBUG] 09:22:34 org.apache.ibatis.transaction.jdbc.JdbcTransaction.setDesiredAutoCommit(JdbcTransaction.java:101):
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@428640fa]
[DEBUG] 09:22:34 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Preparing: SELECT * FROM user_info WHERE id = ?
[DEBUG] 09:22:34 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Parameters: 1(Integer)
[DEBUG] 09:22:34 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
<== Total: 1
[DEBUG] 09:22:34 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.5
Process finished with exit code 0
同样,在第一次查询提交后,第二次查询也没有走数据库
这次的命中率是 0.5,所以走的是二级缓存
现在已经证实了上面所说的,二级缓存需要手动提交,接下来测试不同 sqlSession 共享二级缓存
不同 SqlSession
@Test
public void test3() {
try {
UserInfoMapper mapper1 = session1.getMapper(UserInfoMapper.class);
UserInfoMapper mapper2 = session2.getMapper(UserInfoMapper.class);
mapper1.selectById(1);
session1.commit();
mapper2.selectById(1);
} catch (Exception e) {
log.error("", e);
}
}
[DEBUG] 09:24:58 MyBatisTest.sessionInit(MyBatisTest.java:32):
sqlSession创建成功
[DEBUG] 09:24:58 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.0
[DEBUG] 09:24:58 org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137):
Opening JDBC Connection
[DEBUG] 09:25:00 org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:434):
Created connection 1116094714.
[DEBUG] 09:25:00 org.apache.ibatis.transaction.jdbc.JdbcTransaction.setDesiredAutoCommit(JdbcTransaction.java:101):
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@428640fa]
[DEBUG] 09:25:00 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Preparing: SELECT * FROM user_info WHERE id = ?
[DEBUG] 09:25:00 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
==> Parameters: 1(Integer)
[DEBUG] 09:25:00 org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137):
<== Total: 1
[DEBUG] 09:25:00 org.apache.ibatis.cache.decorators.LoggingCache.getObject(LoggingCache.java:60):
Cache Hit Ratio [cn.cimoc.mapper.UserInfoMapper]: 0.5
Process finished with exit code 0
依旧是只走了一次数据库,说明二级缓存确实是可以跨 SqlSession 共享的