MyBatis3源码深度解析(十六)SqlSession的创建与执行(三)Mapper方法的调用过程

文章目录

    • 前言
    • 5.9 Mapper方法的调用过程
    • 5.10 小结

前言

上一节【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取】已经知道,调用SqlSession对象的getMapper(Class)方法,传入指定的Mapper接口对应的Class对象,即可获得一个动态代理对象,然后通过代理对象调用方法即可完成对数据库的操作。

本节源码分析使用的示例代码如下:

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>
@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()方法。

源码1org.apache.ibatis.binding.MapperProxypublic class MapperProxy<T> implements InvocationHandler, Serializable {@Overridepublic 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()方法。

源码2org.apache.ibatis.binding.MapperProxyprivate 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的新特性,指的是在接口类型中声明的,公开的,非抽象的,具有方法体的非静态方法。 例如:

public interface UserMapper {default String doSth() {return "这是一个默认方法";}
}

在本节的示例代码中,selectAll()方法、selectById()方法都没有方法体,因此它们都不是默认方法m.isDefault()的结果为false,程序会进入if结构中创建一个PlainMethodInvoker对象。而该对象的构造方法需要传入一个MapperMethod对象的实例。

源码3org.apache.ibatis.binding.MapperProxypublic 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对象

源码4org.apache.ibatis.binding.MapperMethodpublic 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的IDString 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。

源码5org.apache.ibatis.binding.MapperMethodprivate 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();}// 判断方法返回值类型是否为voidthis.returnsVoid = void.class.equals(this.returnType);// 判断方法返回值类型是否为集合this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();// 判断方法返回值类型是否为Cursorthis.returnsCursor = Cursor.class.equals(this.returnType);// 判断方法返回值类型是否为Optionalthis.returnsOptional = Optional.class.equals(this.returnType);// 判断方法返回值类型是否为Mapthis.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方法中的参数名称及参数注解信息。

源码6org.apache.ibatis.reflection.ParamNameResolverprivate final SortedMap<Integer, String> names;
public ParamNameResolver(Configuration config, Method method) {this.useActualParamName = config.isUseActualParamName();final Class<?>[] paramTypes = method.getParameterTypes();// 获取所有参数注解@Paramfinal Annotation[][] paramAnnotations = method.getParameterAnnotations();final SortedMap<Integer, String> map = new TreeMap<>();int paramCount = paramAnnotations.length;// 遍历参数注解@Paramfor (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()方法。

源码7org.apache.ibatis.binding.MapperProxyprivate static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);}
}

由 源码7 可知,PlainMethodInvoker对象的invoke()方法会转调MapperMethod对象的execute()方法。

源码8org.apache.ibatis.binding.MapperMethodprivate 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()方法:

源码9org.apache.ibatis.binding.MapperMethodprivate <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中。

源码10org.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类中。

源码11org.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对象,用于缓存KeyCacheKey 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()方法从数据库查询,再将查询结果保存到二级缓存中。

源码12org.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()方法从数据库查询。

源码13org.apache.ibatis.executor.BaseExecutorprivate <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子类来处理。

源码14org.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实现。

源码15org.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。

源码16org.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()方法。

源码17org.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。

源码18org.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缓存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/756916.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#,图论与图算法,计算无向连通图中长度为n环的算法与源代码

1 无向连通图中长度为n环 给定一个无向连通图和一个数n,计算图中长度为n的环的总数。长度为n的循环仅表示该循环包含n个顶点和n条边。我们必须统计存在的所有这样的环。 为了解决这个问题,可以有效地使用DFS(深度优先搜索)。使用DFS,我们可以找到特定源(或起点)的长度…

十一、MYSQL 基于MHA的高可用集群

目录 一、MHA概述 1、简介 2、MHA 特点 3、MHA 工作原理&#xff08;流程&#xff09; 二、MHA高可用结构部署 1、环境准备 2、安装MHA 监控manager 3、在manager管理机器上配置管理节点&#xff1a; 4、编master_ip_failover脚本写 5、在master上创建mha这个用户来访…

web容器导论

一、基础概念 1.Web容器是什么&#xff1f; 让我们先来简单回顾一下Web技术的发展历史&#xff0c;可以帮助你理解Web容器的由来。 早期的Web应用主要用于浏览新闻等静态页面&#xff0c;HTTP服务器&#xff08;比如Apache、Nginx&#xff09;向浏览器返回静态HTML&#xff…

轻松解锁微博视频:基于Perl的下载解决方案

引言 随着微博成为中国最受欢迎的社交平台之一&#xff0c;其内容已经变得丰富多彩&#xff0c;特别是视频内容吸引了大量用户的关注。然而&#xff0c;尽管用户对微博上的视频内容感兴趣&#xff0c;但却面临着无法直接下载这些视频的难题。本文旨在介绍一个基于Perl的解决方…

PHP/后端/Tp/fastadmin/消息通知企业微信机器人

第一步&#xff0c;先在企业微信的群聊里面添加一个机器人。 第二步&#xff0c;复制获取机器人的WebHook地址 第三步&#xff0c;拼接发送内容 public function webhook(){//机器人webhook地址 $url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyca8c9c-72b1-4faf-…

基于python+vue智慧社区家政服务系统的设计与实现flask-django-nodejs

论文主要是对智慧社区家政服务系统进行了介绍&#xff0c;包括研究的现状&#xff0c;还有涉及的开发背景&#xff0c;然后还对系统的设计目标进行了论述&#xff0c;还有系统的需求&#xff0c;以及整个的设计方案&#xff0c;对系统的设计以及实现&#xff0c;也都论述的比较…

