MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取

前言

SqlSession对象创建后,接下来是执行Mapper。执行Mapper的过程可以拆解为三步:注册Mapper接口与XML配置文件;注册MappedStatement对象;调用Mapper方法。

5.5 Mapper接口与XML配置文件的注册过程

Mapper接口用于定义执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中的<select|update|insert|delete>标签的id属性相同,Mapper接口的完全限定名一般和Mapper XML配置文件的命名空间相同。例如:

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>

如上面的代码所示,UserMapper接口的selectAll()方法名与UserMapper.xml中的<select>标签的id属性相同;UserMapper接口的完全限定名与<mapper>标签的namespace属性相同。

如果要将Mapper接口或Mapper XML配置文件注册到Configuration组件中,需要在主配置文件mybatis-config.xml中配置<mappers>标签。

<!--mybatis-config.xml-->
<mappers>
    <!--方式一:通过指定XML文件的类路径来注册-->
    <mapper resource="mapper/UserMapper.xml"/>
    <!--方式二:通过指定XML文件的完全限定资源定位符来注册-->
    <mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/>
    <!--方式三:通过Mapper接口的类路径来注册-->
    <mapper class="com.star.mybatis.mapper.UserMapper"/>
    <!--方式四:通过Mapper接口所在包路径类注册-->
    <package name="com.star.mybatis.mapper"/>
</mappers>

如上面的代码所示,<mappers>标签支持四种配置方式,其中方式一和方式二指定XML配置文件的相对和绝对路径,方式三和方式四指定Mapper接口的类路径和所在包路径。实际开发中,选择其一即可。

【MyBatis3源码深度解析(十四)Configuration与SqlSession的创建过程 5.2 Configuration实例创建过程】中已经研究过,在mappersElement()方法中,会对<mappers>标签的四种配置方式分别进行解析。

源码1org.apache.ibatis.builder.xml.XMLConfigBuilder

private void mappersElement(XNode context) throws Exception {
    if (context == null) {
        return;
    }
    for (XNode child : context.getChildren()) {
        if ("package".equals(child.getName())) {
            // 方式四的处理
            String mapperPackage = child.getStringAttribute("name");
            configuration.addMappers(mapperPackage);
        } else {
            String resource = child.getStringAttribute("resource");
            String url = child.getStringAttribute("url");
            String mapperClass = child.getStringAttribute("class");
            if (resource != null && url == null && mapperClass == null) {
                // 方式一的处理 ...
                ErrorContext.instance().resource(resource);
                try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                        configuration.getSqlFragments());
                    mapperParser.parse();
                }
            } else if (resource == null && url != null && mapperClass == null) {
                // 方式二的处理 ...
                ErrorContext.instance().resource(url);
                try (InputStream inputStream = Resources.getUrlAsStream(url)) {
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
                        configuration.getSqlFragments());
                    mapperParser.parse();
                }
            } else if (resource == null && url == null && mapperClass != null) {
                // 方式三的处理 ...
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
            } else {
                // throw ...
        }
    }
}

5.5.1 Mapper接口的注册过程

由 源码1 可知,如果是方式三或方式四这两种配置Mapper接口的方式,则会调用Configuration对象的addMappers()方法。

源码2org.apache.ibatis.session.Configuration

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 用于处理指定包路径下的所有Mapper接口的注册
public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
}
// 用于处理指定的Mapper接口的注册
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

由 源码2 可知,Configuration组件中组合了一个MapperRegistry对象,其addMappers(String)方法用于处理指定包路径下的所有Mapper接口的注册,其addMapper(Class<T>)方法用于处理指定的Mapper接口的注册。

先来看addMapper(Class<T>)方法:

