Mybatis源码05 - Mapper映射文件的配置

Mapper映射文件的配置

文章目录

  • Mapper映射文件的配置
    • 一:更新的配置和使用
      • 1:模板mapper
      • 2:实例说明
    • 二:select、resultMap的配置及使用
      • 1:select的配置
      • 2:实例说明
      • 3:resultMap
      • 4:字符串代入法和SQL注入
    • 三:子元素解析
      • 1:子元素cache解析
      • 2:子元素cache-ref解析
      • 3:子元素resultMap解析
      • 4:子元素sql解析
      • 5:子元素statement解析
        • 5.1:动态解析子元素
        • 5.2:生成SqlSource
        • 5.3:生成KeyGenerator
        • 5.4:创建MapperStatement
    • 四:注册mapper类型

一:更新的配置和使用

1:模板mapper

看到insert, update, delete我们就知道其作用了,顾名思义嘛,myabtis 作为持久层框架,必须要对CRUD啊

先来看看 insert, update, delete 怎么配置, 能配置哪些元素吧:

   
DOCTYPE mapper   
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"  
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> 



<mapper namespace="com.dy.dao.UserDao">
    
    
    <cache type="PERPETUAL" eviction="LRU" flushInterval="60000"  size="512" readOnly="true" />
    
    
    <cache-ref namespace="com.someone.application.data.SomeMapper"/>
    
    <insert
      id="insertUser" 
      parameterType="com.demo.User"
      flushCache="true"
      statementType="PREPARED"
      keyProperty=""
      keyColumn=""
      useGeneratedKeys="false"
      timeout="20">

    <update
      id="updateUser"
      parameterType="com.demo.User"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">

    <delete
      id="deleteUser"
      parameterType="com.demo.User"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">
mapper>

id - (必须配置)

id是命名空间中的唯一标识符,可被用来代表这条语句。

一个命名空间(namespace) 对应一个dao接口, 这个id也应该对应dao里面的某个方法(相当于方法的实现),因此id 应该与方法名一致

parameterType (可选配置, 默认为mybatis自动选择处理)

将要传入语句的参数的完全限定类名或别名

如果不配置,mybatis会通过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理

parameterType 主要指定参数类型,可以是int, short, long, string等类型,也可以是复杂类型(如对象)

flushCache (可选配置,默认配置为true)

将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)

statementType (可选配置,默认配置为PREPARED)

STATEMENT,PREPARED 或 CALLABLE 的一个。

这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED

keyProperty (可选配置, 默认为unset)

仅对 insert 和 update 有用 - 唯一标记一个属性

MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。

如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

keyColumn (可选配置)

仅对 insert 和 update 有用,通过生成的键值设置表中的列名

这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。

如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表

useGeneratedKeys (可选配置, 默认为false)

仅对 insert 和 update 有用

这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键,默认值:false。

timeout (可选配置, 默认为unset, 依赖驱动)

这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)

2:实例说明

以上就是一个模板配置, 哪些是必要配置,哪些是根据自己实际需求,看一眼就知道了。看一个真实的UserDao-Mapper.xml配置:

   
DOCTYPE mapper   
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"  
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> 

<mapper namespace="com.dy.dao.UserDao">
   
   
   <insert id="insertUser" parameterType="com.dy.entity.User">
           insert into user(id, name, password, age, deleteFlag) 
               values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
   insert>
   
   
   <update id="updateUser" parameterType="com.dy.entity.User">
           update user set name = #{name}, password = #{password}, age = #{age}, deleteFlag = #{deleteFlag}
               where id = #{id};
   update>
    
    
   <delete id="deleteUser" parameterType="com.dy.entity.User">
           delete from user where id = #{id};
   delete>
mapper>

这样,一个简单的映射关系就建立了。仔细观察上面parameterType => "com.dy.entity.User",会发现要是包名很长,如果每次都这么写,非常麻烦,此时typeAliases就派上用场了。

<typeAliases>
     
     <typeAlias alias="user" type="com.dy.entity.User"/>
typeAliases>

这样,一个别名就取好了,咱们可以把上面的 com.dy.entity.User 都直接改为user 了。 这多方便呀!

