这篇文章主要从MapperProxy类出发,来纵向分析下Mybatis执行sql的流程。
MapperProxy类
MapperProxyFactory类只是MapperProxy的工厂类,所以主要看下MapperProxy就好了。MapperProxy是用jdk动态代理来实现对Mapper的代理,因此先看下他的invoke方法。不出意外,这个方法里就有我们最关心的代码,是如何执行的查询。
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方法。接下来看下。
MapperMethod类
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中。
@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类,接下来看下。
DefaultSqlSession类
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。
DefaultSqlSessionFactory类
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
在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());//执行sql前的准备操作,sql参数绑定、设置超时时间、异常处理等。
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler
StatementHandler的结构也和Executor差不多,也是用了模板模式。SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler这三个实现类分别对应了jdbc的三种Statement,他们的区别可以查看jdbc文档。我们就主要看PreparedStatementHandler。
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转化为需要的结果类型
}
statement就是我们最开始说的addMapper过程中保存的MappedStatement。这个类便是下一章要讲的。
可以看到最终执行的还是jdbc的PreparedStatement.execute()。而jdbc最终将结果保存在ResultSets中,需要resultSetHandler将其解析为最终类型。这个过程下下章要讲,涉及到addMapper过程中保存的ResultMap。
总结
sql执行的纵向过程如下图
当然每个类还有其他的代码,以后会对缓存,插件,日志等进行分析。