MyBatis源码分析

MyBatis源码分析

MyBatis是常用的持久层框架,帮助我们减少了很多的访问数据库的代码。这次我们就来看看MyBatis是怎么做到这些的?看看它里面用到了哪些值得我们借鉴的技术。

一、示例程序

为了方便后续在本地进行debug调试,首先准备一个示例程序。

1、项目工程结构

在这里插入图片描述

2、父工程pom.xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>keepLearnOnMaven</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>studyJavaLua</module>
        <module>studyBase</module>
        <module>studyMybatis</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

3、studyMybatis项目的pom.xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>keepLearnOnMaven</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>studyMybatis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
    </dependencies>
</project>

4、mybatis-config.xml文件内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="mucao"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="userMapper.xml"/>
    </mappers>
</configuration>

5、userMapper.xml文件内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mucao.UserMapper">
    <select id="selectUser" resultType="com.mucao.User">
        select * from user
    </select>
</mapper>

6、User.java文件内容:

package com.mucao;

import lombok.*;

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private int id;  //id

    private String name;   //姓名

    private String pwd;   //密码
}

7、UserMapper.java文件内容:

package com.mucao;

import java.util.List;

public interface UserMapper {

    List<User> selectUser();

}

8、Application.java文件内容:

package com.mucao;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class Application {

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectUser();

        for (User user: users) {
            System.out.println(user);
        }

        sqlSession.close();
    }
}

运行结果如下所示:

User(id=1, name=王二, pwd=123456)
User(id=2, name=张三, pwd=abcdef)
User(id=3, name=李四, pwd=987654)

Process finished with exit code 0

二、MyBatis启动流程分析

2.1 SqlSessionFactory的创建

先看创建SqlSessionFactory的地方,入口代码如下所示:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // ref-1

ref-1处的代码会创建一个SqlSessionFactory出来,我们看下build方法内部实现:

// SqlSessionFactoryBuilder.java文件

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse()); // ref-12
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);  // ref-2
}

最终会在ref-2处创建一个DefaultSqlSessionFactory,调用的构建函数如下所示:

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

总结一下,读取配置文件的xml内容作为配置数据Configuration, 然后依据配置数据创建DefaultSqlSessionFactory

2.2 SqlSession的创建

下面接着看SqlSession的获取。

// Application.java文件

SqlSession sqlSession = sqlSessionFactory.openSession(); // ref-3

ref-3处的代码会进入到如下的逻辑中:

// DefaultSqlSessionFactory.java文件

@Override
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 获取环境设置,就是mybatis-config.xml文件中的environments标签内容
        final Environment environment = configuration.getEnvironment();
        // 获取事务工厂,示例程序的事务管理工厂是直接从Environment对象中获取的
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 依据数据源(environment.getDataSource())、事务隔离级别(level)和是否自动提交(autoCommit)创建事务
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // ref-4
		// 创建执行器
        final Executor executor = configuration.newExecutor(tx, execType); // ref-5
        // 创建默认的session
        return new DefaultSqlSession(configuration, executor, autoCommit); // ref-7
    } 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();
    }
}

ref-4处创建事务的实际代码如下所示:

// JdbcTransactionFactory.java文件

@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
}

ref-5处创建执行器的实际代码如下所示:

// Configuration.java文件

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        // 示例程序会走到这儿来
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) { // 判断为true
        // 对执行器进行装饰,使其有缓存能力
        executor = new CachingExecutor(executor);
    }
    // 拦截器会对执行器进行封装(责任链模式)
    executor = (Executor) interceptorChain.pluginAll(executor); // ref-6
    return executor;
}

ref-6处使用到了责任链模式,Mybatis中责任链模式的实现和我们常见的实现不太一样,先分析完主干流程,后面再对这个点进行详细分析。

2.3 UserMapper实现对象的获取

通过SqlSession可以获取UserMapper接口的实现对象,如下所示:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // ref-8

ref-8处代码实际执行逻辑如下:

// DefaultSqlSession.java文件

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}
// Configuration.java文件

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 这个mapperRegistry就代表userMapper.xml文件
    return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry.java文件

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取提前注册好的工程对象
    // ref-10
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 工厂对象创建实例对象
        return mapperProxyFactory.newInstance(sqlSession);  // ref-9
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

我们来看看ref-9处是怎么创建实例对象的:

