执行Map -- maperProxy#
上一个模块我们知道了Mybatis为我们创建出来了mapper接口的代理对象,那当我们获取到这个代理对象之后执行它的mapper.findAll();
实际上触发的是代理对象的invoke()
方法
所以说,接着看上面的MapperProxy
的invoke()
方法:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
上面的方法我们关注两个地方,第一个地方就是final MapperMethod mapperMethod = cachedMapperMethod(method);
,见名知意: 缓存MapperMethod
第一个问题: 这个MapperMethod
是什么? 它其实是Mybatis为sql命令+方法全限定名设计的封装类
*/ public class MapperMethod { private final SqlCommand command; private final MethodSignature method;
说白了,就是想为MapperProxy保存一份map格式的信息,key=方法全名 value=MapperMethod(command,method),存放在MapperProxy的属性methodCache中
comand -> "name=com.changwu.dao.IUserDao,fingAll"
method -> "result= public abstract java.util.List.com.changwu.dao.IUserDao.findAll()"
为什么要给这个MapperProxy保存这里呢? 很简单,它是mapper的代理对象啊,程序员使用的就是他,在这里留一份method的副本,再用的话多方便?
完成缓存后,继续看它如何执行方法:,跟进mapperMethod.execute(sqlSession, args)
因为我使用的是@Select("select * from user")
,所以一定进入下面的result = executeForMany(sqlSession, args);
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 SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); ... }
所以我们继续关注这个 executeForMany(sqlSession, args);
方法,看他的第一个参数是sqlSession
,也就是我们的DefaultSqlSession
,他里面存在两大重要对象: 1是configuration 配置对象, 2是Executor 执行器对象
继续跟进:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { result = sqlSession.selectList(command.getName(), param); }
我们在继续跟进这个selectList
方法之前,先看看这个command.getName()是啥? 其实我们上面的代码追踪中有记录: 就是name=com.changwu.dao.IUserDao,fingAll
继续跟进去到下面的方法:
这个方法就比较有趣了,首先来说,下面的入参都是什么
statement = "com.changwu.dao.IUserDao.findAll"
parameter=null
第二个问题:MappedStatement
是什么? 它是一个对象,维护了很多很多的配置信息,但是我们关心它里面的两条信息,这其实可以理解成一种方法与sql之间的映射,如下图
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); 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(); } }
前面阅读时,我们知道Mybatis为我们创建的默认的执行器 Executor是CachingExecutor
,如下图
继续跟进,主要做了下面三件事, 获取到绑定的sql,然后调用SimpleExecutor
缓存key,然后继续执行query()
方法
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
接着调用SimpleExecutor
的query()
方法,然后我们关注SimpleExecutor
的doQuery()
方法,源码如下
@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()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
我们注意到.在SimpleExcutor中创建的了一个 XXXStatementHandler这样一个处理器, 所以我们的只管感觉就是,sql真正执行者其实并不是Executor,而是Executor会为每一条sql的执行重新new 一个 StatementHandler ,由这个handler去具体的执行sql
关于这个StatementHandler到底是是何方神圣? 暂时了解它是Mybatis定义的一个规范接口,定义了如下功能即可
public interface StatementHandler { // sql预编译, 构建statement对象 Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; // 对prepare方法构建的预编译的sql进行参数的设置 void parameterize(Statement statement) throws SQLException; // 批量处理器 void batch(Statement statement) throws SQLException; // create update delete int update(Statement statement) throws SQLException; // select <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(Statement statement) throws SQLException; // 获取sql的封装对象 BoundSql getBoundSql(); // 获取参数处理对象 ParameterHandler getParameterHandler(); }
了解了这个StatementHandler是什么,下一个问题就是当前我们创建的默认的statement是谁呢? routingStatementHandler
如下图
创建preparedStatement#
马上马就发生了一件悄无声息的大事!!!根据现有的sql等信息,构建 PreparedStatement,我们关注这个prepareStatement(handler, ms.getStatementLog());
方法,通过调试我们得知,prepareStatement()
是RoutingStatementHandler
的抽象方法,被PreparedStatementHandler
重写了,所以我们去看它如何重写的,如下:
@Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { statement = instantiateStatement(connection);
我们关注这个instantiateStatement()
方法, 并且进入它的connection.prepareStatement(sql);
方法,如下图:
纯洁的微笑... 见到了原生JDK, jdbc的亲人...
创建完事这个 preparedStatement,下一步总该执行了吧...绕这么多圈...
我们回到上面代码中的return handler.query(stmt, resultHandler);
准备执行,此时第一个参数就是我们的刚创建出来的PreparedStatement, 回想一下,上面创建的这个默认的statement中的代表是PreparedStatementHandler
,所以,我们进入到这个StatementHandler的实现类RountingStatementHandler
中,看他的query()
方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.query(statement, resultHandler); }
调用RountingStatementHandler
中维护的代表的StatementHandler也就是PreparedStatementHandler
的query()
方法,顺势跟进去,最终会通过反射执行jdbc操作,如图, 我圈出来的对象就是我们上面创建出来的preparedStatement
提交事务#
跟进conmit()
方法,分成两步
- 清空缓存
- 提交事务
清空缓存是在CachingExecutor
中调用了SimpleExecutor
简单执行器的方法commit(required)
@Override public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit(); }
接在SimpleExecutor
的父类BaseExecutor
中完成
@Override public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } }
提交事务的操作在tcm.commit();
中完成