基于docker+rancher部署Vue项目的教程

基于dockerrancher部署Vue的教程 前段时间总有前端开发问我Vue如何通过docker生成镜像&#xff0c;并用rancher上进行部署&#xff1f;今天抽了2个小时研究了一下&#xff0c;给大家记录一下这个过程。该部署教程适用于Vue、Vue2、Vue3等版本。 PS&#xff1a;该教程基于有一定…

Android 项目实战,APP开发,含源码

Android 项目实战&#xff0c;APP开发&#xff0c;含源码 源码项目详情 源码项目详情 切鱼达人&#xff0c;Android休闲游戏开发 打砖块&#xff0c;Android休闲小游戏开发 “牛弹琴”&#xff0c;Android 弹钢琴 app 开发 2048 数字合成大作战&#xff0c;Android小游戏开…

Flink中JobManager与TaskManage的运行架构以及原理详解

Flink中JobManager与TaskManage的运行架构详解 整体架构 Flink的运行时架构中&#xff0c;最重要的就是两大组件&#xff1a;作业管理器&#xff08;JobManger&#xff09;和任务管理器&#xff08;TaskManager&#xff09;。对于一个提交执行的作业&#xff0c;JobManager是真…

lang-segment-anything使用介绍

Language Segment-Anything 是一个开源项目&#xff0c;它结合了实例分割和文本提示的强大功能&#xff0c;为图像中的特定对象生成蒙版。它建立在最近发布的 Meta 模型、segment-anything 和 GroundingDINO 检测模型之上&#xff0c;是一款易于使用且有效的对象检测和图像分割…

软件工程-第8章 软件测试

8.1 软件测试目标域软件测试过程模型 8.2 软件测试技术 8.3 静态分析技术-程序正确性证明 8.4 软件测试步骤 8.5 本章小结

基于支持向量机(SVM)的数据时序预测(单输入输出)

代码原理 支持向量机(SVM)通常被用于处理分类问题,而对于数据时序预测(单输入输出),可以采用以下步骤使用SVM进行建模: 1. 数据准备:准备时间序列数据集,包括历史观测值和对应的目标值,按照时间顺序排列。 2. 特征提取:将时间序列数据转换为模型可接受的特征表示…

卷积神经网络五:GoogleNet

在2014年的ImageNet图像识别大赛中&#xff0c;一个名叫GoogleNet的网络架构大放异彩。GoogleNet使用了一种叫作Inception的结构。其实GoogleNet本质上就是一种Inception网络&#xff0c;而一个Inception网络又是由多个Inception模块和少量的汇聚层堆叠而成。 Inception模块 …

FPGA通过I2C控制AT24C64

文章目录 前言一、代码设计框图二、IIC_drive模块设计2.1、模块接口&#xff1a;2.2、代码功能描述&#xff1a;2.3、IIC协议实现过程&#xff1a; 三、EEPROM_ctrl模块设计3.1、模块接口&#xff1a;3.2、代码功能描述 四、EEPROM_drive模块五、iic_top模块 前言 继上一篇FPG…

Harvester基于 Kubernetes 构建的开源超融合基础架构 (HCI) 软件

Harvester 是基于 Kubernetes 构建的开源超融合基础架构 (HCI) 软件。它是使用专有 HCI 堆栈的一种开放替代方案&#xff0c;该堆栈结合了 Cloud Native Computing 的设计和精神。 Harvester 功能​ Harvester 支持在裸机服务器上实施 HCI。Harvester 使用本地、直接连接的存…

【Python】Miniconda+Vscode+Jupyter 环境搭建

1.安装 Miniconda Conda 是一个开源的包管理和环境管理系统&#xff0c;可在 Windows、macOS 和 Linux 上运行&#xff0c;它可以快速安装、运行和更新软件包及其依赖项。使用 Conda&#xff0c;我们可以轻松在本地计算机上创建、保存、加载和切换不同的环境 Conda 分为 Anaco…

Mysql数据库概念与安装

目录 一、数据库概述 1、数据库的基本概念 2、数据库管理系统&#xff08;DBMS&#xff09; 2.1 数据库管理系统概念 2.2 数据库管理系统工作模式 3、数据库系统&#xff08;DBS&#xff09; 3.1 数据库系统概念 3.2 数据库系统发展史 4、关系型数据库与非关系型数据库…

10-项目部署_持续集成-黑马头条

项目部署_持续集成 1 今日内容介绍 1.1 什么是持续集成 持续集成&#xff08; Continuous integration &#xff0c; 简称 CI &#xff09;指的是&#xff0c;频繁地&#xff08;一天多次&#xff09;将代码集成到主干 持续集成的组成要素 一个自动构建过程&#xff0c; 从…

学习vue3第八节(自定义指令 directive)

1、自定义指令的作用&#xff1a; 自定义指令是用来操作底层DOM的&#xff0c;尽管vue推崇数据驱动视图的理念&#xff0c;但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和拓展&#xff0c;不仅仅可用于定义任何DOM操作&#xff0c;并且是可以重复使用。 自定义…

只需3步,使用Stable Diffusion无限生成AI数字人视频

基本方法 搞一张照片&#xff0c;搞一段语音&#xff0c;合成照片和语音&#xff0c;同时让照片中的人物动起来&#xff0c;特别是头、眼睛和嘴。 语音合成 语音合成的方法很多&#xff0c;也比较成熟了&#xff0c;大家可以选择自己方便的&#xff0c;直接录音也可以&#…