MyBatis 源码分析-03 - MyBatis 方法执行流程分析
限于篇幅, 本文仅分析 mapper.selectBlog(1)这一过程,不包括 mapperproxy 代理对象生成, 以及方法参数解析, 以及resultset 解析的过程.
ps: 图片来自聊聊MyBatis 缓存这件事
Version :
MyBatis : 3.5.5
项目地址 : MyBatis-source-learn
- PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)
流程:
userMapper.selectBlog(1);
MapperProxy.invoke();
MapperMethod.execute()
DefaultSqlSession.selectOne();
DefaultSqlSession.SelectList();
cachingExecutor.query(); // 二级缓存 CachingExecutor.Cache
baseExecutor.query();// 一级缓存 SimpleExecutor.localCache(Map)
baseExecutor.queryFromDataBase();
simpleExecutor.doQuery();
测试代码:
ps :其他代码请自己去 MyBatis-source-learn查看
测试类:
- CreateSqlSession.java
public class CreateSqlSession {
public static void main(String[] args) {
// 1. 连接数据库,配置基本参数
DataSource dataSource = getDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
// 2. 处理XXXMapper, 生成SqlSource(即处理xml 以及注解生成的SQL语句 与PreparedStatement绑定)
configuration.addMapper(BlogMapper.class);
// 3. 获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取mapper,执行方法
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
for (int i = 0; i < 10; i++) {
Blog blog = mapper.selectBlog(1);
System.out.println("i: "+blog);
}
}
只分析 mapper.selectBlog(1)这个方法执行流程, 必要内容会给予补充说明.
**PS: MyBatis 默认查询 顺序是 二级缓存 (CachedExecutor.cache) -> 一级缓存(BaseExecutor.localCache) -> 数据库. **
源码分析:
MapperProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {// 如果是object方法 ,直接 invoke
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {//返回mappedMethod(缓存中有,就在缓存中拿,缓存中没有, 就创建) 并执行mappedMethod,即mapperMethod.execute(sqlSession, args);
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);// 这个步骤可以先不看,如果想看不阻拦,注意方法调用栈即可看懂.
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
MappedMethod.java
根据SQL语句类型 ,执行相应的方法 ,对于mapper.selectBlog(1) 来说, 即select代码块.不想看的话,可以直接跳过.
- 结论:对于mapper.selectBlog(1) , 会调用 DefaultSqlSession.selectOne(args)方法 ,然后在调用DefaultSqlSession.selectList(args);
//switch: 根据SQL语句类型 ,执行相应的方法 基本步骤构建statement并处理方法参数 以生成SQL语句, 执行SQL命令,映射结果集
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {// 处理无返回值
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) { //处理多个返回值
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) { // 返回值类型为map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {// 处理游标
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);//处理方法参数
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
DefaultsqlSession.java
结论:对于mapper.selectBlog(1) , 会调用 DefaultSqlSession.selectOne(args)方法 ,然后在调用DefaultSqlSession.selectList(args), 然后交给executor 执行query方法.
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);// 获取mappedStatement
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
补充说明: Executor 是在 sqlSessionFactory.openSession() 执行初始化时创建的. 即executor = CachingExecutor.
创建executor 方法是:Configuration.newExecutor(Transaction transaction, ExecutorType executorType) ; 有需要的自己看.
public class CachingExecutor implements Executor {
private final Executor delegate;// simpleExecutor extends baseExecutor
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
CachingExecutor.java
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);// 获取sql
//用于一级缓存比较是否相同:具体查看CacheKey.equals()方法
// 即statementid(class 全路径+ 方法名)+ offset()+ limmt + sql 语句+ params
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();//二级缓存,没有就出一级缓存找
if (cache != null) {// 二级缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {// SimpleExecutor 去查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor.java
- BaseExecutor.query(args)
@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.");
} //是否刷新缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();// 一级缓存
}
List<E> list;
try {
queryStack++; // 一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {// 加缓存
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);//处理存储过程 callabelStatement
} else {//查数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();//处理单条SQL命令的缓存 (ps: 默认返回的是session级别的缓存)
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);// 查询
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list); //加缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter); // 处理存储过程
}
return list;
}
SimpleExecutor.java
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());//对statement 进行处理,绑定参数到sql中
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);//利用事务获取connection
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);// 处理参数 绑定 sql - java type
return stmt;
}
PreParedStatement.java
PS: 真正干活的PreParedStaementLogger.java , 依旧是委托模式, 不过有log日志生成.
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();//与数据库交互, 执行操作
return resultSetHandler.handleResultSets(ps);// 处理结果集 ,将结果集与 resultmap映射,返回list对象这一步有点复杂,
}
PreParedStatementLogger.java
人话解释: 对方法进行分类, 对应执行 PreparedStatement 对应的方法.
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {// 处理obj 方法
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {// 处理PreParedStatement.execute()|| preparedStatement.executeQuery()等 方法
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {//preparedStatement.executeQuery()
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);//执行preparedStatement.execute() 方法
}
} else if (SET_METHODS.contains(method.getName())) {// 处理preparedStatement.setXXX()方法
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
} else if ("getResultSet".equals(method.getName())) {//处理结果集PreParedStatem.getResultSet()
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else if ("getUpdateCount".equals(method.getName())) {
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);// 其他方法
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
总结:
Mybatis 默认查询方法的执行流程是: 二级缓存 -> 一级缓存 -> DB
一级缓存 LocalCache: localCacheScope= session( default), 建议修改为 statement ,session 存在脏读现象
二级缓存 Cache: cacheEnabled=true(default), 对应xxxMapper.xml 添加
即可使用,使用二级缓存,可以有效避免在单表查询时出现的脏读现象,但是在多表查询时,极大可能出现脏数据,存在缺陷.
参考:
- https://tech.meituan.com/2018/01/19/mybatis-cache.html