我这儿数据库用的是mysql, 我把user表的主键id 设置了自动增长,以上代码运行正常,那么问题来了:要是换成oracle数据库怎么办? oracle 可是不支持id自增长啊? 怎么办?


<insert id="insertUser" parameterType="com.dy.entity.User">
    
      
    <selectKey resultType="int" order="BEFORE" keyProperty="id">
        select seq_user_id.nextval as id from dual
    selectKey>


    insert into user(id, name, password, age, deleteFlag) 
    values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
insert>

同理,如果我们在使用mysql的时候,想在数据插入后返回插入的id, 我们也可以使用 selectKey 这个元素


<insert id="insertUser" parameterType="com.dy.entity.User">
     

    
    <selectKey keyProperty="id" resultType="int" order="AFTER" >
        SELECT LAST_INSERT_ID() as id
    selectKey>

    insert into user(id, name, password, age, deleteFlag) 
    values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
insert>

selectKey给了你一个简单的行为在你的数据库中来处理自动生成的主键,而不需要使你的Java代码变得复杂。

在上面的示例中,selectKey元素将会首先运行,userid会被设置,然后插入语句会被调用。

另外,selectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象

也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了

二:select、resultMap的配置及使用

select无疑是我们最常用,也是最复杂的,mybatis通过resultMap能帮助我们很好地进行高级映射

下面就开始看看select 以及 resultMap的用法:

1:select的配置

<select
        id="selectPerson"
        parameterType="int"
        resultType="hashmap"
        resultMap="personResultMap"
        flushCache="false"
        useCache="true"
        timeout="10000"
        fetchSize="256"
        statementType="prepared"
        resultSetType="forward_only"
>

id - (必须配置)

id是命名空间中的唯一标识符,可被用来代表这条语句。

一个命名空间(namespace) 对应一个dao接口, 这个id也应该对应dao里面的某个方法(相当于方法的实现),因此id 应该与方法名一致

parameterType (可选配置, 默认为mybatis自动选择处理)

将要传入语句的参数的完全限定类名或别名

如果不配置,mybatis会通过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理

parameterType 主要指定参数类型,可以是int, short, long, string等类型,也可以是复杂类型(如对象)

resultType (resultType & resultMap二选一)

resultType用以指定返回类型,指定的类型可以是基本类型,可以是java容器,也可以是javabean

resultMap (resultType & resultMap二选一)

resultMap用于引用我们通过 resultMap标签定义的映射类型,这也是mybatis组件高级复杂映射的关键

flushCache(可选)

将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false

useCache(可选)

将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true

statementType (可选配置,默认配置为PREPARED)

STATEMENT,PREPARED 或 CALLABLE 的一个。

这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED

timeout (可选配置, 默认为unset, 依赖驱动)

这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)

fatchSize (可选)

这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)

resultSetType (可选)

FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)

2:实例说明

配置看起来总是这么多,不过实际常用的配置也就那么几个,看一个CourseDao-Mapper.xml配置

<mapper namespace="com.dy.dao.CourseDao">
    
    <select id="findCourseById"  resultType="course" >
        select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=#{courseId}
    select>
mapper>

3:resultMap

有个问题值得思考: 一个student可以对应多个course, 那么,在mybatis中如何处理这种一对多, 甚至于多对多,一对一的关系呢?

这就引出了 resultMap 这个东西, mybatis的resultMap功能可谓十分强大,能够处理复杂的关系映射


<resultMap type="" id="">
    
    <id property="" column=""/>
    
        
    <result property="" column=""/>

    
    <constructor>
        
        <idArg column=""/>
        
        <arg column=""/>
    constructor>

    
    <collection property="" column="" ofType="">collection>
    
    
    <association property="" column="" javaType="">association>
    
    <discriminator column="CLASS_ID" javaType="String" jdbcType="VARCHAR">  
        <case value="20000001" resultType="liming.student.manager.data.model.StudentEntity" >  
            <result property="classId" column="CLASS_ID" javaType="String" jdbcType="VARCHAR"/>  
        case> 
    discriminator>
