zoukankan      html  css  js  c++  java
  • MyBatis官方教程及源代码解析——mapper映射文件

    缓存

    1.官方文档

    MyBatis 包括一个非常强大的查询缓存特性,它能够非常方便地配置和定制。

    MyBatis 3 中的缓存实现的非常多改进都已经实现了,使得它更加强大并且易于配置。

    默认情况下是没有开启缓存的,除了局部的session 缓存,能够增强变现并且处理循环 依赖也是必须的。要开启二级缓存,你须要在你的 SQL 映射文件里加入一行:

    <cache/>

    字面上看就是这样。

    这个简单语句的效果例如以下:

    ·        映射语句文件里的全部 select语句将会被缓存。

    ·        映射语句文件里的全部 insert,update和 delete 语句会刷新缓存。

    ·        缓存会使用 Least Recently Used(LRU,近期最少使用的)算法来收回。

    ·        依据时间表(比方 no Flush Interval,没有刷新间隔), 缓存不会以不论什么时间顺序 来刷新。

    ·        缓存会存储列表集合或对象(不管查询方法返回什么)的 1024 个引用。

    ·        缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且能够安全地被调用者改动,而不干扰其它调用者或线程所做的潜在改动。

    全部的这些属性都能够通过缓存元素的属性来改动。

    比方:

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

    这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,并且返回的对象被觉得是仅仅读的,因此在不同线程中的调用者之间改动它们会 导致冲突。

    可用的收回策略有:

    ·        LRU –近期最少使用的:移除最长时间不被使用的对象。

    ·        FIFO –先进先出:按对象进入缓存的顺序来移除它们。

    ·        SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。

    ·        WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    默认的是 LRU

    flushInterval(刷新间隔)能够被设置为随意的正整数,并且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

    size(引用数目)能够被设置为随意正整数,要记住你缓存的对象数目和你执行环境的 可用内存资源数目。

    默认值是 1024。

    readOnly(仅仅读)属性能够被设置为 true 或 false。

    仅仅读的缓存会给全部调用者返回缓 存对象的同样实例。因此这些对象不能被改动。这提供了非常重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,可是安全,因此默认是 false。


    2.源代码解析

    缓存的解析比較简单。mybatis依据配置生成一个cache对象,并存入configuration。每一个映射配置文件仅仅能有一个cache,配置多个时仅仅有第一个生效

    //XMLMapperBuilder类
    private void cacheElement(XNode context) throws Exception {
    if (context != null) {
          //获取配置
          String type = context.getStringAttribute("type", "PERPETUAL");
          Class<?

    extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); 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); boolean blocking = context.getBooleanAttribute("blocking", false); Properties props = context.getChildrenAsProperties(); //由builderAssistant对象生成 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } } //MapperBuilderAssistant类 public Cache useNewCache(Class<?

    extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { Cache cache = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); //将cache存入configuration configuration.addCache(cache); //置为当前cache currentCache = cache; return cache; }


    除了定义一个新的缓存,我们还能够直接使用其它映射文件配置的缓存,这就利用到了cache-ref

    <cache-ref namespace="com.someone.application.data.SomeMapper"/>

    要注意的是<cache-ref>的解析在<cache>之前,所以currentCache 对象会选择后者

    private void cacheRefElement(XNode context) {
    if (context != null) {
          //依照两个映射文件的名空间存入configuration
          configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
          CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
          try {
    //这里尽管定义了cacheRefResolver 对象,但终于调用的是MapperBuilderAssistant类的useCacheRef方法
            cacheRefResolver.resolveCacheRef();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteCacheRef(cacheRefResolver);
          }
        }
      }
    public Cache useCacheRef(String namespace) {
        if (namespace == null) {
          throw new BuilderException("cache-ref element requires a namespace attribute.");
        }
        try {
          unresolvedCacheRef = true;
          Cache cache = configuration.getCache(namespace);
          //cache找不到的一种可能是相应的映射文件还未解析,这样的时候会抛出异常
          if (cache == null) {
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
          }
          //假设找到相应cache则currentCache 会置为该对象
          currentCache = cache;
          unresolvedCacheRef = false;
          return cache;
        } catch (IllegalArgumentException e) {
          throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
        }
      }
    

    Sql语句块

    1.官方文档

    这个元素能够被用来定义可重用的 SQL 代码段,能够包括在其它语句中。它能够被静态地(在载入參数) 參数化. 不同的属性值通过包括的实例变化. 比方:

    <sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

    这个 SQL 片段能够被包括在其它语句中,比如:

    <select id="selectUsers" resultType="map">
      select
        <include refid="userColumns"><property name="alias" value="t1"/></include>,
        <include refid="userColumns"><property name="alias" value="t2"/></include>
      from some_table t1
        cross join some_table t2</select>

    属性值能够用于包括的refid属性或者包括的字句里面的属性值,比如:

    <sql id="sometable">
      ${prefix}Table</sql>
    <sql id="someinclude">
      from
        <include refid="${include_target}"/></sql>
    <select id="select" resultType="map">
      select
        field1, field2, field3
      <include refid="someinclude">
        <property name="prefix" value="Some"/>
        <property name="include_target" value="sometable"/>
      </include></select>

    2.源代码解析

    Sql语句块和映射语句在解析的时候会依据DatabaseId来进行区分,假设同一时候找到带有 databaseId 和不带 databaseId 的同样语句,则后者会被舍弃。利用这点,在配置语句的时候能够为不同数据库配置不同的语句。

    //XMLMapperBuilder类中
    private void sqlElement(List<XNode> list) throws Exception {
    //这里的配置取自配置文件里的databaseIdProvider
        if (configuration.getDatabaseId() != null) {
          sqlElement(list, configuration.getDatabaseId());
        }
        sqlElement(list, null);
      }
    
      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);
          //在此处对databaseId配置和当前数据库进行匹配
          if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
    //终于依据ID将整个节点存入
            sqlFragments.put(id, context);
          }
        }
      }
      
      private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    //假设有配置databaseIdProvider,则两者必须一致
        if (requiredDatabaseId != null) {
          if (!requiredDatabaseId.equals(databaseId)) {
            return false;
          }
    } else {
      //假设没有配置databaseIdProvider,则不须要配置databaseId 
          if (databaseId != null) {
            return false;
          }
          // 假设存在同样ID且databaseId不为空。则省略
          if (this.sqlFragments.containsKey(id)) {
            XNode context = this.sqlFragments.get(id);
            if (context.getStringAttribute("databaseId") != null) {
              return false;
            }
          }
        }
        return true;
      }
    

    在最后sql代码块将整个节点都存入Map中,这样做是由于sql能够实现动态复用,因此每次都必须又一次解析sql代码块的值,这些在接下来映射语句的解析部分完毕。


    映射语句

    映射语句是Mapper配置中比較复杂的一部分。一方面能够嵌入sql语句块,还有一方面还有动态Sql。

    //XMLMapperBuilder类中
    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) {
        for (XNode context : list) {
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            //这里又由XMLStatementBuilder类来进行解析
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    //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));
    //这里推断是不是查询语句。影响到后面flushCache和userCache的默认值
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        //1.先处理sql代码块(include)
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        //2.再处理selectKey并移除
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        //3.最后解析SQL语句
        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();
        }
    
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    

    先来看<include>的解析。在官方文档里面能够看到<include>中包括了<propery>子节点。

    用户能够为<propery>配置不同的值来实现动态的复用,假设没有对<propery>进行配置,Mybatis会从XML配置文件里面寻找

    public void applyIncludes(Node source) {
    Properties variablesContext = new Properties();
    //获取XML配置文件里的property值
        Properties configurationVariables = configuration.getVariables();
        if (configurationVariables != null) {
          variablesContext.putAll(configurationVariables);
        }
        applyIncludes(source, variablesContext);
      }
      private void applyIncludes(Node source, final Properties variablesContext) {
    //针对<include>进行解析
        if (source.getNodeName().equals("include")) {
          //这里新定义fullContext对象,保证在一个解析过程中使用同一套值      
    Properties fullContext;
          String refid = getStringAttribute(source, "refid");
          //对refid进行解析(比如refid="${include_target}"的形式)
          refid = PropertyParser.parse(refid, variablesContext);
          Node toInclude = findSqlFragment(refid);
          //这里对<property>进行解析并返回结果
          Properties newVariablesContext = getVariablesContext(source, variablesContext);
          //依据解析结果使用不同的值
          if (!newVariablesContext.isEmpty()) {
            // merge contexts
            fullContext = new Properties();
            fullContext.putAll(variablesContext);
            fullContext.putAll(newVariablesContext);
          } else {
            // no new context - use inherited fully
            fullContext = variablesContext;
          }
          //针对Sql代码块解析,toInclude是<sql>节点
          applyIncludes(toInclude, fullContext);
          if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
          }
          //将<include>替换成相应<sql>
          source.getParentNode().replaceChild(toInclude, source);
          while (toInclude.hasChildNodes()) {
    //插入<sql>节点解析后的sql语句
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
          }
          //最后移除<sql>节点
          toInclude.getParentNode().removeChild(toInclude);
    }else if (source.getNodeType() == Node.ELEMENT_NODE) {
          NodeList children = source.getChildNodes();
          for (int i=0; i<children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext);
          }
        } else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
          // replace variables in all attribute values
          source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
        } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
          // replace variables ins all text nodes
          source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
      }
    
    以上过程终于将<include>替换成相应的sql语句。


    接下来是<selectKey>的解析。我们能够将其视为一种特殊的映射语句。终于结果保存在configuration的keyGenerators中
    private void processSelectKeyNodes(String id, Class<?

    > parameterTypeClass, LanguageDriver langDriver) { List<XNode> selectKeyNodes = context.evalNodes("selectKey"); //也须要推断databaseId if (configuration.getDatabaseId() != null) { parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null); //解析后删除节点 removeSelectKeyNodes(selectKeyNodes); } private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) { for (XNode nodeToHandle : list) { String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX; String databaseId = nodeToHandle.getStringAttribute("databaseId"); if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) { parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId); } } } private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) { String resultType = nodeToHandle.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString())); String keyProperty = nodeToHandle.getStringAttribute("keyProperty"); String keyColumn = nodeToHandle.getStringAttribute("keyColumn"); boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER")); //defaults boolean useCache = false; boolean resultOrdered = false; KeyGenerator keyGenerator = new NoKeyGenerator(); Integer fetchSize = null; Integer timeout = null; boolean flushCache = false; String parameterMap = null; String resultMap = null; ResultSetType resultSetTypeEnum = null; SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass); //这里将<selectKey>当做一种select语句 SqlCommandType sqlCommandType = SqlCommandType.SELECT; //和映射语句一样,<selectKey>解析成MappedStatement对象并保存, builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null); id = builderAssistant.applyCurrentNamespace(id, false); //这里将解析结果保存在configuration中 MappedStatement keyStatement = configuration.getMappedStatement(id, false); configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore)); }


    最后是映射语句的解析

    public void parseStatementNode() {
        //....省略
        
    //3.最后解析SQL语句
    //先是生产SqlSource对象。保存解析后的Sql语句
    //该对象由langDriver产生,这部分主要是和动态Sql有关,临时省略
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    //下面生成KeyGenerator 
    KeyGenerator keyGenerator;
    //keyStatementId 和<SelectKey>的id解析方式一样,这就保证能取到前面<SelectKey>解析的结果
    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();
        }
    
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    

    从以上代码能够看出,映射语句终于会由MapperBuilderAssistant解析成MappedStatement对象。最后看看该过程怎样实现

    //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) {
    //必须cache-ref解析完毕后才干继续
        if (unresolvedCacheRef) {
          throw new IncompleteElementException("Cache-ref not yet resolved");
        }
    
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //最后由statementBuilder构建
        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)
            .resulSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id))
            .resultSetType(resultSetType)
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);
    
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
          statementBuilder.parameterMap(statementParameterMap);
        }
    
        MappedStatement statement = statementBuilder.build();
        configuration.addMappedStatement(statement);
        return statement;
      }
    




  • 相关阅读:
    LeetCode——Basic Calculator
    LeetCode——Sqrt(x)
    LeetCode——Binary Search Tree Iterator
    LeetCode——Search for a Range
    LeetCode——pow(x, n)
    LeetCode——Single Number II
    LeetCode——Summary Ranges
    LeetCode——Largest Number
    LeetCode——Kth Largest Element in an Array
    LeetCode——Implement Stack using Queues
  • 原文地址:https://www.cnblogs.com/llguanli/p/8664212.html
Copyright © 2011-2022 走看看