• mybatis源码分析(三)------------映射文件的解析


    本篇文章主要讲解映射文件的解析过程

    Mapper映射文件有哪几种配置方式呢?看下面的代码:

        <!-- 映射文件 -->
        <mappers>
            <!-- 通过resource指定Mapper文件 -->  方式一
            <mapper resource="com/yht/mybatisTest/dao/goods.xml" />
            
            <!-- 通过class指定接口,但需要将接口与Mapper文件同名,从而将两者建立起关系,此处接口是GoodsDao,那么Mapper映射文件就需要是GoodsDao.xml --> 方式二
            <mapper class="com.yht.mybatisTest.dao.GoodsDao" />
            
            <!-- 扫描指定包中的接口,需要将接口名与Mapper文件同名 -->  方式三
            <package name="com.yht.mybatisTest.dao"/>
            
            <!-- 通过url指定Mapper文件位置 -->  方式四
            <mapper url="file://........" />
        </mappers>

    源码部分如下:

     private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
    // 循环处理mappers节点下所有的子节点
    for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) {
    // 获的package节点的name属性值 String mapperPackage
    = child.getStringAttribute("name");
    // 针对 方式三 的解析方法 configuration.addMappers(mapperPackage); }
    else {
    // 获取mapper节点的resource属性值 String resource
    = child.getStringAttribute("resource"); // 获取mapper节点的url属性值
    String url
    = child.getStringAttribute("url");
    // 获取mapper节点的class属性值 String mapperClass
    = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); 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); 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 new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }

    由上面代码可知:针对四种不同的配置分别进行了解析,这里我们主要分析 方式一 的解析方法,进入该方法:

     public void parse() {
    // 检测Mapper映射文件是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
    // 解析mapper节点 configurationElement(parser.evalNode(
    "/mapper"));
    // 将资源文件添加到 已解析资源集合 中 configuration.addLoadedResource(resource);
    // 注册Mapper接口 bindMapperForNamespace(); } // 处理 configurationElement方法中解析失败的<ResultMap />节点 parsePendingResultMaps();
    // 处理 configurationElement方法中解析失败的<cache-ref />节点 parsePendingChacheRefs();
    // 处理 configurationElement方法中解析失败的SQL语句节点 parsePendingStatements(); }

     一 解析Mapper节点

    进入XMLMapperBuilder类的configurationElement方法中:

    private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (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"));
    // 解析parameterMap节点 parameterMapElement(context.evalNodes(
    "/mapper/parameterMap"));
    // 解析resultMap节点 resultMapElements(context.evalNodes(
    "/mapper/resultMap"));
    // <sql/>节点 sqlElement(context.evalNodes(
    "/mapper/sql"));
    // 解析<insert>等节点 buildStatementFromContext(context.evalNodes(
    "select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }

    XMLMapperBuilder类主要是用于解析映射配置文件,它继承了BaseBuilder抽象类,所有对映射配置文件的解析方法都在这个类中,接下来就分别对这些节点的解析过程进行分析。

    1.<cache-ref />的解析

    1.1 使用方法:

    <!-- 表示使用以下namespace中的cache对象,也就是说和下面namespace共用一个cache对象 -->
    <cache-ref namespace="com.yht.mybatisTest.dao.GoodsDao"/>

    1.2 源码分析:

      private void cacheRefElement(XNode context) {
        if (context != null) {
    // 这个方法是把当前节点的namespace作为key,<cache-ref>节点指定的namespace属性值作为value,存放到HashMap中;前者共用后者的cache对象 configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute(
    "namespace"));
    //cacheRefResolver是一个cache引用解析器,封装了当前XMLMapperBuilder对应的MapperBuilderAssistant对象,和被引用的namespace CacheRefResolver cacheRefResolver
    = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try {
    // 这个方法主要是指定当前mapper文件使用的cache对象,也就是设置MapperBuilderAssistant的currentCache和unresolvedCacheRef字段 cacheRefResolver.resolveCacheRef(); }
    catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } }

    进入 cacheRefResolver.resolveCacheRef();方法:

        public Cache resolveCacheRef() {
    // 进入此方法
    return assistant.useCacheRef(cacheRefNamespace); }

    进入MapperBuilderAssistant类的userCacheRef方法,这个类是XMLMapperBuilder的一个辅助类,用于保存当前mapper文件的namespace,以及使用的cache对象

      public Cache useCacheRef(String namespace) {
        if (namespace == null) {
          throw new BuilderException("cache-ref element requires a namespace attribute.");
        }
        try {
          unresolvedCacheRef = true;
    // 根据被引用的namespace,获取对应的cache对象 Cache cache
    = configuration.getCache(namespace); if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); }
    // 使当前mapper文件的cache对象currentCache指向cache,也就是共用一个cache对象 currentCache
    = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }

    1.3 总结:对于<cahce-ref>节点的解析,就是找到被引用namespace对应的cache对象,然后是当前namespace中的currentCache执向那个cache对象,也就是两者共用一个cache对象。在这个过程中,MapperBuilderAssistant这个辅助类保存了当前mapper文件中的namespace的值,cache对象以及其他属性。

    2.<cache />的解析

    2.1 使用方法:

    <cache
      eviction="FIFO" 
      flushInterval="60000"
      size="512"
      readOnly="true"/>

    cache节点有这几个标签:

    (a)  eviction:缓存的回收策略

    (b) flushInterval:刷新间隔

    (c) size:要缓存的元素数目

    (d) readOnly:如果为true表示只读,不能修改

    (e) type:指定自定义的缓存的全类名

    2.2 源码分析:

     进入XMLMapperBuilder类的cacheElement方法:

      private void cacheElement(XNode context) throws Exception {
        if (context != null) {
    // 获取<cache>节点的type属性,默认值是PERPETUAL String type
    = context.getStringAttribute("type", "PERPETUAL");
    // 获取type属性对应的Cache接口实现 Class
    <? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    // 获取<cache>节点的eviction属性 String eviction
    = context.getStringAttribute("eviction", "LRU"); // 根据eviction属性获取对应的类
    Class
    <? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    // 获取<cache>节点的flushInterval属性 Long flushInterval
    = context.getLongAttribute("flushInterval");
    // 获取size熟悉和readOnly属性 Integer size
    = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); Properties props = context.getChildrenAsProperties();
    // 以上从<cache>节点配置中获取的属性和对应的class,都是为生成cache对象做准备的,此处cache对象的生成使用了构造者模式 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); } }

    通过上面的代码可知:Cache对象是由MapperBuilderAssistant类生成的,进入useNewCache方法:

     public Cache useNewCache(Class<? extends Cache> typeClass,
          Class<? extends Cache> evictionClass,
          Long flushInterval,
          Integer size,
          boolean readWrite,
          Properties props) {
        typeClass = valueOrDefault(typeClass, PerpetualCache.class);
        evictionClass = valueOrDefault(evictionClass, LruCache.class);
    // 这里使用到了构造者模式,CacheBuilder是建造者的角色,Cache是生成的产品,产品类的角色 Cache cache
    = new CacheBuilder(currentNamespace) .implementation(typeClass) .addDecorator(evictionClass) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .properties(props) .build();
    // 将cache对象放到configuration对象的StrctMap中,cache的id作为key,cache对象作为value。此处的cache对象使用了装饰器模式,最底层的对象是PerpetualCache configuration.addCache(cache);
    // 记录当前命名空间使用的cache对象 currentCache
    = cache; return cache; }

    CacheBuilder是Cache的建造者,接下来分析CacheBuilder这个类:

    // 这个类是建造者角色,根据<cache>节点中配置的各种属性来生成不同的Cache对象,<cache>节点中配置的属性都被赋予了这个类中的下面这些属性,然后又为这些属性提供了不同的赋值方法,可以灵活的生成任意组合的Cache对象;这是典型的建造者模式
    public
    class CacheBuilder { private String id; // Cache对象的唯一表示,一般情况下对应Mapper映射文件的namespace private Class<? extends Cache> implementation; // Cache接口的真正实现类,默认是PerpetualCache private List<Class<? extends Cache>> decorators; // 装饰器集合,默认只包含LRUCache.class private Integer size; // Cache的大小 private Long clearInterval; //清理时间周期 private boolean readWrite; // 是否可读写 private Properties properties;// 其它配置信息
    public CacheBuilder(String id) { this.id = id; this.decorators = new ArrayList<Class<? extends Cache>>(); } // 这几个方法就是为生成的Cache对象使用到的方法 public CacheBuilder implementation(Class<? extends Cache> implementation) { this.implementation = implementation; return this; } public CacheBuilder addDecorator(Class<? extends Cache> decorator) { if (decorator != null) { this.decorators.add(decorator); } return this; } public CacheBuilder size(Integer size) { this.size = size; return this; } public CacheBuilder clearInterval(Long clearInterval) { this.clearInterval = clearInterval; return this; } // 生成Cache对象,cache对象是产品角色 public Cache build() {
    //implement为null,decorators为空,则给予默认值 setDefaultImplementations();
    // 根据implement指定的类型,创建Cache对象 Cache cache
    = newBaseCacheInstance(implementation, id);
    // 根据<cache>节点下配置的<properties>信息,初始化Cache对象 setCacheProperties(cache);
    // 如果cache对象的类型是PerpetualCahce类型,那么为其添加decorators集合中的装饰器,cache对象本身使用了装饰器模式
    if (PerpetualCache.class.equals(cache.getClass())) { // issue #352, do not apply decorators to custom caches for (Class<? extends Cache> decorator : decorators) {
    // 为Cache对象添加装饰器 cache
    = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache);// 为cache对象配置属性 }
    // 添加mybatis中提供的标准装饰器 cache
    = setStandardDecorators(cache); } return cache; } }

     3.<resultMap/>解析

    3.1 使用方法:

        <resultMap id="goodsMap" type="goods">
            <id column="id" property="id"/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
            <result column="name" property="name"/>
        </resultMap>

    3.2 源码解析

    <resultMap>节点定义了数据库的结果集和javaBean对象之间的映射关系,在解析<resultMap>节点之前,先看两个类ResultMapping和ResultMap。

    每个ResultMapping对象记录了结果集中的一列与javaBean中一个属性之间的映射关系,看它的属性字段:

      private Configuration configuration; //Configuration对象
      private String property; // 对应节点的property属性,表示的是javaBean中对应的属性
      private String column; // 对应节点的column属性,表示的是从数据库中得到的列名或者列名的别名
      private Class<?> javaType; //对应节点的javaType属性,表示的是一个javaBean的完全限定名,或者一个类型别名
      private JdbcType jdbcType;// 对应节点的jdbcType属性,表示的是进行映射的列的JDBC类型
      private TypeHandler<?> typeHandler;// 对应节点的typeHandler属性,表示的是类型处理器
      private String nestedResultMapId; // 对应节点的resultMap属性   嵌套的结果映射时有用到
      private String nestedQueryId; //对应节点的select属性 嵌套查询时有用到
      private Set<String> notNullColumns;
      private String columnPrefix;
      private List<ResultFlag> flags;
      private List<ResultMapping> composites;
      private String resultSet; //对应节点的resultSet属性
      private String foreignColumn;// 对应节点的foreignColumn属性
      private boolean lazy; //是否延迟加载,对应节点的fetchType属性

    对于ResultMap类,每个<resultMap>节点都会被解析成一个ResutltMap对象,看它的属性:

      private String id;   //<resultMap>节点id的属性
      private Class<?> type; // <resultMap>节点type的属性
      private List<ResultMapping> resultMappings; //ResutlMapping的集合
      private List<ResultMapping> idResultMappings; //记录了映射关系中带有ID标志的映射关系 例如<id>节点和<constructor>节点的<idArg>子节点
      private List<ResultMapping> constructorResultMappings; //记录映射关系中带有Constructor标志的映射关系,例如<constructor>所有子元素
      private List<ResultMapping> propertyResultMappings; // 记录映射关系中不带有Constructor标志的映射关系
      private Set<String> mappedColumns; // 记录所有映射关系中涉及的column熟悉的集合
      private Discriminator discriminator;// 鉴别器 对应<discriminator>节点
      private boolean hasNestedResultMaps; // 是否含有嵌套的结果映射,如果有,则为true
      private boolean hasNestedQueries; // 是否含有嵌套查询,如果有,则为true
      private Boolean autoMapping; //是否开启自动映射

    现在我们进入<resultMap>节点的源码解析部分,进入XMLMapperBuilder的resultMapElement方法:

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取<resutlMap>节点的id属性 String id
    = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    // 获取<resultMap>节点的type属性 String type
    = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
    // 获取<resultMap>节点的extends属性,该属性指定了<resultMap>节点的继承关系 String extend
    = resultMapNode.getStringAttribute("extends");
    // 读取resultMap节点的autoMapping属性 Boolean autoMapping
    = resultMapNode.getBooleanAttribute("autoMapping");
    // 解析type类型 Class
    <?> typeClass = resolveClass(type); Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren();
    // 处理<resultMap>的所有子节点
    for (XNode resultChild : resultChildren) {
    // 处理<constructor>节点
    if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) {
    // 处理<discriminator>节点 discriminator
    = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else {
    // 处理<id>,<result>,<association>,<collection>等节点 ArrayList
    <ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); }
    // 创建ResultMapping对象,并添加到集合中 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver
    = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try {
    // 创建ResultMap对象,并添加到Configuration.resultMaps集合中
    return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }

    接下来,我们分析上面红色字体的方法首先是buildResultMappingFromContext方法,根据字面意思也可以知道,该方法是从上下文环境中获取到的属性信息创建ResultMapping对象,进入该方法:

    private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, ArrayList<ResultFlag> flags) throws Exception {
    // 获取每一个映射关系中 property,column,....的属性值 String property
    = context.getStringAttribute("property"); String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping> emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resulSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 解析javaType,jdbcType和TypeHandler Class
    <?> javaTypeClass = resolveClass(javaType); @SuppressWarnings("unchecked") Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 创建ResultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy); }

    4.<sql/>的解析

    4.1 使用用法

        <sql id="sql_where_key">
            id = #{id}
        </sql>

    4.2 源码解析

      private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    // 遍历<sql>节点
    for (XNode context : list) {
    // 获取databaseId属性 String databaseId
    = context.getStringAttribute("databaseId");
    // 获取id属性 String id
    = context.getStringAttribute("id");
    // 为id添加命名空间 id
    = builderAssistant.applyCurrentNamespace(id, false);
    // 以id为key,context为value存放到Map中
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context); } }

    5.<select>,<insert>等sql节点的解析

    5.1 使用方法:

        <select id="selectGoodsById" resultMap="goodsMap">  
            select * from goods 
                <where>
                  <include refid="sql_where_key" />
                </where>
        </select>

    5.2 源码解析

    在源码解析前,先了解SQLSource接口和MappedStatement类

    SqlSource接口表示映射文件或者注解中描述的sql语句,但是它并不是数据库可执行的sql语句,因为它还可能包含有动态sql语句相关的节点或者占位符等需要解析的元素。

    public interface SqlSource {
    // 根据映射文件或者注解描述的sql语句,以及传入的参数,返回可执行的sql BoundSql getBoundSql(Object parameterObject); }

    MappedStatement表示映射文件中定义的sql节点,它的部分属性如下:

      private String resource;  //节点中id的属性
      private Configuration configuration; 
      private String id;
      private Integer fetchSize;
      private Integer timeout;
      private StatementType statementType;
      private ResultSetType resultSetType;
      private SqlSource sqlSource; //sqlSource对象,对应一条sql语句
      private Cache cache;
    private SqlCommandType sqlCommandType; // SQL的类型,INSERT,SELECT 等

    SQL节点的解析是XMLStatementBuilder类来解析的,进入解析方法的入口:

    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("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);
    
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 以上代码是获取sql节点中的各种属性值,如 useCache,resultMap,resultType,paramterMap,timeout等
    // Include Fragments before parsing 解析SQL语句前,先处理<sql>节点中的<include/>节点 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // 处理selectKey节点 // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver);
    // 解析SQL语句
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 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); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } // 将SQL节点解析为MappedStatement对象,然后放到Configuration.mappedStatements集合中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

    到这里,映射配置文件的整个解析过程就结束了,在下一篇文章中,我们介绍SQL的执行过程。

  • 相关阅读:
    关于 锁的四种状态与锁升级过程 图文详解
    悲观锁与乐观锁的实现(详情图解)
    面试三轮我倒在了一道sql题上——sql性能优化
    我的程序跑了60多小时,就是为了让你看一眼JDK的BUG导致的内存泄漏。
    快来!我从源码中学习到了一招Dubbo的骚操作!
    我从LongAdder中窥探到了高并发的秘籍,上面只写了两个字...
    震惊!ConcurrentHashMap里面也有死循环,作者留下的“彩蛋”了解一下?
    mybatis 逆向工程使用姿势不对,把表清空了,心里慌的一比,于是写了个插件。
    吐血输出:2万字长文带你细细盘点五种负载均衡策略。
    mybatis开发,你用 xml 还是注解?我 pick ...
  • 原文地址:https://www.cnblogs.com/51life/p/9518604.html
Copyright © 2020-2023  润新知