resultMap>

一个student对应多个course, 典型的一对多,就来看看mybatis怎么配置这种映射吧:StudentDao-Mapper.xml

<mapper namespace="com.dy.dao.StudentDao">

    
    <resultMap type="student" id="studentMap">
    
        
        <id property="idCard" column="stu_id_card"/>
        <result property="id" column="stu_id"/>
        <result property="name" column="stu_name"/>
        <result property="deleteFlag" column="stu_delete_flg"/>
  
        <constructor>  
            <idArg javaType="String" column="STUDENT_ID"/>  
            <arg javaType="String" column="STUDENT_NAME"/>  
            <arg javaType="String" column="STUDENT_SEX"/>  
            <arg javaType="Date" column="STUDENT_BIRTHDAY"/>  
        constructor>
        
        
        <collection property="courseList" column="stu_course_id" ofType="Course">
            <id property="id" column="course_id"/>
            <result property="name" column="course_name"/>
            <result property="deleteFlag" column="course_delete_flg"/>
        collection>
    resultMap>
    
    
    <select id="findStudentById" resultMap="studentMap">
        SELECT s.*, c.* 
        FROM t_student s LEFT JOIN t_course c 
        ON s.stu_course_id=c.course_id 
        WHERE s.stu_id_card=#{idCard}
    select>

    
    <sql id="userColumns"> 
        userid,username,password
    sql>
    
    <select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false">
		select <include refid="userColumns"/> 
        from t_user t 
        where t.username = #{username}
    select>
    
mapper>

4:字符串代入法和SQL注入

默认的情况下,使用#{}语法会促使MyBatis 生成PreparedStatement 属性并且使用PreparedStatement 的参数(=?)来安全的设置值。

尽量这些是快捷安全,也是经常使用的。

但有时候你可能想直接未更改的字符串代入到SQL 语句中。

比如说,对于ORDER BY,你可能会这样使用:ORDER BY ${columnName}但MyBatis 不会修改和规避掉这个字符串。

这样地接收和应用一个用户输入到未更改的语句中,是非常不安全的。

这会让用户能植入破坏代码,所以,要么要求字段不要允许客户输入,要么你直接来检测他的合法性 。【SQL注入】

三:子元素解析

1:子元素cache解析

Mapper配置文件是由XMLMapperBuilder解析的,其中cacheElement方法负责解析cache元素

它通过调用CacheBuilder的相应方法完成cache的创建。

每个cache内部都有一个唯一的ID,这个id的值就是namespace。

创建好的cache对象存入configuration的cache缓存中

该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
        // type属性,默认是perpertual
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // eviction属性,默认是LRU
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // 其他属性
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        Properties props = context.getChildrenAsProperties();
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
    }
}

2:子元素cache-ref解析

cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用

创建好的cache-ref引用关系存入configuration的cacheRefMap缓存中

private void cacheRefElement(XNode context) {
    if (context != null) {
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
        try {
            // 通过调用resolveCacheRef完成cache的引用,将关系存储到Configuration中的cacheRefMap中
            cacheRefResolver.resolveCacheRef();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteCacheRef(cacheRefResolver);
        }
    }
}

3:子元素resultMap解析

resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析

创建好的resultMap存入configuration的resultMaps缓存中

该缓存以 namespace + resultMap 的 id 为 key,这里再次体现了 mybatis 的 namespace 的强大用处

/**
 * 这是一个重载的方法,它接受一个 XNode 类型的参数 resultMapNode,代表 XML 中的  元素。
 * 方法内部调用了另一个重载版本的 resultMapElement 方法,并传入了一个空的 ResultMapping 列表作为额外的结果映射。
 */
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}