// MapperProxyFactory.java文件

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 使用jdk的动态代理创建代理对象
    // 这个mapperInterface就是UserMapper接口对应的class对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
	// 这儿的mapperProxy其实是个InvocationHandler
    // 这个mapperInterface就是UserMapper接口对应的class对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

简单总结就是使用jdk的动态代理创建UserMapper接口的实现对象。

还有一个遗留问题,那就是ref-10处的mapperProxyFactory是什么时候注册进去的?答案就在下面的代码中:

// MapperRegistry.java文件

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

这个增加addMapper方法会是在ref-12build(parser.parse())parse()方法中被调用的,Mybatis会把从配置文件中解析到的Mapper注册到knownMappers中。

三、Mybatis执行SQL语句的过程

启动流程分析完了,我们大概知道Mybatis启动过程中干了哪些事情,这些事情都是为执行SQL语句做准备的,接下来我们就分析下Mybatis执行SQL语句的过程。

入口代码如下所示:

// Application.java文件

List<User> users = userMapper.selectUser(); // ref-11

for (User user: users) {
    System.out.println(user);
}

重点语句就是ref-10处的代码。我们来详细分析下执行过程:

// MapperProxy.java文件

private final Map<Method, MapperMethod> methodCache;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (method.isDefault()) {
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 尝试从缓存中获取对应的MapperMethod对象,如果获取不到就进行创建
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 真正的执行方法                                  
    return mapperMethod.execute(sqlSession, args); // ref-13
}

private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

ref-13处会进行方法的真正执行,具体代码如下:

// 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); // ref-14
            } 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);
                if (method.returnsOptional()
                    && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            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;
}

execute方法会依据command的类型执行不同的分支,具体的就是对应到不同类别的SQL语句,这和之前分析过的H2数据库执行SQL语句的过程很像。我们着重看一下示例程序会进入的executeForMany方法,具体代码如下:

// MapperMethod.java文件

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 将UserMapper方法的参数转换为SQL命令的参数
    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); // ref-15
    }
    // 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;
}

ref-15处代码会真正的执行SQL语句,我们进行分析下:

// DefaultSqlSession.java文件

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) {
    try {
        // 通过配置信息将接口方法语句'com.mucao.UserMapper.selectUser'映射为对应的SQL语句(ms)
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 调用执行器的查询方法 // ref-16
        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();
    }
}

ref-16处的调用执行器执行查询信息,我们在分析启动过程的时候,知道这个执行器是缓存执行器,把真正的执行器包装了一层。我们接着看执行器的查询操作:

// CachingExecutor.java文件

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 依据参数对象获取约束的SQL对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
	// 计算用于缓存结果的key,这儿计算的值为:1053852532:2785247288:com.mucao.UserMapper.selectUser:0:2147483647:select * from user:development
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}

@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) {
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    // 示例程序会走这儿,这个delegate才是真正执行计算的执行器
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // ref-17
}

ref-17处代码会调用被装饰的那个执行的查询方法,具体代码如下所示:

// BaseExecutor.java文件

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        // 优先从缓存中去获取结果。
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 示例程序会进入到这儿  
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); // ref-18
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}

ref-18处会从数据库查询结果,代码如下:

// BaseExecutor.java文件

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 {
        // 执行查询操作
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);  // ref-19
    } finally {
        // 清楚提前放置的占位标识
        localCache.removeObject(key);
    }
    // 将查询结果放入缓存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

我们接着看ref-19处的查询操作具体逻辑,代码如下所示:

// SimpleExecutor.java文件

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);
        // 准备jdbc的Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 由handler来执行jdbc的Statement
        return handler.query(stmt, resultHandler);  // ref-20
    } finally {
        closeStatement(stmt);
    }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取连接
    Connection connection = getConnection(statementLog);
    // 由handler准备jdbc的Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

ref-20处的代码昭示着快要和jdbc联系到一起了,我们尽一步分析handler是如何执行jdbc的Statement,详细代码如下:

// RoutingStatementHandler.java文件

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 这个delegate是PreparedStatementHandler实例
    return delegate.query(statement, resultHandler);
}
// PreparedStatementHandler.java文件

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 回到了jdbc,将statement强转成PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行jdbc的PreparedStatement
    ps.execute();
    // 调用结果集处理器处理结果
    return resultSetHandler.handleResultSets(ps); // ref-21 
}

