Mybatis调用流程

Mybatis调用流程

准备

sql语句
create table `t_user`
(
    id     int not null auto_increment,
    `name` varchar(255) default null,
    `pwd`  varchar(255) default null,
    gender int          default null,
    age    int          default null,
    primary key (id)
) engine = innodb
  auto_increment = 1
  charset = utf8mb4;
启动类
public class MybatisApplication {
    public static void main(String[] args) {
        final String configFileName = "mybatis-config.xml";

        try {
            InputStream inputStream = Resources.getResourceAsStream(configFileName);

            SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            try (SqlSession sqlSession = sessionFactory.openSession()) {

                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

                User user = userMapper.selectById(1);

                System.out.println(user);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
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="msa.dao.UserMapper">

    <resultMap id="User" type="msa.entity.User">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
    </resultMap>

    <select id="selectById" resultMap="User">
        select * from `t_user`
        <where>
            <if test="id != null">
                id = #{id}
            </if>
        </where>
    </select>

</mapper>
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>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.56.10:3306/msa?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="ba7QhVdN0GZuATUq"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>
UserMapper接口
public interface UserMapper {
    User selectById(Integer id);
}
User实体类
@Data
public class User {

    private Integer id;
    private String name;
    private String pwd;
    private Integer gender;
    private Integer age;
}

前提

Mybatis是基于XML + Mapper接口来作为用户调用的DAO层。

XML文件

Mybatis调用流程

接口

Mybatis调用流程

使用SqlSession获取接口的代理类

Mybatis调用流程

Mapper接口的代理类为org.apache.ibatis.binding.MapperProxy

Mybatis调用流程

MapperProxy实现了InvocationHandler接口

这也是为什么Mybatis不支持使用类来写Mapper文件对应的接口,因为Mybatis的代理是基于JDK的,而不是基于CGLIB。

Mybatis调用流程

现在已经知道Mapper接口的是由MapperProxy这个类的代理的,那这个MapperProxy类又是如何获取的呢?

SqlSession创建MapperProxy

SqlSession的默认实现类为org.apache.ibatis.session.defaults.DefaultSqlSession

Mybatis调用流程

Mybatis调用流程

DefaultSqlSession类的getMapper方法

DefaultSqlSession类内部的getMapper方法又调用了org.apache.ibatis.session.ConfigurationgetMapper方法。

注意:Configuration没有实现任何接口和继承任何类。

Mybatis调用流程

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

Configuration类的getMapper方法

Mybatis调用流程

Mybatis调用流程

Mybatis调用流程

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // mapperProxyFactory使用了工厂模式,在需要时new一个MapperProxy
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

MapperProxyFactory类源码

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;
    // 同一个SqlSession创建的MapperProxy共用一个方法缓存
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // 使用了JDK的动态代理
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

}

MapperProxy执行流程

MapperProxy的代理逻辑:invoke方法

MapperProxy实际上只是对InvocationHandler接口的一层封装,实际的方法调用逻辑由MapperMethod实现。

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    // 被代理的接口
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @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);
            } else if (method.isDefault()) {
                // 调用接口的默认方法
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 从缓存中查找对应的MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 通过MapperMethod来执行对应的逻辑
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
}
MapperMethod的execute方法逻辑

execute方法实际还是调用了SqlSession类。

Mybatis调用流程

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:
            // select语句走这个逻辑
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                // 转换方法参数为xml里面对应的参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 调用SqlSession来获取结果
                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;
}
DefaultSqlSession类的selectList方法

DefaultSqlSession内部又调用了org.apache.ibatis.executor.CachingExecutor类的query方法。

Mybatis调用流程

CachingExecutor类的query方法

Mybatis调用流程

@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是SimpleExecutor类)
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
SimpleExecutor类的query方法
@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);
        }
    } 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;
}

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);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 这里就会去打开数据库连接并获取对应的PreparedStatement去执行sql语句
        // 详情: PreparedStatementHandler#query
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}
PreparedStatementHandler类的query方法

这个方法内部调用resultSetHandler的默认实现类为org.apache.ibatis.executor.resultset.DefaultResultSetHandler

Mybatis调用流程

DefaultResultSetHandler类的handleResultSets方法
//
// HANDLE RESULT SETS
//
@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);
}

MapperMethod类的execute方法使用策略模式

MapperMethod的execute方法使用SqlCommand来区分执行哪种逻辑(增删改查)

Mybatis调用流程

Mybatis调用流程


题外话:Mybatis如何把接口参数注入XML中对应的参数?

实现逻辑在org.apache.ibatis.reflection.ParamNameResolvergetNamedParams中。

我们知道通过上面的分析知道了MapperProxy代理类是调用MapperMethod类的execute来间接调用SqlSession,那么在MapperMethodSqlSession这个过程中是如何注入这些参数的呢?

这就要看MapperMethod里面的MethodSignatureconvertArgsToSqlCommandParam方法。

MethodSignature内部有一个ParamNameResolver的成员,convertArgsToSqlCommandParam方法就调用了ParamNameResolvergetNameParams

Mybatis调用流程

public class ParamNameResolver {

    private static final String GENERIC_NAME_PREFIX = "param";

    /**
     * 保存接口方法的参数索引和其参数名称的对应关系
     * key: 方法参数在方法中的索引, 例如:0, 1, ...
     * value: 对应的参数名称, 如果参数上存在@Param则使用@Param注解的值,否则该值等于调用key.toString()
     * 注意: RowBounds和ResultHandler接口的方法参数会被忽略
     * aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
     * aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
     * aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
     */
    private final SortedMap<Integer, String> names;

    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
            // 如果没有方法参数,则返回null
            return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
            // 没有@Param且只有一个参数,则返回对应的参数值
            return args[names.firstKey()];
        } else {
            final Map<String, Object> param = new ParamMap<>();
            int i = 0;
            for (Map.Entry<Integer, String> entry : names.entrySet()) {
                param.put(entry.getValue(), args[entry.getKey()]);
                // 通用的方法名称param1, prarm2, ...
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
                if (!names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }
}

Mybatis调用流程

上一篇:将java.util.Date date转java.sql.Date 存入sql


下一篇:我要手撕mybatis源码