/**
 * 解析resultMap
 * resultMapNode:代表  元素的 XML 节点。
 * additionalResultMappings:一个额外的结果映射列表,可能从其他地方继承而来。
 */
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    // 设置错误上下文,记录当前正在处理的  元素的标识符。
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取  元素的 id 属性,如果没有指定,则使用基于值的标识符。
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    // 获取  元素的 type 属性,如果没有指定,则依次尝试获取 ofType、resultType 和 javaType 属性。
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));
    // 获取  元素的 extends 属性,用于指定继承的其他 
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取  元素的 autoMapping 属性,指示是否启用自动映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 解析 type 属性中的类型字符串为 Java 类。
    Class<?> typeClass = resolveClass(type);
    // 初始化 discriminator 为 null,稍后可能会被赋值。
    Discriminator discriminator = null;
    
    // 创建一个新的 ResultMapping 列表,并添加任何额外的结果映射。
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    // 获取  元素的所有子节点,并遍历它们。
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        // 如果子节点名称为 "constructor",则调用 processConstructorElement 方法来处理构造器元素。
        // 如果子节点名称为 "discriminator",则调用 processDiscriminatorElement 方法来处理判别器元素,并设置 discriminator。
        // 对于其他子节点,如果名称为 "id",则添加 ResultFlag.ID 标志,并构建一个 ResultMapping。
        if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    // 创建一个 ResultMapResolver 实例,用于解析和创建最终的 ResultMap。
    // 尝试通过调用 resolve 方法来创建 ResultMap。
    // 如果抛出 IncompleteElementException 异常,则将未完成的 ResultMapResolver 添加到配置中以供后续处理,并重新抛出异常。
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

4:子元素sql解析

sqlElement方法负责解析sql元素。

id属性用于区分不同的sql元素,在同一个mapper配置文件中可以配置多个sql元素

// 这段代码的主要作用是根据当前数据库 ID 来筛选并存储 SQL 片段,确保只有那些适用于当前环境的 SQL 片段会被加载和处理。
// 这样可以提高框架的灵活性和可扩展性,支持多种数据库类型的配置。

/**
 * 这个方法接收一个 List 类型的参数 list,其中 XNode 表示 XML 节点。
 * 此方法的目的是根据当前数据库 ID 来选择性地处理 SQL 片段。
 * 检查当前数据库 ID:
 * 如果当前配置的 databaseId 不为空,则调用重载的 sqlElement 方法,并传入当前的 databaseId。
 * 然后,再次调用重载的 sqlElement 方法,但这次传入 null 作为 requiredDatabaseId 参数,这意味着处理那些没有指定 databaseId 的 SQL 片段。
 * 调用重载方法:
 * 通过这种方式,确保了所有适用的 SQL 片段都会被正确处理。
 */
private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
        sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
}

/**
 * 这个方法接收两个参数:
 * list: 包含多个 XNode 的列表,每个 XNode 代表一个 SQL 片段。
 * requiredDatabaseId: 一个字符串,表示当前处理的 SQL 片段必须匹配的数据库 ID。
 * 遍历 SQL 片段: 遍历传入的 list 中的每个 XNode。
 * 从每个 XNode 中提取 databaseId 和 id 属性。
 * 使用 builderAssistant 应用命名空间到 id 上。
 * 判断是否符合条件:
 * 调用 databaseIdMatchesCurrent 方法来确定当前 SQL 片段是否应该被处理。
 * 存储 SQL 片段:
 * 如果符合条件,则将 SQL 片段存储在 sqlFragments 映射中,键为 id。
 */
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
    }
}

/*
 * 这个方法用于判断给定的 SQL 片段是否应该被当前环境处理。
 * 检查特定数据库 ID:
 * 如果 requiredDatabaseId 不为空,则只有当 databaseId 与 requiredDatabaseId 相匹配时才返回 true。
 * 处理通用 SQL 片段:
 * 如果 requiredDatabaseId 为空,则检查 databaseId 是否也为空。
 * 如果 databaseId 不为空,则直接返回 false。
 * 接下来,检查 sqlFragments 映射中是否已经存在相同的 id,并且其 databaseId 是否不为空。如果是这种情况,则返回 false,因为已经有特定数据库 ID 的 SQL 片段存在。
 * 返回结果:
 * 如果以上条件都不满足,则返回 true,表示当前 SQL 片段可以被处理。
 */
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
        if (!requiredDatabaseId.equals(databaseId)) {
            return false;
        }
    } else {
        if (databaseId != null) {
            return false;
        }
        // skip this fragment if there is a previous one with a not null databaseId
        if (this.sqlFragments.containsKey(id)) {
            XNode context = this.sqlFragments.get(id);
            if (context.getStringAttribute("databaseId") != null) {
                return false;
            }
        }
    }
    return true;
}