我们已经跟进到Mybatis使用jdbc执行SQL语句的部分了,基本的执行路径已经分析完了,最后再来看看ref-21处是如何对结果进行处理的:

// DefaultResultSetHandler.java文件

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        handleResultSet(rsw, resultMap, multipleResults, null);
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }

    return collapseSingleResultList(multipleResults);
}

这个结果处理有点儿复杂,大概意思是将jdbc的返回结果封装为User对象的List,然后返回给上层。

四、Mybatis中责任链模式特殊的实现

最后我们来看一下ref-6处,Mybatis中责任链的实现。ref-6处代码逻辑如下:

// InterceptorChain.java文件

public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<>();

    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            // 使用拦截器对target进行包装
            target = interceptor.plugin(target); // ref-22
        }
        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

ref-22处代码如下所示:

// org.apache.ibatis.plugin.Interceptor.java文件

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this); // ref-23
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

ref-23处代码如下所示:

// org.apache.ibatis.plugin.Plugin.java文件

public class Plugin implements InvocationHandler {

    private final Object target;
    private final Interceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;

    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
            // 使用jdk的动态代理对target对象进行包装
            return Proxy.newProxyInstance(
                type.getClassLoader(),
                interfaces,
                new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            if (methods != null && methods.contains(method)) {
                // 对方法调用进行拦截
                return interceptor.intercept(new Invocation(target, method, args));
            }
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    // ...... 省略其他部分

}

简单总结一下,Mybatis中利用jdk的动态代理对目标对象和拦截器进行一层套一层的封装,使得它们组成了一个链式结构。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Hello Algorithm】单调栈(未完待续)

单调栈解决的问题 我们单调栈的提出主要是为了解决这么一个问题 现在给我们一个数组 现在要求你建立一张表 这张表中能够查询到两个信息 这两个信息分别是 当前数字左边小于该数字并且下标位置最相近的下标当前数字右边小于该数字并且下标位置最相近的下标 同理 大于也可以…

机器学习入门案例(3)之使用决策树预测是否适合打网球

大家好&#xff0c;我是邵奈一&#xff0c;一个不务正业的程序猿、正儿八经的斜杠青年。 1、世人称我为&#xff1a;被代码耽误的诗人、没天赋的书法家、五音不全的歌手、专业跑龙套演员、不合格的运动员… 2、这几年&#xff0c;我整理了很多IT技术相关的教程给大家&#xff0…

如何解决网页中的pdf文件无法下载?pdf打印显示空白怎么办?

问题描述 偶然间&#xff0c;遇到这样一个问题&#xff0c;一个网页上的附件pdf想要下载打印下来&#xff0c;奈何尝试多种办法都不能将其下载下载&#xff0c;点击打印出现的也是一片空白 百度搜索了一些解决方案都不太行&#xff0c;主要解决方案如&#xff1a;https://zh…

【万字长文】Python 日志记录器logging 百科全书 之 日志过滤

Python 日志记录器logging 百科全书 之 日志过滤 前言 在Python的logging模块中&#xff0c;日志过滤器&#xff08;Filter&#xff09;用于提供更细粒度的日志控制。通过过滤器&#xff0c;我们可以决定哪些日志记录应该被输出&#xff0c;哪些应该被忽略。这对于复杂的应用…

【每日一题】—— D. Epic Transformation(Codeforces Round 710 (Div. 3))(找规律+贪心)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

vue离线地图(瓦片)

最近公司要弄一个这样的离线地图&#xff0c;要求在图上打点画线之类的。折腾了几天&#xff0c;学习了三种方式&#xff1a; 1.拿到各省市区的经纬度json&#xff0c;通过echarts来制作&#xff0c;再套一个卫星图的地图背景 2.下载地图瓦片&#xff0c;再通过百度/高德的离线…

image J 对Western blot 条带进行灰度分析 量化分析

用ImageJ对条带进行定量分析 | Public Library of Bioinformatics (plob.org) 3分钟Get&#xff01;大牛教你用 image J 对Western blot 条带进行灰度分析&#xff01; - 哔哩哔哩 (bilibili.com) 科研人员做的western blot实验一般需要对其结果扫描后进行灰度分析&#xff0…

【Qt之QWizard】使用2,示例分析

效果图 根据首页的选择不同&#xff0c;进入不同的选项。 以下是代码。 示例 .h #ifndef LICENSEWIZARD_H #define LICENSEWIZARD_H#include <QWizard>QT_BEGIN_NAMESPACE class QCheckBox; class QLabel; class QLineEdit; class QRadioButton; QT_END_NAMESPACEcla…

vue请求代理查看真实地址

查看真实地址方式&#xff1a; 通过配置vue.config.js文件&#xff0c;直接在请求头输出完整地址&#xff1a; /api/: { changeOrigin: true, target: process.env.VUE_APP_PLATFORM_URL, logLevel: debug, // 在终端输出 onProxyRes(proxyR…

请求头,响应头

目录 常见的请求方式 GET/POST HEAD&#xff08;报文首部&#xff0c;验证URI有效性&#xff09; PUT/DELETE(报文文件) OPTIONS&#xff08;查询URI支持的HTTP方法&#xff09; Connection: keep-alive TCP 就会一直保持连接。 Cache-Control public&#xff1a;响应…

数据银行:安全保障的重要一环

随着信息技术的快速发展&#xff0c;数据银行已经成为了我们日常生活中不可或缺的一部分。它存储了我们的个人信息、财务数据、医疗记录等重要信息&#xff0c;这些信息对于我们的生活和工作至关重要。然而&#xff0c;由于数据的安全性备受关注&#xff0c;因此&#xff0c;对…

【星海出品】SDN neutron (四) 流分析

Neutron框架之流分析 1.控制端neutron-server通过wsgi接收北向REST API请求&#xff0c;neutron-plugin通过rpc与设备端进行南向通信。 2.设备端agent则向上通过rpc与控制端进行通信&#xff0c;向下则直接在本地对网络设备进行配置。 3.Neutron-agent的实现很多&#xff0c;彼…

容斥dp,二项式反演

前言 由于水平有限&#xff0c;这篇文章比较难懂&#xff0c;并且也有很多不够透彻的地方&#xff0c;如果您有任何的看法&#xff0c;非常感谢您私信指导。 容斥dp 用dp的方法来描述容斥&#xff0c;大概的想法是&#xff0c;把容斥系数分到每一步里去乘。 通常当你有容斥…

本田发布全新CB1000 Hornet,是杜卡迪街霸劈了腿还是Z1000红杏出墙?

米兰车展上&#xff0c;本田带来了全新的大黄蜂CB1000 Hornet&#xff0c;外观方面抛弃了之前的本田推出的Neo Sports Caf风格&#xff0c;新款的外观看起来要更加战斗一点。不过新的这个前脸改的&#xff0c;我只能说是杜卡迪街霸劈了腿还是Z1000红杏出墙&#xff1f;外观方面…

【Python】Numpy(学习笔记)

一、Numpy概述 1、Numpy Numpy&#xff08;Numerical Python&#xff09;是一个开源的Python科学计算库&#xff0c;用于快速处理任意维度的数组。 Numpy使用ndarray对象来处理多维数组&#xff0c;该对象是一个快速而灵活的大数据容器&#xff0c; Numpy num - numerical 数…

Kohana框架的安装及部署

Kohana框架的安装及部署 tipsKohana安装以及部署1、重要文件作用说明1.1 /index.php1.2 /application/bootstrap.php 2、项目结构3、路由配置3.1、隐藏项目入口的路由3.2、配置默认路由3.3、配置自定义的路由(Controller目录下的控制器)3.4、配置自定义的路由(Controller/direc…

JS操作canvas

<canvas>元素本身并不可见&#xff0c;它只是创建了一个绘图表面并向客户端js暴露了强大的绘图API。 1 <canvas> 与图形 为优化图片质量&#xff0c;不要在HTML中使用width和height属性设置画布的屏幕大小。而要使用CSS的样式属性width和height来设置画布在屏幕…

父组件用ref获取子组件数据

子组件 Son/index.vue 子组件的数据和方法一定要记得用defineExpose暴露&#xff0c;不然父组件用ref是获取不到的&#xff01;&#xff01;&#xff01; <script setup> import { ref } from "vue"; const sonNum ref(1); const changeSon () > {sonNum.…

DAY54 392.判断子序列 + 115.不同的子序列

392.判断子序列 题目要求&#xff1a;给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是…

探秘Vue组件间通信:详解各种方式助你实现目标轻松搞定!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! ​ 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一…