
通过前面几篇文章,Mybatis深入源码分析之SqlSessionFactoryBuilder源码分析,Mybatis深入源码分析之Mapper与接口绑定原理源码分析。我们对Mybatis源码也有了一定的了解。本篇文章,我们继续分析:SQLSession一级缓存原理。
一:invoke()方法源码分析
首先,当我们调用getMapper的时候,就会进入invoke()方法:
// 5.操作Mapper接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { MapperMethod mapperMethod = this.cachedMapperMethod(method); //将我们的代理方法缓存起来 return mapperMethod.execute(this.sqlSession, args); } }
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; }
缓存的目的:知道SQL语句对应的mapper接口中的方法
下面看看execute()方法
public Object execute(SqlSession sqlSession, Object[] args) { .... } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); //最终调用selectOne()方法 } ..... }
到了本篇文章的重点了,下面我们就开始分析selectOne()方法里面怎么实现的。
通过源码分析我们可以知道mapper.getUser()就是调用selectOne()方法。所以下面的两行代码是等效的,效果一样
UserEntity user = mapper.getUser(2); UserEntity o = sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser", 2);
进入selectOne()可知,SqlSession这个接口帮我们封装了CRUD的方法,便于我们操作。
最终执行DefaultSqlSession,因为前面new了DefaultSqlSessionFactory()
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
下面会执行这段代码
public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);//进入这里 }
所以,通过上述分析,我们知道这里就是执行DefaultSqlSession
public
底层还是查询所有的,但是还是取第一个,查询多个的化就会抛出异常。
public
public
通过上面的RowBounds,我们可知这个是分页用到的类。得出结论:Mybatis默认最多能查到Integer的最大值
下面进入这个方法
MappedStatement ms = this.configuration.getMappedStatement(statement);
MappedStatement存放的 SQL语句的配置内容,到query()方法的时候,有两个实现类的方法,我们应该走哪一个方法,下面我们开始debug源码分析这块
下面是debug到getMappedStatement()这块的MappedStatement
由此我们可知:MappedStatement是存放我们SQL的配置内容
我们可以知道了,executor为CachingExecutor,我们再来看看Executor接口下面有哪些Executor执行器
这里先透露下:CachingExecutor为二级缓存执行器,BaseExecutor为一级缓存执行器。
二:OpenSession()方法源码分析
下面我们来分析下原因:先回到我们的openSession()方法
// 4.获取Session SqlSession sqlSession = sqlSessionFactory.openSession();
public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); //看到没有,看到没用 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
Executor executor = this.configuration.newExecutor(tx, execType);
上述代码,在我们创建sqlSession的时候帮我们创建好了执行器,进入这个方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? this.defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Object 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 (this.cacheEnabled) { executor = new CachingExecutor((Executor)executor); //开启了缓存,则开启缓存执行器 } Executor executor = (Executor)this.interceptorChain.pluginAll(executor); return executor; }
由此我们知道开启了缓存执行器,executor传递的是简单执行器,我们就明白了,先有简单执行器(SimpleExecutor),判断是否开启了二级缓存,开启了就创建缓存执行器(CacheExecutor)
最后返回executor
下面我们总结下这几个执行器的作用:
public
进入这行代码:
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { return this.delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); }
由此我们知道,这里使用了多态的思想,没有SimpleExecutor执行器,说明走的是SimpleExecutor的父类BaseExecutor执行器的方法
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List
for(int i = 0; i < parameterMappings.size(); ++i) { ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { String propertyName = parameterMapping.getProperty(); Object value; if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = this.configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } if (this.configuration.getEnvironment() != null) { cacheKey.update(this.configuration.getEnvironment().getId()); } return cacheKey; //返回了cacheKey为:-978696406:1452564227:com.mayikt.mapper.UserMapper.getUser:0:2147483647:select \* from user where id=?:2:development }
}
通过上面得到了缓存key,得到key后,再调用query方法去查询:
public
public
通过上述代码:我们可知,如果二级缓存没有,走简单执行器
二级缓存没用,则进入else分支:
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
思考:为什么CachingExecutor要找SimpleExecutor创建缓存key?
答案是为了复用,实现缓存key代码复用。mybatis缓存控制:先查找二级缓存(需要自己配置),二级缓存没有的情况下,再去找一级缓存(默认都有)
一级缓存是绝对有的,二级缓存(硬盘、Redis、EHCache)是可以没有的(表示没用使用,配置存储介质,就不会缓存,相当于空壳的)。
public
执行从HashMap中查找缓存
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
public Object getObject(Object key) { return this.cache.get(key); }
所以,我们知道了PerpetualCache指的是我们的一级缓存,一级缓存指的是本地缓存,存放在内存中的。使用Map集合存放的。
我们知道,我们一级缓存现在也没有,所以会先往数据库中查询一次
private
this.localCache.putObject(key, list); //再存到缓存中 if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list;
}
最后回到前面:
protected int queryStack = 0;
这里queryStack为全局的,存在线程安全问题。
三:Mybatis缓存源码分析
下面我们开启日志,来验证下本地一级缓存作用:在Mybatis配置文件加入下面配置,开启打印日志:
结果:说明只发出一条SQL语句去数据库查询一次,第一次去查询数据库,将查询结果集存放在缓存中,第二次查询就直接走本地缓存查询。
加入在中间加入一条update语句,结果是怎样?
//中间执行一条update语句 sqlSession.update("com.mayikt.mapper.UserMapper.updateUser",1);
结果:发出了三条SQL语句。
为什么是三条SQL语句?不是有缓存吗?
我们的注意:sqlSession缓存为了防止脏数据,在我们进行增加、修改、删除的时候,都会清除一级缓存。下面我们看下源码。
public int update(String statement, Object parameter) { int var4; try { this.dirty = true; MappedStatement ms = this.configuration.getMappedStatement(statement); var4 = this.executor.update(ms, this.wrapCollection(parameter)); } catch (Exception var8) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8); } finally { ErrorContext.instance().reset(); } return var4; }
var4 = this.executor.update(ms, this.wrapCollection(parameter));
public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (this.closed) { throw new ExecutorException("Executor was closed."); } else { this.clearLocalCache(); //清除所有一级缓存 return this.doUpdate(ms, parameter); } }
public void clearLocalCache() { if (!this.closed) { this.localCache.clear(); this.localOutputParameterCache.clear(); } }
protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache;
我们就明白了,PerpetualCache都会被清除掉了。
四:最后,我们来分析下:一级缓存存在哪些问题?
如上图,会存在脏读问题。
所有我们怎么解决呢?集群的情况下,我们可以不去使用一级缓存,是不是可以直接关闭一级缓存?
答案是:不可用直接关闭一级缓存,Mybatis默认走SimpleExecutor,不能直接关闭一级缓存。
那么如何去关闭一级缓存?
方案1:案例演示:
System.out.println("第一次调用...."); Map randomMap=new HashMap(); randomMap.put("randomString",new Random().nextInt()); randomMap.put("id",1); UserEntity o = sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser", randomMap); System.out.println(o.getName()); //中间执行一条update语句 //sqlSession.update("com.mayikt.mapper.UserMapper.updateUser",1); System.out.println("第二次调用...."); randomMap.put("randomString",new Random().nextInt()); UserEntity o2 = sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser", randomMap); System.out.println(o2.getName());
输出结果
五:总结
SQLSession一级缓存原理分析流程
后面解决方案我们下次再分析
本文参考
蚂蚁课堂