源码3org.apache.ibatis.binding.MapperRegistry

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public <T> void addMapper(Class<T> type) {
    // 判断是否是Mapper接口
    if (type.isInterface()) {
        // 判断是否已经注册过
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 注册Mapper接口到knownMappers属性中
            knownMappers.put(type, new MapperProxyFactory<>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

由 源码3 可知,MapperRegistry类中有一个knownMappers属性,用于注册Mapper接口对应的Class对象和MapperProxyFactory对象之间的关系。addMapper(Class<T>)方法经过type.isInterface()hasMapper(type)两个前置检查之后,然后为Mapper接口对应的Class对象创建一个MapperProxyFactory对象,并添加到knownMappers属性中。

再来看addMappers(String)方法:

源码4org.apache.ibatis.binding.MapperRegistry

public void addMappers(String packageName, Class<?> superType) {
    // 借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 遍历Class对象集合,分别调用addMapper方法注册到MapperRegistry类的knownMappers属性中
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

由 源码4 可知,addMappers(String)方法借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象,然后遍历Class对象集合,分别调用addMapper(Class<T>)方法将Class对象注册到MapperRegistry类的knownMappers属性中。

再深入resolverUtil.find()方法的源码可以发现,ResolverUtil是使用VFS工具扫描指定包路径下所有Class文件,然后再交给addMapper(Class<T>)方法进行筛选。

至此,Mapper接口的注册完毕,MapperRegistry类的knownMappers属性中保存了Mapper接口对应的Class对象与MapperProxyFactory对象之间的映射关系。

5.5.2 XML配置文件的注册过程

回到 源码1 可知,如果是方式一或方式二这两种配置Mapper XML配置文件的方式,则会先获取Mapper XML配置文件的输入流,再调用XMLMapperBuilder类的parse()方法来完成注册。

源码5org.apache.ibatis.builder.xml.XMLMapperBuilder

private final XPathParser parser;
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // 调用XPathParser的evalNode方法获取mapper根节点对应的XNode对象
        // 调用configurationElement方法进一步解析
        configurationElement(parser.evalNode("/mapper"));
        // 将资源路径添加到Configuration对象中
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }
    // ......
}

由 源码5 可知,parse()方法首先调用XPathParser的evalNode()方法获取<mapper>根节点对应的XNode对象,然后调用configurationElement()方法进一步解析XNode对象。

源码6org.apache.ibatis.builder.xml.XMLMapperBuilder

private void configurationElement(XNode context) {
    try {
        // 获取并设置命名空间
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 解析<cache-ref>标签
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析<cache>标签
        cacheElement(context.evalNode("cache"));
        // 解析<parameterMap>标签
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 解析<resultMap>标签
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 解析<sql>标签
        sqlElement(context.evalNodes("/mapper/sql"));
        // 解析<select|insert|update|delete>标签
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } // catch ......
}

由 源码6 可知,configurationElement()方法会对Mapper XML配置文件的所有标签进行解析,并封装成一个MappedStatement对象。

5.6 MappedStatement对象的注册过程

重点关注<select|insert|update|delete>标签的解析。由 源码6 可知,获取<select|insert|update|delete>标签节点对应的XNode对象后,调用buildStatementFromContext()方法做进一步的解析。

源码7org.apache.ibatis.builder.xml.XMLMapperBuilder

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        // 创建XMLStatementBuilder对象
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
                requiredDatabaseId);
        try {
            // 真正解析标签
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

由 源码7 可知,buildStatementFromContext()方法会对所有的XNode对象进行遍历,为每一个XNode对象创建一个XMLStatementBuilder对象。然后调用该对象的parseStatementNode()方法真正进行解析标签。

源码8org.apache.ibatis.builder.xml.XMLStatementBuilder

public void parseStatementNode() {
    // 获取id、databaseId属性并进行前置判断
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }
    // 根据标签名字得到SQL语句的类型
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 获取flushCache属性,如果是SELECT语句,则默认为false
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 获取useCache属性,如果是SELECT语句,则默认为true
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 获取resultOrdered属性,默认为false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 将<include>标签内容替换为<sql>标签定义的SQL片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 获取parameterType属性,并转换成对应的Class对象
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 获取lang属性,并转换成对应的LanguageDriver对象
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 解析<selectKey>标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 获取主键生成策略
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 通过LanguageDriver解析SQL内容,生成SqlSource对象
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 获取statementType属性,默认为PREPARED类型
    StatementType statementType = StatementType
            .valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // 获取fetchSize、timeout、parameterMap属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    // 获取resultType属性,并转换为对应的Class对象
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    if (resultTypeClass == null && resultMap == null) {
        resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
    }
    // 获取resultSetType属性,并转换为对应的ResultSetType对象
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    // 获取keyProperty、keyColumn、resultSets、affectData属性
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
    // 使用上面获取的所有属性构造一个MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
            parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}

如 源码8 所示,parseStatementNode()方法篇幅很长,但逻辑很清晰,主要做了以下几件事情:

(1)获取<select|insert|update|delete>标签的所有属性信息。
(2)将<include>标签引用的SQL片段替换为对应的<sql>标签中定义的内容。
(3)获取lang属性指定的LanguageDriver对象,通过该对象创建代表SQL资源的SqlSource对象。
(4)获取KeyGenerator对象。KeyGenerator的不同实例代表不同的主键生成策略。
(5)所有解析工作完成后,使用MapperBuilderAssistant对象的addMappedStatement()方法创建一个MappedStatement对象。

源码9org.apache.ibatis.builder.MapperBuilderAssistant

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
                                          SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
                                          String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
                                          boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
                                          LanguageDriver lang, String resultSets, boolean dirtySelect) {
    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    id = applyCurrentNamespace(id, false);
    // 创建MappedStatement.Builder对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
            .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
            .resultOrdered(resultOrdered).resultSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
            .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }
    // 创建MappedStatement对象
    MappedStatement statement = statementBuilder.build();
    // 注册到Configuration组件中
    configuration.addMappedStatement(statement);
    return statement;
}

