加载Mapper映射文件

XMLMapperBuilder用来解析XML中的SQL,MapperAnnotationBuilder用来解析Mapper接口中的注解SQL。这里只分析一下XMLMapperBuilder

以解析 resource 为例,即解析XML文件。解析完XML文件中的SQL,还会去解析该XML文件对应的Mapper接口中的注解SQL。

// XMLConfigBuilder.java
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.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);
                    // 获取Mapper.xml的文件流
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 构造 XMLMapperBuilder 对象
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    // 解析 Mapper.xml 文件
                    mapperParser.parse();
                } 
                // 省略......
            }
        }
    }
}

XMLMapperBuilder

parse

解析 Mapper XML 配置文件

// XMLMapperBuilder.java
public void parse() {
    // 判断当前 Mapper 是否已经加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析 <mapper /> 节点
        configurationElement(parser.evalNode("/mapper"));
        // 标记该 Mapper 已经加载过
        configuration.addLoadedResource(resource);

        // 绑定 Mapper.xml 对应的 Mapper 接口,并解析 Mapper 接口中的SQL注解
        // 第一步:
        // 该方法会将Mapper接口保存到 Configuration 的 MapperRegistry 属性里面
        // MapperRegistry 里面有一个 Map<Class<?>, MapperProxyFactory<?>> knownMappers
        // 最终通过 knownMappers.put(type, new MapperProxyFactory<T>(type)); 注册Mapper代理
        // 这里为每一个Mapper接口生成了一个 MapperProxyFactory 
        // 第二步:解析 Mapper 接口中SQL语句并封装成 MappedStatement 对象
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

configurationElement

// XMLMapperBuilder.java
private void configurationElement(XNode context) {
    try {
        // 获得 namespace 属性
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // 设置 namespace 属性
        builderAssistant.setCurrentNamespace(namespace);
        // 解析 <cache-ref /> 节点
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析 <cache /> 节点,缓存相关的一些属性设置
        cacheElement(context.evalNode("cache"));
        // 已废弃!老式风格的参数映射。
        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 (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

主要分析buildStatementFromContext解析流程

buildStatementFromContext

解析

// XMLMapperBuilder.java
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 遍历 <select />、<insert />、<update />、<delete /> 节点们,
    // 依次创建 XMLStatementBuilder 对象,执行解析
    for (XNode context : list) {
        // 创建 XMLStatementBuilder 对象
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 执行解析
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

XMLStatementBuilder

org.apache.ibatis.builder.xml.XMLStatementBuilder ,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即 <select /><insert /><update /><delete /> 标签。

构造函数

public class XMLStatementBuilder extends BaseBuilder {
    // 保存当前 Mapper.xml 的一些属性信息,如namespace
    private final MapperBuilderAssistant builderAssistant;
    // 当前 XML 节点,例如:<select />、<insert />、<update />、<delete /> 标签
    private final XNode context;
    // 当前节点的 databaseId
    private final String requiredDatabaseId;

    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.context = context;
        this.requiredDatabaseId = databaseId;
    }
}

parseStatementNode

执行 Statement 解析

// XMLStatementBuilder
public void parseStatementNode() {
    // 获取当前节点的 id 属性
    String id = context.getStringAttribute("id");
    // 获取当前节点的 databaseId 属性
    String databaseId = context.getStringAttribute("databaseId");
    // 判断 databaseId 是否匹配
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }
    // 获取当前节点的各种属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    // 获得 SQL 对应的 SqlCommandType 枚举值
    // select、insert、update、delete
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 判断是否为 select 操作
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 获取一些属性
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析 <selectKey /> 标签
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 创建 SqlSource,保存了完整的SQL信息
    // 此时 <selectKey> 和 <include>标签已经被解析过了并且被删除了
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    // 获得 KeyGenerator 对象
    // (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 
    // 方法来取出由数据库内部生成的主键
    // (比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

    // 先从 configuration 中获得 KeyGenerator 对象。如果存在,说明已经配置过了
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // 根据标签属性,判断是使用Jdbc3KeyGenerator 还是 NoKeyGenerator 对象
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

解析完节点信息后,最后会生成 MappedStatement 对象保存到Configuration中。构建MappedStatement 对象的方法在MapperBuilderAssistant类里面

MapperBuilderAssistant

构建 MappedStatement 对象

// MapperBuilderAssistant.java
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) {

    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    // 获得 id 编号,格式为 ${namespace}.${id}
    id = applyCurrentNamespace(id, false);
    // 判断是否为 select 操作
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 创建 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(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    // 获得 ParameterMap ,并设置到 MappedStatement.Builder 中
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }
    // 创建 MappedStatement 对象
    MappedStatement statement = statementBuilder.build();
    // 添加到 configuration 中的 Map<String, MappedStatement> mappedStatements
    // key 为 id
    // value 为 MappedStatement
    configuration.addMappedStatement(statement);
    return statement;
}

MappedStatement

org.apache.ibatis.mapping.MappedStatement 保存了每个 <select /><insert /><update /><delete /> 节点的详细信息。

public final class MappedStatement {
    // Mapper.xml配置文件名,如:UserMapper.xml
    // Mapper接口文件名,如com/example/demo/UserMapper.java
    private String resource;
    // 全局配置
    private Configuration configuration;
    // 节点的id属性加命名空间,如:com.example.demo.UserMapper.getUserByUserName
    private String id;
    private Integer fetchSize;
    private Integer timeout;
    // 操作SQL的对象的类型
    // STATEMENT: 直接操作SQL,不进行预编译
    // PREPARED: 预处理参数,进行预编译,获取数据
    // CALLABLE: 执行存储过程
    private StatementType statementType;
    // 返回结果类型
    // FORWARD_ONLY:结果集的游标只能向下滚动
    // SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时当前结果集不变
    // SCROLL_SENSITIVE:返回可滚动的结果集,当数据库变化时,当前结果集同步改变
    private ResultSetType resultSetType;
    // sql语句
    private SqlSource sqlSource;
    private Cache cache;
    private ParameterMap parameterMap;
    // 返回结果类型
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
    private boolean useCache;
    private boolean resultOrdered;
    // sql语句的类型,如select、update、delete、insert
    private SqlCommandType sqlCommandType;
    private KeyGenerator keyGenerator;
    private String[] keyProperties;
    private String[] keyColumns;
    private boolean hasNestedResultMaps;
    private String databaseId;
    private Log statementLog;
    private LanguageDriver lang;
    private String[] resultSets;

    public BoundSql getBoundSql(Object parameterObject) {
        // 获得 BoundSql 对象
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 忽略这一步,因为 <parameterMap /> 已经废弃
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }

        // check for nested result maps in parameter mappings (issue #30)
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }
}

SqlSource

org.apache.ibatis.mapping.SqlSource,代表从 Mapper XML 或接口方法注解上读取的一条 SQL 内容。代码如下:

public interface SqlSource {
    // 根据用户传入的入参,返回 BoundSql 对象
    BoundSql getBoundSql(Object parameterObject);
}

BoundSql

public class BoundSql {
    // 保存sql语句,例如:select * from user where username = ?
    private final String sql;
    // 保存对#{}字符串的解析结果
    private final List<ParameterMapping> parameterMappings;
    // 用户传递进来的参数
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;
}

BoundSql语句的解析主要是通过对#{}字符的解析,将其替换成?。最后均包装成预表达式供PrepareStatement调用执行

#{}中的key属性以及相应的参数映射,比如javaType、jdbcType等信息均保存至BoundSql的parameterMappings属性中供最后的预表达式对象PrepareStatement赋值使用

总结

MyBatis初始化完成后,每一个接口方法都会被封装成了一个MappedStatement对象,这个对象里面包含了执行SQL语句的详细信息。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!