这篇文章主要从MapperProxy类出发,来纵向分析下Mybatis执行sql的流程。java
MapperProxyFactory类只是MapperProxy的工厂类,因此主要看下MapperProxy就行了。MapperProxy是用jdk动态代理来实现对Mapper的代理,所以先看下他的invoke方法。不出意外,这个方法里就有咱们最关心的代码,是如何执行的查询。sql
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args);//Object定义的方法,则透传给Mapper } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args);//java8接口的默认方法和静态方法,代理方法有所不一样。 } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method);//先从缓存中取,没有则根据method新建MapperMethod类 return mapperMethod.execute(sqlSession, args);//执行sql }
能够看到真正执行Sql的是MapperMethod的execute方法。接下来看下。api
MapperMethod的execute方法主要就是判断sql语句是update,insert,delete仍是select,来执行不一样的逻辑。update,insert,delete的逻辑基本相同,都是执行sql语句。select较为复杂,须要将返回结果映射为java bean。返回多个结果时还要分为集合,map,Cursor三种状况。所以,咱们就只看select,而且只考虑将结果集保存在集合的状况。实如今executeForMany方法中。缓存
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args);//获取参数Map if (method.hasRowBounds()) {//逻辑分页,通常不多用 RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } if (!method.getReturnType().isAssignableFrom(result.getClass())) {//若结果类型不符合方法的返回类型,则转化为对应的结合类型 if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
method这个属性是MethodSignature类,是MapperMethod的内部静态类。主要封装了下处理Method的方法。convertArgsToSqlCommandParam这个方法就是当有多个参数时,将此次方法调用的参数和参数名保存在一个map中。session
@Select("select * from product where id > #{id} and price > #{price}") ArrayList<Product> findAll(@Param("id") long id, @Param("price") double price);
例如上面这个方法,代理类在执行这个方法时,执行到Object param = method.convertArgsToSqlCommandParam(args)这句时,若入参为1,20。则param的值为Map,键值对为:
{
"id":"1",
"price":"20",
"param1":"1",
"price":"1"
}
有了这个Map,则方便将Sql中的#{id},#{price}替换为"1"和"20"。
command这个属性是SqlCommand类,是MapperMethod的内部静态类。主要保存了sql数据,封装了一些工具方法。 getName方法,则是获得"${Mapper包路径}.${Mapper类名}.${执行方法名}",例如,若包名是mapper,上面的方法则是"mapper.ProductMapper.findAll"。这个字符串有什么用呢?
上一篇说过addMapper的过程当中,主要存储下了三份数据,其中sql信息存储在MappedStatement类中,而全部的MappedStatement存储在mappedStatements这个Map中,而他的key也是这个字符串。
因此此时,咱们能够找到这个方法对应的MappedStatement。即获得addMapper时解析Sql的相关数据。 能够看到最终执行Sql是SqlSession的selectList方法。mybatis默认使用的SqlSession是DefaultSqlSession类,接下来看下。mybatis
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement);//经过key找到对应的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(); } }
能够看到,ms即是刚刚说到的MappedStatement。而最终执行查询的是executor。executor是DefaultSqlSession的一个属性,是在构造方法中传来的。DefaultSqlSession是在哪里构造的呢?就是在第一篇中demo中调用到的,SqlSessionFactory的OpenSession方法。mybatis默认的SqlSessionFactory是DefaultSqlSessionFactory。app
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment();//保存了transactionFactory和DataSource 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);//一个session对应一个Executor,一级缓存存在Executor里,这也就是为何一级缓存是Session级别的。 } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
能够看到主要分了三步:事务工厂实例化事务对象tx,实例化执行器executor,传入刚刚的tx,实例化DefaultSqlSession,传入刚刚的executor。
事务类主要考虑JdbcTransaction,这个类其实就是稍微封装了下Jdbc的commit,rollback,setAutoCommit等操做。
接下来就到了我们要找的executor了。Executor是个接口,mybatis封装了三种实现类,BatchExecutor,ReuseExecutor,SimpleExecutor。configuration.newExecutor(tx, execType),这句代码就是根据类型来实例化不一样的Executor。以及后续要说的缓存和插件扩展。
继续回到刚刚的DefaultSqlSession类,里面主要用到了Executor的query方法。接下来看下Executor吧。工具
在Executor中,咱们能看到装饰者模式和模板模式的影子。
BaseExecutor抽象类,实现了query,commit,rollback等基本方法。但doQuery等是抽象方法,由实现类BatchExecutor,ReuseExecutor,SimpleExecutor去具体实现。这种模板模式,耦合代码较少,值得学习。
CachingExecutor是个装饰类,为类赋予缓存功能。不须要为BatchExecutor,ReuseExecutor,SimpleExecutor写三个对应的缓存类。
具体的查询代码在BatchExecutor,ReuseExecutor,SimpleExecutor的doQuery方法中,咱们就来看下最简单的SimpleExecutor的doQuery方法吧。学习
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);//实例化StatementHandler stmt = prepareStatement(handler, ms.getStatementLog());//获得jdbc中的 Statement,并进行sql参数绑定、设置超时时间、异常处理等。 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
StatementHandler的结构也和Executor差很少,也是用了模板模式。SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler这三个实现类分别对应了jdbc的三种Statement,他们的区别能够查看jdbc文档。咱们就主要看PreparedStatementHandler。fetch
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement;//将statement强制转换为jdbc中的PreparedStatement ps.execute();//执行sql return resultSetHandler.<E> handleResultSets(ps);//将jdbc的ResultSets转化为须要的结果类型 }
能够看到最终执行的仍是jdbc的PreparedStatement.execute()。而jdbc最终将结果保存在ResultSets中,须要resultSetHandler将其解析为最终类型。这个过程下篇文章要讲,涉及到addMapper过程当中保存的ResultMap。
sql执行的纵向过程以下图
固然每一个类还有其余的代码,之后会对缓存,插件,日志等进行分析。有的细节也并无去讨论,主要是由于这些代码并不算难,但又比较繁琐。例如sql参数绑定,已经将参数保存在了map中,剩下就是字符串匹配而后用jdbc的api进行参数绑定。