由 源码9 可知,addMappedStatement()方法会通过MappedStatement.Builder对象的build()方法创建一个MappedStatement对象,最后注册到Configuration组件中。

至此,Mapper接口和Mapper XML配置文件的解析全部完成,保存这些信息的位置是Configuration组件中的mappedStatements属性和mapperRegistry属性。

借助Debug,可以查看到这两个属性保存的信息:

5.7 Mapper接口的动态代理对象的获取

Mapper接口和Mapper XML配置文件解析注册完成后,接下来是执行Mapper接口中定义的方法。

为了执行Mapper接口中定义的方法,首先需要调用SqlSession对象的getMapper()方法获取一个Mapper接口的动态代理对象,然后通过代理对象调用Mapper方法。

代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();

SqlSession对象的getMapper()方法的源码如下:

源码10org.apache.ibatis.session.defaults.DefaultSqlSession

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}
源码11org.apache.ibatis.session.Configuration

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
源码12org.apache.ibatis.binding.MapperRegistry

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
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 {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

由 源码10-12 可知,SqlSession对象的getMapper()方法最终会从MapperRegistry对象的knownMappers属性中,根据传入的Class对象取出一个MapperProxyFactory对象,并调用其newInstance()方法创建一个实例并返回。

由类名可知,MapperProxyFactory是一个代理工厂类,其newInstance()方法会创建一个代理对象实例。

源码13org.apache.ibatis.binding.MapperProxyFactory

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

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

由 源码13 可知,MapperProxyFactory对象的newInstance()方法会创建一个MapperProxy对象,并调用Proxy类的newProxyInstance()方法创建一个代理对象实例。

源码14org.apache.ibatis.binding.MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> 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())) {
            return method.invoke(this, args);
        }
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

由 源码14 可知,MapperProxy类实现了InvocationHandler接口,使用的是JDK内置的动态代理

通过以上分析可以知道,SqlSession对象的getMapper()方法返回的是一个MapperProxy动态代理对象。


本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值