5:子元素statement解析

buildStatementFromContext()方法负责解析statement元素。

id属性用于区分不同的statement元素,在同一个配置文件中可以配置多个statement元素。

通过调用XMLStatementBuilder#parseStatementNode()方法完成解析。

在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素很有帮助

5.1:动态解析子元素

statement节点可以配置各种子元素,比如前面提到的include子元素和selectKey子元素等(在动态sql里还有更多的子元素)。

动态解析子元素通过parseDynamicTags方法完成。

该方法根据子元素的类型递归的解析成一个个的SqlNode,这些SqlNode对象提供了apply方法,供后续调用时生成sql语句所需。

需要注意的是SelectKey没有对应的SqlNode对象,因为它的功能是用来生成KeyGenerator对象的(具体来说是SelectKeyGenerator对象)。

另外,SelectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象

也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了

5.2:生成SqlSource

SqlSource用于后续调用时根据SqlNode和参数对象生成sql语句。它接收一个叫做rootsqlNode的对象作为构造参数

5.3:生成KeyGenerator

如果配置了selectKey子元素,KeyGenerator直接使用selectKey子元素里生成的KeyGenerator对象(具体来说是SelectKeyGenerator对象)。

若没配置,则如果useGeneratedKeys属性的值为"true"且配置了 keyProperty属性,则生成默认的Jdbc3KeyGenerator对象

该对象调用JDBC驱动的getGeneratedKeys方法返回insert语句执行后生成的自增长主键

5.4:创建MapperStatement

MappedStatement对象封装了statement元素的所有属性以及子节点值,MappedStatement对象有一个id属性用于唯一标记它

这个id由namespace加statement元素的id属性值构成。

创建好的MappedStatement对象存入Configuration对象的mappedStatements缓存中,key为MappedStatement对象的id值

XMLMapperBuilder

// 这段代码的主要作用是根据当前数据库 ID 来筛选并解析 SQL 语句,确保只有那些适用于当前环境的 SQL 语句会被加载和处理。
// 这样可以提高框架的灵活性和可扩展性,支持多种数据库类型的配置。
// 如果解析过程中遇到不完整的元素,则将其标记为不完整并稍后处理,以避免程序中断。


/**
 * 这个方法接收一个 List 类型的参数 list,其中 XNode 表示 XML 节点。
 * 此方法的目的是根据当前数据库 ID 来选择性地处理 SQL 语句。
 * 检查当前数据库 ID:
 * 如果当前配置的 databaseId 不为空,则调用重载的 buildStatementFromContext 方法,并传入当前的 databaseId。
 * 然后,再次调用重载的 buildStatementFromContext 方法,但这次传入 null 作为 requiredDatabaseId 参数,这意味着处理那些没有指定 databaseId 的 SQL 语句。
 * 调用重载方法:
 * 通过这种方式,确保了所有适用的 SQL 语句都会被正确处理。
 */
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

/**
 * 这个方法接收两个参数:
 * list: 包含多个 XNode 的列表,每个 XNode 代表一个 SQL 语句。
 * requiredDatabaseId: 一个字符串,表示当前处理的 SQL 语句必须匹配的数据库 ID。
 * 遍历 SQL 语句:
 * 遍历传入的 list 中的每个 XNode。
 * 为每个 XNode 创建一个 XMLStatementBuilder 实例,用于解析 SQL 语句。
 * 解析 SQL 语句:
 * 调用 XMLStatementBuilder 的 parseStatementNode 方法来解析 SQL 语句。
 * 如果在解析过程中遇到 IncompleteElementException 异常,则将 XMLStatementBuilder 实例添加到配置中的 incompleteStatements 集合中,以便稍后进行处理。
 */
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

XMLStatementBuilder

/**
   * 解析 Statement 节点
   * 
   * 该方法负责解析 MyBatis 配置文件中的 Statement 节点,如