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

news2025/2/25 6:11:37

文章目录

    • 前言
    • 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.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()方法。

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

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

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

源码3org.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对象

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

源码5org.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方法中的参数名称及参数注解信息。

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

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

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

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

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

源码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.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子类来处理。

源码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.coloradmin.cn/o/1528964.html

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

相关文章

BUU [MRCTF2020]套娃

BUU [MRCTF2020]套娃 开题&#xff0c;啥也没有。 查看网页源代码发现后端源代码&#xff1a; <?php //1st $query $_SERVER[QUERY_STRING];if( substr_count($query, _) ! 0 || substr_count($query, %5f) ! 0 ){die(Y0u are So cutE!); }if($_GET[b_u_p_t] ! 23333 &am…

Vue核心知识点 -Vue2响应式系统是基于什么实现的、以及会产生什么问题和解决方案

一、概念 在Vue 2中&#xff0c;响应式系统是基于Object.defineProperty实现的。它通过劫持对象的属性来实现数据的响应式更新。 当你将一个对象传递给Vue实例的data选项时&#xff0c;Vue会遍历对象的每个属性&#xff0c;并使用Object.defineProperty方法将其转换为getter和s…

项目总结报告-word

2 项目工作成果 2.1 交付给用户的产品 2.2 交付给研发中心的产品 2.2.1 代码部分 2.2.2 文档部分 2.3 需求完成情况与功能及性能符合性统计 2.3.1 需求完成情况统计 2.3.2 功能符合性分析 2.3.3 性能符合性分析 3 项目工作分析 3.1 项目计划与进度实施分析 3.1.1 开发进度 3.1.…

2000-2021年各省研发强度数据(原始数据+计算结果)(无缺失)

2000-2021年各省研发强度数据&#xff08;原始数据计算结果&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;2000-2021年 2、指标&#xff1a;RD经费内部支出&#xff08;万元&#xff09;、国内生产总值、研发强度 3、范围&#xff1a;31省 4、来源&#…

【源码阅读】EVMⅢ

参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385 大致流程如下&#xff1a; 编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation 以太坊虚拟机的工作流程&#xff1a; 由solidity语言编…

数据库系统概论-练手题集合【期末复习|考研复习】

前言 总结整理不易&#xff0c;希望大家点赞收藏。 给大家整理了一下数据库系统概论中的练手题&#xff0c;以供大家期末复习和考研复习的时候使用。 数据库系统概论系列文章传送门&#xff1a; 第一章 绪论 第二/三章 关系数据库和标准语言SQL 第四/五章 数据库安全性和完整性…

linux:线程互斥

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程互斥问题解释互斥量的接口 二、加锁的原理三、 死锁死锁四个必要条件避免死锁 总结 前言 本文是对于线程互斥的知识总结 一、线程互斥 问题 我们先看下面…

Division by Invariant Integers using Multiplication

在处理器中&#xff0c;整数除法的成本通常是整数乘法的几倍&#xff1a; 流水线式的组合乘法器通常在不到10个周期内完成操作&#xff1b;而对于整数除法则没有硬件支持&#xff0c;或者使用的迭代除法器比乘法器慢几倍。 表 1.1 比较了一些处理器上乘法和除法的时间。这张表…

c++多长时间会被Python或者其他语言取代?

c多长时间会被Python或者其他语言取代&#xff1f; 如果不考虑市场因素&#xff0c;C#今天就可以取代C。 自.NET跨平台至今&#xff0c;C能做的工作&#xff0c;C#都能做了&#xff0c;且性能差别不大。 在C最有优势的嵌入式UI方面&#xff0c;C#可以拿出Avalonia替代QT。用 …

存储器的层次结构和局部性原理

前言 大家好我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第19篇&#xff0c;在这里分享给大家&#xff0c;这篇文章讲存储器的一部分内容。 存储器的层次结构 SRAM 静态随机存取存储器的芯片&#xff0c;SRAM 之所以被称为“静态”存储器&#xff0c;是因为只要处…

MYSQL概念和编译安装

目录 一、数据库概述 1.1数据 1.2表 1.3数据库 总结&#xff1a; 2.数据库管理系统&#xff08;DBMS&#xff09; 3.DBMS工作模式 4.数据库系统原理 二、数据库发展史 三、主流数据库 四、关系型数据库和非关系型数据库 1.关系型数据库 2.非关系数据库 MYSQL数据…

输出菱形(*)--c语言

//输出菱形 #include<stdio.h>int main(){//上int line0;scanf("%d",&line);int i0;for(i0;i<line;i){int j0;//输出空格for(j0;j<line-1-i;j){printf(" ");}//输出*号for(j0;j<2*i1;j){printf("*");}printf("\n")…

Redisson 分布式锁原理分析

Redisson 分布式锁原理分析 示例程序 示例程序&#xff1a; public class RedissonTest {public static void main(String[] args) {Config config new Config();config.useSingleServer().setPassword("123456").setAddress("redis://127.0.0.1:6379"…

【开发环境】Ubuntu 18.04 搭建 QT编译环境详细步骤 【亲测有效】

目录 1 查看Ubuntu系统中Qt版本 2 下载Ubuntu系统Qt版本安装包 3 Qt安装 3.1 Qt 安装步骤 3.2 安装qt发现Ubuntu空间不足&#xff0c;怎么去扩容呢&#xff1f; 3.2.1 硬盘操作步骤&#xff08;需要关闭虚拟机进行操作&#xff09; 3.2.2 Ubuntu命令操作&#xff1a;安装…

基于单片机的模糊PID炉温控制系统设计

摘 要 电热炉是在工业热处理的生产中广泛使用的一种设备&#xff0c;电热炉的温度控制系统存在时变性&#xff0c;非线性&#xff0c;滞后性等特征&#xff0c;难以用常规PID的控制器对系统达到很好的控制效果。当控温精度的要求高时&#xff0c;使用传统的控制理论方法难以达…

蓝桥杯刷题|03普及-真题

[蓝桥杯 2017 省 B] k 倍区间 题目描述 给定一个长度为 N 的数列&#xff0c;​,,⋯&#xff0c;如果其中一段连续的子序列 ​,,⋯ (i≤j) 之和是 K 的倍数&#xff0c;我们就称这个区间 [i,j] 是 K 倍区间。 你能求出数列中总共有多少个 K 倍区间吗&#xff1f; 输入格式 …

微服务高级篇(一):微服务保护+Sentinel

文章目录 一、初识Sentinel1.1 雪崩问题及解决方案1.2 微服务保护技术对比1.3 Sentinel介绍与安装1.4 微服务整合Sentinel 二、Sentinel的流量控制三、Sentinel的隔离与降级四、Sentinel的授权规则五、规则持久化5.1 规则管理模式【原始模式、pull模式、push模式】5.2 实现push…

第二十六节 Java 重写(Override)与重载(Overload)

重写 (Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写&#xff01;返回值和形参都不能改变。即外壳不变&#xff0c;核心重写&#xff01; 重写的好处在于子类可以根据需要&#xff0c;定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。…

面试算法-48-二叉树的锯齿形层序遍历

题目 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,…