前言
本文衔接上文(Mapper接口篇),继续分析Mapper相关的源码
进入源码
上文中我们分析到了MapperMethod的execute方法
MapperMethod.java
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()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
根据上文示例,我们的代码会执行到executeForMany(sqlSession, args);这个分支,下面分析这个方法做了什么工作
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
// 1. 首先将传入的Object[] args转为上文分析的合适的参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
// 1.1. 该调用方法有行限制参数RowBounds
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 1.2. 无行限制参数RowBounds
result = sqlSession.selectList(command.getName(), param);
}
// 对于返回值为Collections和Array的支持,因为SqlSession接口定义的返回值全部都是List,如果我们的Mapper接口中定义了Array类型的返回值,就需要进行转换
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
method.convertArgsToSqlCommandParam(args);
这里在真正调用SqlSession的相关方法前会执行MethodSignature的convertArgsToSqlCommandParam(Object[] args)
方法
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
就是调用了内部持有的paramNameResolver
对象的getNamedParams
方法,将从代理Mapper对象方法调用拿到的Object[] args
参数转为合适的名称参数对象
以便接下来的sqlSession相关方法执行使用
接下来,我们将目光转向Mybatis中大名鼎鼎的Sqlsession
SqlSession.java
Mybatis暴露的顶层Api接口,用于操作Sql的执行,以屏蔽掉底层JDBC操作数据库的繁琐,让我们可以直接调用其申明的各种方便的方法如,查询/新增/更新/删除/提交事务/回滚事务/获取Mapper代理对象等
下面看下SqlSession
的接口定义
public interface SqlSession extends Closeable {
// 查询单个结果
T selectOne(String statement);
T selectOne(String statement, Object parameter);
// 查询List结果,可指定RowBounds参数
List selectList(String statement);
List selectList(String statement, Object parameter);
List selectList(String statement, Object parameter, RowBounds rowBounds);
// 返回Map的查询方式
Map selectMap(String statement, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
// 返回懒处理迭代器Cursor游标的查询,注意这种方式的查询非常适合与数百万数据项的查询(不会直接加载进内存中)
Cursor selectCursor(String statement);
Cursor selectCursor(String statement, Object parameter);
Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds);
// 指定resultHandler参数的查询
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
// insert相关
int insert(String statement);
int insert(String statement, Object parameter);
// update相关
int update(String statement);
int update(String statement, Object parameter);
// delete相关
int delete(String statement);
int delete(String statement, Object parameter);
// 事务相关,force参数代表是否强制提交/回滚事务
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
// 检索当前Mybatis配置对象
Configuration getConfiguration();
// 检索一个绑定到当前SqlSession对象的type类型的Mapper代理对象
T getMapper(Class type);
// 检错内部持有的数据库连接对象
Connection getConnection();
}
接口终归是接口,活还是得有人来干
,下面看看SqlSession的实现类有哪些
DefaultSqlSession.java
Mybatis 默认的SqlSession实现类,实现了接口中定义的所有功能
接口定义
public class DefaultSqlSession implements SqlSession {...}
可以看到这里DefaultSqlSession仅实现了SqlSession接口
内部属性定义
// 重要属性,持有Mybatis Configuration实例
private final Configuration configuration;
// 持有执行器Executor,后面会详细分析
private final Executor executor;
// 是否自动提交事务
private final boolean autoCommit;
// 是否已过期,执行update操作就会置为true
private boolean dirty;
// 当查询数以百万计的数据时用到
private List> cursorList;
每个SqlSession实例内部都持有一个Mybatis的配置实例以及一个执行器Executor实例,且在其构造函数中就需要初始化这两个属性了
构造函数
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
接下来我们继续上面的查询例子sqlSession.
这里的command.getName的值是"com.wilson.mapper.UserOrderMapper.completedOrdersByUserPhone"
这个字符串,而param则是一个Map对象,其值为{{"phone", "15800000000"}, {"param1", "15800000000"}}
ok,接下来我们直接看DefaultSqlSession的selectList方法做了什么
@Override
public List selectList(String statement, Object parameter) {
// 内部调用重载的带有RowBounds参数的方法,传入默认的RowBounds,即offset为0,limit为Integer的最大值
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object
parameter, RowBounds rowBounds) {
try {
// 1.
MappedStatement ms = configuration.getMappedStatement(statement);
// 2.
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();
}
}
重点:
- 首先从
configuration
实例中获取到MappedStatement
实例 - 调用执行器
executor
的query
方法,并传入1. 中得到的MappedStatement实例作为参数
以上两步我们分开分析,首先看第一步如何从配置对象configuration中获取到MappedStatement实例
根据statementId获取MappedStatement实例
// Configuration.java
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
public MappedStatement getMappedStatement(String id) {
// 根据传入的statementId获取
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
// 就是从mappedStatements取出已经存入的MappedStatement实例
return mappedStatements.get(id);
}
总结
至此本文内容分析结束,SqlSession真正的SQL查询
交给了Executor执行器
,前文不是说过SqlSession是Mybatis提供的顶层API嘛,通过SqlSession就可以操作数据库的各种SQL操作了,这里怎么又出来一个执行器呢?实际上,Mybatis的SqlSession
非常聪明,将各种SQL操作的脏活、累活
都委托
给了Executor执行器
干,自己则坐享其成
...
下文我们将分析Mybatis的Executor篇