文章目录
-
- 前言
- [5.9 Mapper方法的调用过程](#5.9 Mapper方法的调用过程)
- [5.10 小结](#5.10 小结)
前言
上一节【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取】已经知道,调用SqlSession对象的getMapper(Class)
方法,传入指定的Mapper接口对应的Class对象,即可获得一个动态代理对象,然后通过代理对象调用方法即可完成对数据库的操作。
本节源码分析使用的示例代码如下:
java
public interface UserMapper {
List<User> selectAll();
@Select("select * from user where id = #{id, jdbcType=INTEGER}")
User selectById(@Param("id") Integer id);
}
<!--UserMapper.xml-->
<mapper namespace="com.star.mybatis.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from user
</select>
</mapper>
java
@Test
public void testMybatis() throws IOException, NoSuchMethodException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 操作数据库
List<User> userList = userMapper.selectAll();
userList.forEach(System.out::println);
System.out.println("---------");
User user = userMapper.selectById(1);
System.out.println(user.toString());
}
5.9 Mapper方法的调用过程
由动态代理的原理可知,当调用动态代理对象的方法时(如执行userMapper.selectAll()
),会执行MapperProxy类的invoke()
方法。
java
源码1:org.apache.ibatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
// 从Object类继承的方法,不做任何处理,直接执行目标方法
return method.invoke(this, args);
}
// Mapper接口中定义的方法,调用cachedInvoker获取一个MapperMethodInvoker对象
// 再执行该对象的invoke方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} // catch ...
}
}
由 源码1 可知,在MapperProxy类的invoke()
方法中,对从Object类继承的方法不做任何处理,直接执行目标方法;对Mapper接口中定义的方法,调用cachedInvoker()
方法获取一个MapperMethodInvoker对象,然后再调用该对象的invoke()
方法。
java
源码2:org.apache.ibatis.binding.MapperProxy
private final Map<Method, MapperMethodInvoker> methodCache;
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) {
// 该方法不是一个默认方法时,创建一个PlainMethodInvoker对象并返回
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
// 如果是默认方法,则会创建默认的DefaultMethodInvoker对象
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
}
return new DefaultMethodInvoker(getMethodHandleJava9(method));
} // catch ...
});
} // catch ...
}
由 源码2 可知,如果Mapper接口定义的方法不是一个默认方法,则会创建一个PlainMethodInvoker对象。也就是说MapperMethodInvoker的实现类是PlainMethodInvoker。
默认方法是JDK1.8的新特性,指的是在接口类型中声明的,公开的,非抽象的,具有方法体的非静态方法。 例如:
java
public interface UserMapper {
default String doSth() {
return "这是一个默认方法";
}
}
在本节的示例代码中,selectAll()
方法、selectById()
方法都没有方法体,因此它们都不是默认方法 ,m.isDefault()
的结果为false,程序会进入if结构中创建一个PlainMethodInvoker对象。而该对象的构造方法需要传入一个MapperMethod对象的实例。
java
源码3:org.apache.ibatis.binding.MapperProxy
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
由 源码3 可知,MapperProxy类的构造方法中,创建了两个对象,分别是SqlCommand对象 和MethodSignature对象。
java
源码4:org.apache.ibatis.binding.MapperMethod
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取方法名以及声明该方法的类或接口的Class对象
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 从Configuration对象中,以Class对象和方法名为条件
// 获取描述<select|insert|update|delete>标签的MappedStatement对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException(
"Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
name = null;
type = SqlCommandType.FLUSH;
} else {
// 获取Mapper的ID和SQL语句的类型
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass,
Configuration configuration) {
// 接口的完全限定名和方法名拼接在一起,就是Mapper的ID
String statementId = mapperInterface.getName() + "." + methodName;
// 如果Configuration对象已经注册了这个ID的MappedStatement对象
// 则直接获取该对象并返回
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
}
if (mapperInterface.equals(declaringClass)) {
return null;
}
// 如果前面都没找到,则从父接口中遍历查找
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
由 源码4 可知,在SqlCommand类的构造方法中,调用了resolveMappedStatement()
方法,根据Mapper接口的完全限定名和方法名获取对应的MappedStatement对象,然后通过MappedStatement对象获取SQL语句的类型和Mapper的ID。简单来说,SqlCommand对象用于获取SQL语句的类型、Mapper的ID等信息。
在resolveMappedStatement()
方法中,首先将Mapper接口的完全限定名和方法名进行拼接,作为Mapper的ID从Configuration对象中查找对应的MappedStatement对象(MappedStatement对象的创建详见【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取】)。
如果查找不到,则判断该方法是否是从父接口中继承的,如果是,就以父接口作为参数递归调用resolveMappedStatement()
方法,若找到对应的MappedStatement对象,则返回该对象,否则返回null。
java
源码5:org.apache.ibatis.binding.MapperMethod
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取方法返回值的类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 判断方法返回值类型是否为void
this.returnsVoid = void.class.equals(this.returnType);
// 判断方法返回值类型是否为集合
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
// 判断方法返回值类型是否为Cursor
this.returnsCursor = Cursor.class.equals(this.returnType);
// 判断方法返回值类型是否为Optional
this.returnsOptional = Optional.class.equals(this.returnType);
// 判断方法返回值类型是否为Map
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// 获取RowBounds参数位置索引
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 获取ResultHandler参数位置索引
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 创建ParamNameResolver对象,用于解析Mapper方法参数
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
由 源码5 可知,MethodSignature类的构造方法做了3件事:
(1)获取Mapper方法的返回值类型,并通过boolean类型的属性进行标记。例如当返回值类型为List时,将returnsMany属性的值设为true。
(2)记录RowBounds参数位置索引,用于处理后续的分页查询;记录ResultHandler参数位置索引,用于处理从数据库中检索的每一行数据。
(3)创建ParamNameResolver对象,用于解析Mapper方法中的参数名称及参数注解信息。
java
源码6:org.apache.ibatis.reflection.ParamNameResolver
private final SortedMap<Integer, String> names;
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取所有参数注解@Param
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// 遍历参数注解@Param
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 跳过特殊参数:RowBounds参数、ResultHandler参数
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
// 判断方法参数中是否有@Param注解,如果有则从注解中获取参数名称
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 如果没有@Param注解,则判断是否使用实际的参数名称
if (useActualParamName) {
// 借助ParamNameUtil工具类获取参数名
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// 使用参数索引作为参数名称
name = String.valueOf(map.size());
}
}
// 将参数信息保存到Map集合中,key为参数位置索引,value为参数名称
map.put(paramIndex, name);
}
// 将参数信息保存到names属性中
names = Collections.unmodifiableSortedMap(map);
}
由 源码6 可知,在ParamNameResolver类的构造方法中,会对Mapper方法的所有参数进行遍历。
首先跳过两种特殊参数,即RowBounds参数和ResultHandler参数;然后判断参数中是否有@Param注解,如果有则从注解中获取参数名称;
如果没有@Param注解,则根据useActualParamName属性判断是否使用实际的参数名称。useActualParamName属性由MyBatis的主配置文件中的<setting name="useActualParamName" value="true"/>
决定,默认值为true。如果useActualParamName属性为true,则借助ParamNameUtil工具类获取参数名。
经过以上一系列获取动作,如果还没获取到参数名,则直接使用参数索引作为参数名称。紧接着将参数信息保存到一个Map集合中,key为参数位置索引,value为参数名称。最后将参数信息保存到一个SortedMap集合中。
至此,MapperMethod类的构造方法执行完毕(源码3)。 借助Deug工具,可以查看示例代码中selectAll()
方法和selectById()
方法所对应的MapperMethod对象中包含的信息:
MapperMethod对象创建完成,也意味着一个PlainMethodInvoker对象创建完成,cachedInvoker()
方法执行完毕返回一个MapperMethodInvoker对象(源码2)。
接下来回到MapperProxy的invoke()
方法,执行MapperMethodInvoker对象的invoke()
方法,实际上是执行其落地实现类PlainMethodInvoker对象的invoke()
方法。
java
源码7:org.apache.ibatis.binding.MapperProxy
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
由 源码7 可知,PlainMethodInvoker对象的invoke()
方法会转调MapperMethod对象的execute()
方法。
java
源码8:org.apache.ibatis.binding.MapperMethod
private final SqlCommand command;
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// SqlCommand对象中保存了SQL语句的类型
switch (command.getType()) {
// 对于INSERT、UPDATE、DELETE语句
// 先调用convertArgsToSqlCommandParam()方法获取参数信息
// 再调用SqlSession的insert()方法执行SQL语句
// 最后调用rowCountResult()方法统计影响行数
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;
}
// 对于SELECT语句
// 根据不同的返回值类型做不同的处理
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 示例代码的selectAll方法会调用
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 示例代码的selectById方法会调用
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;
// FLUSH语句
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 ...
}
return result;
}
由 源码8 可知,在MapperMethod对象的execute()
方法中,首先根据SqlCommand对象获取SQL语句的类型,然后根据SQL语句的类型调用SqlSession对象的对应的方法。
而示例代码中的selectAll()
方法会进入第一个else if结构,调用executeForMany()
方法;selectById()
方法会进入else结构,调用SqlSession对象的selectOne()
方法。
通过以上分析可以发现,MyBatis通过动态代理,将Mapper方法的调用转换为通过SqlSession提供的API方法完成数据库的增删改查操作。
重点研究一下selectAll()
方法:
java
源码9:org.apache.ibatis.binding.MapperMethod
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);
}
// ......
return result;
}
由 源码9 可知,executeForMany()
方法最终会调用SqlSession对象的selectList()
方法。该方法的实现在其子类DefaultSqlSession中。
java
源码10:org.apache.ibatis.session.defaults.DefaultSqlSession
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 从Configuration对象中获取对应的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
dirty |= ms.isDirtySelect();
// 以MappedStatement对象为参数,调用Executor的query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} // catch finally ......
}
由 源码10 可知,DefaultSqlSession的selectList()
方法,首先会根据Mapper的ID从Configuration对象中获取对应的MappedStatement对象,然后以该对象为参数,调用Executor的query()
方法完成查询操作。
由于MyBatis的主配置文件中,cacheEnabled属性默认为true(<setting name="cacheEnabled" value="true"/>
),同时Mapper XML配置文件中<select>标签的useCache属性默认为true,因此二级缓存默认开启,调用Executor的query()
方法的实现在CachingExecutor类中。
java
源码11:org.apache.ibatis.executor.CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException {
// 获取BoundSql对象,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建CacheKey对象,用于缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载的query()方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@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) {
// 结果为空,再调用BaseExecutor的query方法
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);
}
由 源码11 可知,CachingExecutor的query()
方法做了3件事情:获取BoundSql对象;创建CacheKey对象,用于缓存Key;调用重载的query()
方法。
BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装。 借助Debug工具,可以查看执行selectAll()
方法时的BoundSql对象:
在重载的query()
方法中,会根据缓存Key从二级缓存中查询结果,如果成功查询到则直接返回,没有查询到则调用BaseExecutor的query()
方法从数据库查询,再将查询结果保存到二级缓存中。
java
源码12:org.apache.ibatis.executor.BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// ......
List<E> list;
try {
queryStack++;
// 从本地缓存(一级缓存)中查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 本地缓存查询到了的处理
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 本地缓存中没有查询到,则从Database查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
// ...
return list;
}
从 源码12 可知,BaseExecutor的query()
方法,首先从本地缓存(一级缓存)中获取查询结果,如果缓存中没有,则调用queryFromDatabase()
方法从数据库查询。
java
源码13:org.apache.ibatis.executor.BaseExecutor
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 {
// 调用doQuery方法从数据库查询结果
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;
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
由 源码13 可知,queryFromDatabase()
方法会调用doQuery()
方法进行查询,然后将查询结果进行缓存。doQuery()
方法是一个模板方法,由子类具体实现。
在MyBatis的主配置文件中,有这样一个配置:<setting name="defaultExecutorType" value="SIMPLE"/>
,它的作用就在于指定使用哪种Executor来处理对数据库的操作。它的默认值是"SIMPLE",因此默认情况下由SimpleExecutor子类来处理。
java
源码14:org.apache.ibatis.executor.SimpleExecutor
@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对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql);
// 调用prepareStatement()方法创建Stetment对象,并进行参数设置
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用Stetment对象的query()方法执行查询操作
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取JDBC的Connection对象
Connection connection = getConnection(statementLog);
// 调用StatementHandler的prepare()方法创建Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 调用StatementHandler的parameterize()方法设置参数
handler.parameterize(stmt);
return stmt;
}
由 源码14 可知,SimpleExecutor类的doQuery()
方法首先会调用Configuration对象的newStatementHandler()
方法创建一个StatementHandler对象,深入该方法的源码可以发现,这里创建的StatementHandler对象是一个RoutingStatementHandler对象。
接着,以RoutingStatementHandler对象为参数,调用prepareStatement()
方法创建Stetment对象,并进行参数设置。StatementHandler的prepare()
方法是一个模板方法,具体由子类BaseStatementHandler实现。
java
源码15:org.apache.ibatis.executor.statement.BaseStatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 调用instantiateStatement()方法创建Statement对象
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} // catch ...
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
由 源码15 可知,BaseStatementHandler的prepare()
方法中,会调用instantiateStatement()
方法创建Statement对象。该方法是一个模板方法,具体由子类实现。
在Mapper XML配置文件中<select>标签中,有一个statementType属性,该属性用于指定执行这条SQL语句时的StatementHandler类型,默认值是PREPARED。 因此,默认的StatementHandler实现子类是PreparedStatementHandler。
java
源码16:org.apache.ibatis.executor.statement.PreparedStatementHandler
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(),
ResultSet.CONCUR_READ_ONLY);
}
}
由 源码16 可知,PreparedStatementHandler的instantiateStatement()
方法最终调用Connection对象的prepareStatement()
方法创建了一个PreparedStatement对象并返回。
回到 源码14 的doQuery()
方法,Statement对象(具体实现是PreparedStatement)创建完毕后,调用StatementHandler(具体实现是PreparedStatementHandler)的query()
方法。
java
源码17:org.apache.ibatis.executor.statement.PreparedStatementHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行SQL语句
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
由 源码17 可知,PreparedStatementHandler的query()
方法会调用PreparedStatement对象的execute()
方法执行SQL语句,然后调用ResultSetHandler的handleResultSets()
方法处理结果集。
ResultSetHandler只有一个默认的实现,即DefaultResultSetHandler。
java
源码18:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
// ......
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 1.获取ResultSet对象,并将其包装为ResultSetWrapper对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 2.获取ResultMap信息
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 3.真正处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// ......
}
由 源码18 可知,DefaultResultSetHandler的handleResultSets()
方法的逻辑如下:
(1)从Statement对象中获取ResultSet对象,并包装成ResultSetWrapper对象,通过ResultSetWrapper对象可以更方便地获取表字段名称、字段对应的TypeHandler信息等。
(2)获取解析Mapper接口及SQL配置的ResultMap信息,一条语句一般对应一个ResultMap。
(3)调用handleResultSet()方法对ResultSetWrapper对象进行处理,将生成的实体对象存放在multipleResults列表中并返回。
至此,MyBatis通过调用Mapper接口定义的方法执行注解或者XML文件中配置的SQL语句的过程梳理完毕。本文以示例代码中的selectAll()
方法为例,其它的方法一样可以遵循这样的思路进行分析。
5.10 小结
第五章到此就梳理完毕了,本章的主题是:SqlSession的创建与执行过程。回顾一下本章的梳理的内容:
(十四)Configuration实例、SqlSession实例的创建过程
(十五)Mapper接口与XML配置文件的注册过程、MappedStatement对象的注册过程、Mapper接口的动态代理对象的获取
(十六)Mapper方法的调用过程
更多内容请查阅分类专栏:MyBatis3源码深度解析
第六章主要学习:MyBatis缓存。主要内容包括:
- MyBatis缓存的使用;
- MyBatis缓存实现类;
- MyBatis一级缓存的实现原理;
- MyBatis二级缓存的实现原理;
- MyBatis使用Redis缓存。