MyBatis 源码系列:MyBatis 解析配置文件、二级缓存、SQL

用CubeMX HAL库5分钟搞定STM32F4外部中断,告别标准库的繁琐配置 本文详细介绍了如何使用STM32CubeMX和HAL库在5分钟内快速配置STM32F4的外部中断(EXIT),告别传统标准库的繁琐操作。通过图形化界面设置GPIO和NVIC参数,自动生成初始化代码,极大提升开发效率,特别适合项目周期紧张或初学者。 阅读详情

解析全局配置文件

启动流程分析

String resource = "mybatis-config.xml";
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中,进去一探究竟:

//整个过程就是将配置文件解析成Configration对象,然后创建SqlSessionFactory的过程
//Configuration是SqlSessionFactory的一个内部属性 
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        if (reader != null) {
          reader.close();
        }
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

下面是解析配置文件的核心方法:

  public Configuration parse() {

    //若已经解析过了 就抛出异常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    /**
     * 解析我们的mybatis-config.xml的
     * 节点
     * <configuration>
     * </configuration>
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  /**
   * 解析我们mybatis-config.xml的 configuration节点
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      /**
       * 解析 properties节点
       *     <properties resource="db.properties" />
       *     解析到org.apache.ibatis.parsing.XPathParser#variables
       *          org.apache.ibatis.session.Configuration#variables
       */
      propertiesElement(root.evalNode("properties"));
      /**
       * 解析我们的mybatis-config.xml中的settings节点
       * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
       * <settings>
       <setting name="cacheEnabled" value="true"/>
       <setting name="lazyLoadingEnabled" value="true"/>
       <setting name="mapUnderscoreToCamelCase" value="false"/>
       <setting name="localCacheScope" value="SESSION"/>
       <setting name="jdbcTypeForNull" value="OTHER"/>
       ..............
       </settings>
       *
       */
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /**
       * 基本没有用过该属性
       * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
       Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
       解析到:org.apache.ibatis.session.Configuration#vfsImpl
       */
      loadCustomVfsImpl(settings);
      /**
       * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
       * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
       * 解析到org.apache.ibatis.session.Configuration#logImpl
       */
      loadCustomLogImpl(settings);
      /**
       * 解析我们的别名
       * <typeAliases>
       <typeAlias alias="User" type="com.mcode.entity.User"/>
       </typeAliases>
       <typeAliases>
       <package name="com.mcode.entity"/>
       </typeAliases>
       解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
       除了自定义的,还有内置的
       */
      typeAliasesElement(root.evalNode("typeAliases"));
      /**
       * 解析我们的插件(比如分页插件)
       * mybatis自带的
       * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
       ParameterHandler (getParameterObject, setParameters)
       ResultSetHandler (handleResultSets, handleOutputParameters)
       StatementHandler (prepare, parameterize, batch, update, query)
       解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
       */
      pluginsElement(root.evalNode("plugins"));
      /**
       * 可以配置  一般不会去设置
       * 对象工厂 用于反射实例化对象、对象包装工厂、
       * 反射工厂 用于属性和setter/getter 获取
       */
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));

      // 设置settings 和默认值到configuration
      settingsElement(settings);

      /**
       * 解析我们的mybatis环境
       <environments default="dev">
       <environment id="dev">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
       <property name="driver" value="${jdbc.driver}"/>
       <property name="url" value="${jdbc.url}"/>
       <property name="username" value="root"/>
       <property name="password" value="Zw726515"/>
       </dataSource>
       </environment>
       </environments>
       *  解析到:org.apache.ibatis.session.Configuration#environment
       *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
       */
      environmentsElement(root.evalNode("environments"));
      /**
       * 解析数据库厂商
       *     <databaseIdProvider type="DB_VENDOR">
       <property name="SQL Server" value="sqlserver"/>
       <property name="DB2" value="db2"/>
       <property name="Oracle" value="oracle" />
       <property name="MySql" value="mysql" />
       </databaseIdProvider>
       *  解析到:org.apache.ibatis.session.Configuration#databaseId
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /**
       * 解析我们的类型处理器节点
       * <typeHandlers>
       <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
       </typeHandlers>
       解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
       */
      typeHandlersElement(root.evalNode("typeHandlers"));
      /**
       * 解析我们的mapper
       *
       resource:来注册我们的class类路径下的
       url:来指定我们磁盘下的或者网络资源的
       class:
       若注册Mapper不带xml文件的,这里可以直接注册
       若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
       -->
       <mappers>
       <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
       <mapper class="com.mcode.mapper.EmployeeMapper"></mapper>


       <package name="com.mcode.mapper"></package>
       -->
       </mappers>
       * package
       *     ·解析mapper接口代理工厂(传入需要代理的接口) 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
       ·解析mapper.xml  最终解析成MappedStatement 到:org.apache.ibatis.session.Configuration#mappedStatements
       */
      mappersElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

image

上面解析流程结束后会生成一个Configration对象,包含所有配置信息,然后会创建一个SqlSessionFactory对象,这个对象包含了Configration对象。

简单总结

对于MyBatis启动的流程(获取SqlSession的过程)这边简单总结下:

  • SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

image

二级缓存解析

解析二级缓存

解析mapper文件

  private void mappersElement(XNode context) throws Exception {
    if (context == null) {
      return;
    }
    /**
     * 获取我们mappers节点下的一个一个的mapper节点
     */
    for (XNode child : context.getChildren()) {
      /**
       * 判断我们mapper是不是通过批量注册的
       * <package name="com.mcode.mapper"></package>
       */
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        /**
         * 判断从classpath下读取我们的mapper
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        String resource = child.getStringAttribute("resource");
        /**
         * 判断是不是从我们的网络资源读取(或者本地磁盘得)
         * <mapper url="D:/mapper/EmployeeMapper.xml"/>
         */
        String url = child.getStringAttribute("url");
        /**
         * 解析这种类型(要求接口和xml在同一个包下)
         * <mapper class="com.mcode.mapper.DeptMapper"></mapper>
         *
         */
        String mapperClass = child.getStringAttribute("class");
        /**
         * 我们得mappers节点只配置了
         * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
         */
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          /**
           * 把我们的文件读取出一个流
           */
          try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            /**
             * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
             */
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                configuration.getSqlFragments());
            /**
             * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
             */
            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 new BuilderException(
              "A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }

解析mapper

  /**
   * 真正的去解析我们的Mapper.xml(EmployeeMapper.xml)
   */
  public void parse() {
    /**
     * 判断当前的Mapper是否被加载过
     */
    if (!configuration.isResourceLoaded(resource)) {
      /**
       * 真正的解析我们的 <mapper namespace="com.mcode.mapper.EmployeeMapper">
       *
       */
      configurationElement(parser.evalNode("/mapper"));
      /**
       * 把资源保存到我们Configuration中
       */
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  /**
   * 解析我们的<mapper></mapper>节点
   * @param context
   */
  private void configurationElement(XNode context) {
    try {
      /**
       * 解析我们的namespace属性
       * <mapper namespace="com.cmode.mapper.EmployeeMapper">
       */
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      /**
       * 保存我们当前的namespace  并且判断接口完全类名==namespace
       */
      builderAssistant.setCurrentNamespace(namespace);
      /**
       * 解析我们的缓存引用
       * 说明我当前的缓存引用和DeptMapper的缓存引用一致
       * <cache-ref namespace="com.tuling.mapper.DeptMapper"></cache-ref>
       解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
       异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
       */
      cacheRefElement(context.evalNode("cache-ref"));
      /**
       * 解析我们的cache节点
       * <cache ></cache>
       解析到:org.apache.ibatis.session.Configuration#caches
       org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
       */
      cacheElement(context.evalNode("cache"));
      /**
       * 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /**
       * 解析我们的resultMap节点
       * 解析到:org.apache.ibatis.session.Configuration#resultMaps
       *    异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
       *
       */
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /**
       * 解析我们通过sql片段
       *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
       *   其实等于 org.apache.ibatis.session.Configuration#sqlFragments
       *   因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
       */
      sqlElement(context.evalNodes("/mapper/sql"));
      /**
       * 解析我们的select | insert |update |delete节点
       * 解析到org.apache.ibatis.session.Configuration#mappedStatements
       */
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

解析Cache

  /**
   * 解析cache
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      //解析cache节点的type属性
      String type = context.getStringAttribute("type", "PERPETUAL");
      // 根据别名(或完整限定名)  加载为Class
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        /*获取缓存过期策略:默认是LRU
      LRU – 最近最少使用:移除最长时间不被使用的对象。(默认)
      FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
      WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
      */
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
      Long flushInterval = context.getLongAttribute("flushInterval");
      //size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
      Integer size = context.getIntAttribute("size");
      //只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //把缓存节点加入到Configuration中
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

image

缓存中的调用过程

image

缓存中使用的设计模式

image

解析SQL

buildStatementFromContext

/**
   * 方法实现说明:解析我们得得select|update|delte|insert节点然后
   * 创建我们得mapperStatment对象
   * @param list:所有的select|update|delte|insert节点
   * @param requiredDatabaseId:判断有没有数据库厂商Id
   */
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    /**
     * 循环我们的select|delte|insert|update节点
     */
    for (XNode context : list) {
      /**
       * 创建一个xmlStatement的构建器对象
       */
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

parseStatementNode

 public void parseStatementNode() {
    /**
     * 我们的insert|delte|update|select 语句的sqlId
     */
    String id = context.getStringAttribute("id");
    /**
     * 判断我们的insert|delte|update|select  节点是否配置了
     * 数据库厂商标注
     */
    String databaseId = context.getStringAttribute("databaseId");

    /**
     * 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
     */
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    /**
     * 获得节点名称:select|insert|update|delete
     */
    String nodeName = context.getNode().getNodeName();
    /**
     * 根据nodeName 获得 SqlCommandType枚举
     */
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    /**
     * 判断是不是select语句节点
     */
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    /**
     *  获取flushCache属性
     *  默认值为isSelect的反值:查询:flushCache=false   增删改:flushCache=true
     */
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    /**
     * 获取useCache属性
     * 默认值为isSelect:查询:useCache=true   增删改:useCache=false
     */
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    /**
     * resultOrdered:  是否需要分组:
     *  select * from user-->User{id=1, name='User1', groups=[1, 2], roles=[1, 2, 3]}
     */
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    /**
     * 解析我们的sql公用片段
     *     <select id="qryEmployeeById" resultType="Employee" parameterType="int">
              <include refid="selectInfo"></include>
              employee where id=#{id}
          </select>
        将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中
     */
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    /**
     * 解析我们sql节点的参数类型
     */
    String parameterType = context.getStringAttribute("parameterType");
    // 把参数类型字符串转化为class
    Class<?> parameterTypeClass = resolveClass(parameterType);

    /**
     * 查看sql是否支撑自定义语言
     * <delete id="delEmployeeById" parameterType="int" lang="mcodeLang">
     <settings>
          <setting name="defaultScriptingLanguage" value="mcodeLang"/>
     </settings>
     */
    String lang = context.getStringAttribute("lang");
    /**
     * 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
     */
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    /**
     * 解析我们<insert 语句的的selectKey节点, 一般在oracle里面设置自增id
     */
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    /**
     * 我们insert语句 用于主键生成组件
     */
    KeyGenerator keyGenerator;
    /**
     * selectById!selectKey
     * id+!selectKey
     */
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    /**
     * 把我们的命名空间拼接到keyStatementId中
     * com.mcode.mapper.Employee.saveEmployee!selectKey
     */
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    /**
     *<insert id="saveEmployee" parameterType="com.mcode.entity.Employee" useGeneratedKeys="true" keyProperty="id">
     *判断我们全局的配置类configuration中是否包含以及解析过的主键生成器对象
     */
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {

      /**
       * 若我们<insert 配置了useGeneratedKeys 那么就取useGeneratedKeys的配置值,
       * 否者就看我们的mybatis-config.xml配置文件中是配置了
       * <setting name="useGeneratedKeys" value="true"></setting> 默认是false
       * 并且判断sql操作类型是否为insert
       * 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
       * 否则就是NoKeyGenerator.INSTANCE
       */
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    /**
     * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
     * sql脚本对象  .  解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
     */
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    /**
     * STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
     */
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    /**
     * 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
     */
    Integer fetchSize = context.getIntAttribute("fetchSize");
    /**
     * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
     */
    Integer timeout = context.getIntAttribute("timeout");
    /**
     * 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
     */
    String parameterMap = context.getStringAttribute("parameterMap");
    /**
     * 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
     * 可以使用 resultType 或 resultMap,但不能同时使用
     */
    String resultType = context.getStringAttribute("resultType");
    /**解析我们查询结果集返回的类型     */
    Class<?> resultTypeClass = resolveClass(resultType);
    /**
     * 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
     * 可以使用 resultMap 或 resultType,但不能同时使用。
     */
    String resultMap = context.getStringAttribute("resultMap");

    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }

    /**
     * 解析 keyProperty  keyColumn 仅适用于 insert 和 update
     */
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    /**
     * 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
     */
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

createSqlSource

  /**
   * 方法实现说明:创建我们的sqlSource对象
   * @param configuration:全局配置
   * @param script:脚本类型
   * @param parameterType:参数类型
   */
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

parseScriptNode

 public SqlSource parseScriptNode() {
    /**
     * 递归解析-组合设计模式  selectById这个sql元素会解析成
     *    1层  MixedSqlNode <SELECT>
     *    2层  WhereSqlNode <WHERE>
     *    3层  IfSqlNode <IF>
     *       test="条件表达式"
     *
     *  contexts= sql语句分: 1.TextSqlNode 带${}   2.StaticTextSqlNode
     */
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      // 动态Sql源
      // 动态Sql 就是还需要后续执行时根据传入参数动态解析Sql(因为有<if>等,还要拼接${}sql)
      //    和参数ParameterMappings   也会在后续执行解析,因为动态条件肯定会有动态参数
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 静态Sql源  如果没有动态标签(<if>、<where>等) 以及 没有${}  就是静态Sql源
      // 静态Sql 就是在这里就解析了Sql  和参数ParameterMappings   后续执行就不用解析了
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    // 其实他们的区别就是动态sql 需要在查询的时候解析 因为有动态sql 和拼接${}
    //                  静态sql 已经在这里确定好sql. 和参数ParameterMapping,
    return sqlSource;
  }

image

Mybatis入门到源码解析 MyBatis是一款优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接、创建Statement等繁杂的过程。持久化持久化是将程序数据在持久状态和瞬时状态间转换的机制。(理解一下这个持久状态转化为瞬时状态,持久化是mybatis最重要的特性)即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。 阅读详情

相关推荐

深入解析MyBatis源码与实战

摘要:本文系统解析MyBatis核心架构与实现原理,涵盖配置解析、SQL执行、结果映射三大模块。通过动态SQL生成、缓存机制(一级/二级缓存)、插件开发(Interceptor接口)等典型示例,深入剖析源码实现。结合SpringBoot集成场景,展示CRUD操作、动态SQL拼接、事务管理、多数据源配置等实战方案,并给出批量操作优化(BatchExecutor)、类型处理器扩展等高级特性实现。特别说明流程图关键节点:从SqlSessionFactory构建到Executor执行,通过StatementHand

keny-大成的博客 1361

NoSuchMethodError: ‘void org.apache.ibatis.session.Configuration.parsePendingMethods(boolean)

就是这玩意儿版本太低了,在test里测试就会显示找不到parsePending,但是一直没想到是这个原因,但是它最高也就3.0.3,把它删了就行了。在创建项目的时候,由于勾选了mybatis framework,所以在pom.xml里会自动创建一个。找遍了百度都没有说这个问题。

qq_36475796的博客 1746

深入MyBatis源码:架构解析、SQL执行流程与缓存机制详解

在Java持久层框架中,ORM(对象关系映射)技术是连接应用程序与关系型数据库的核心桥梁。其基本原理是通过元数据映射,将Java对象与数据库表结构进行关联,自动完成SQL生成、参数绑定和结果集转换。这一技术极大地提升了开发效率,降低了数据库操作的复杂性。从技术价值看,优秀的ORM框架能有效管理数据库连接、事务和缓存,是构建高性能、可维护后端系统的关键组件。在实际应用场景中,无论是电商系统的订单处理,还是内容管理平台的数据查询,都离不开对SQL执行流程和缓存机制的精细控制。本文将聚焦MyBatis这一广泛使用

weixin_33801856的博客 750

Mybatis 源码分析(一)

我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,在它的属性里还有各种各样的容器最后返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例最后时序图。

cfa3288的博客 831

深度解读MyBatis源码 - 从SqlSession到SQL执行入口

Hello!大家好,今天我们来一场深度的MyBatis源码之旅。本篇文章的目标是让你彻底搞懂MyBatis中SqlSession的工作原理及其与SQL执行的关系。SqlSession作为MyBatis与数据库沟通的“桥梁”,在数据持久化的流程中扮演了核心角色。SqlSession可以说是MyBatis操作数据库的“会话窗口”,也是我们用来实现增删查改的最基础工具。虽然MyBatis帮我们封装了很多方便的功能,但理解它的内部原理能帮我们写出更优雅、更高效的代码。

喵手的博客 1477

树上柑橘检测实战:自制数据集与YOLO训练全流程复盘

目标检测模型的性能上限往往由训练数据决定,尤其在农业视觉这样的复杂场景中,公开数据集与真实环境的差异常导致模型漏检、误检频发。高质量的标注数据是模型泛化的基础,从采集、清洗到统一标注规范,每一步都直接影响最终效果。对于检测任务而言,VOC与YOLO两种主流标注格式的转换是工程落地的常见环节,理解坐标归一化原理并做好校验,能有效避免训练事故。在模型训练阶段,合理划分数据集、谨慎选择数据增强策略,并结合切片推理解决小目标漏检,是果园场景下提升精度的关键。这套基于2511张果树图像的单类别检测流程,不仅支撑了柑橘

weixin_29057163的博客 230

MyBatis 源码解读(三)指定VFS 的实现

loadCustomVfs 指定VFS 的实现 解析配置的文件加载系统 loadCustomVfs(settings); VFS :虚拟文件系统,主要是通过程序能够方便读取本地文件系统中的文件资源,通过该配置可以加载自定义的虚拟文件系统应用程序,VFS详细解释参见《百度百科-VFS》 private void loadCustomVfs(Properties props) throws ClassNotFoundException { String value = props.getProperty

坚定的小辣鸡在努力 1160

Ubuntu18.04 SoftRoCE安装教程

目标:创建两台Ubuntu18.04虚拟机,安装SoftRoCE环境,一台作为Server,一台作为Client,模拟RDMA通信环境,熟悉RDMA的通信方式。

GGGYL111的博客 1548

NoSuchMethodError: ‘void org.apache.ibatis.session.Configuration.parsePendingMethods(boolean)‘

因为在Spring Boot数据源初始化的测试中报错,根据错误日志分析,核心问题是 NoSuchMethodError: 'void org.apache.ibatis.session.Configuration.parsePendingMethods(boolean)'MyBatis-Plus 已包含完整的 MyBatis 集成,同时使用两者会导致版本冲突。,这是由于 MyBatis 和 MyBatis-Plus 版本不兼容导致的。版本过低,没有这个方法。

weixin_75256457的博客 939

Abaqus传热分析HETVAL和USDFLD子程序联合

Hetval子程序常用于定义内部热源,该子程序可以定义材料内部的生热量(如相变、高分子结晶生热),并且能够调用state variable 状态变量,可以与USDFLD子程序联合使用。HETVAL子程序用于计算传热系数并在单元上定义,USDFLD子程序用于在每个时间布更新温度场。

qq_41900477的博客 2660

科研生产力革命:手把手教你打造基于大模型的Research Agent(文献调研+论文写作+思路激发)

本文介绍了如何利用Research Agent(科研智能体)提升科研效率。Research Agent能自主完成文献检索、信息整合和报告生成全流程,省去50%以上的基础工作时间。文章详细讲解了搭建运行环境的方法,包括工具准备和MetaGPT配置,并提供了两种实战方案:基础版Search-O1和专业版MetaGPT Research Agent。此外,还介绍了如何为垂直领域定制Agent,以及优化检索效果、改善报告逻辑等技巧。最后列举了学术研究、行业分析、内容创作和个人学习四大应用场景,帮助用户将AI从聊天工

LXY_LOVE36的博客 266

MyBatis源码剖析之二级缓存细节

MyBatis是一个流行的Java持久化框架,它提供了许多功能,包括支持一级缓存和二级缓存。一级缓存是默认开启的,它是在SqlSession层面的缓存。在同一个SqlSession中,如果执行了相同的SQL语句,那么第二次执行将从缓存中获取结果,而不是再次查询数据库。一级缓存是SqlSession级别的,当SqlSession被关闭时,缓存也将被清空。二级缓存是在Mapper级别的缓存,它可以跨越多个SqlSession。

write less , do more 3646

风机叶片小规模振动监测数据集--读取测试

ETH风电叶片缺陷数据集–读取测试 数据简介 提示:这里可以添加本文要记录的大概内容: 结构健康监测是保障重要机电设备、建筑设施健康的重要技术手段。 一、lvm_read包安装 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务

cnjs1994的博客 2850

Mybatis从源码分析——启动到解析配置文件再到执行SQL语句过程

try {//将XML配置文件构建为Configuration配置类// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory// 数据源 执行器 DefaultSqlSessiontry {// 执行查询 底层执行jdbc}finally {从配置文件,通常是XML配置文件,获得SqlSessionFactory从SqlSessionFactory中获得SqlSession。

qq_44027353的博客 1013

京东运维面试题及参考答案

cgroup(Control Group)是 Linux 内核提供的一种可以限制、控制与分离一个进程组群的资源(如 CPU、内存、磁盘 I/O 等)的机制。通过 cgroup,可以为不同的进程或进程组分配特定的资源配额,实现资源的隔离和管理。比如,在一个服务器上运行多个容器时,可以使用 cgroup 来限制每个容器对 CPU 和内存的使用,防止某个容器占用过多资源而影响其他容器的运行。cgroup 还可以对进程进行优先级设置,确保关键进程能够优先获得资源。

大模型大数据攻城狮的专栏 1236

大模型测试人员完整学习体系(分层成长路线 + 知识框架 + 落地实操)

本文系统构建了大模型测试工程师的4级能力成长体系:L1(测试执行)-L2(专项测试)-L3(质量体系)-L4(行业标准),详细拆解了每个阶段的核心技能图谱。重点包括:入门层需掌握传统测试基础和大模型产品逻辑;进阶层需精通Prompt用例设计、RAG测试、安全对齐、自动化评测等专项能力;高级层聚焦评测平台搭建与质量体系建设;专家层深入算法对齐与行业标准制定。文章配套提供了分阶段技术工具栈、3-12个月学习路径及5类实战项目建议,形成了一套完整的大模型测试人才能力培养框架。

SmallBull的博客 455

【MyBatis 源码解析】MyBatis执行SQL源码解析(来看看mybatis是如果封装JDBC的)

看完视频之后自己去 debug 的源码,纯个人理解,仅供参考

qq_55477069的博客 2151

基于51单片机设计的花样流水灯设计

花样流水灯是一种常见的LED灯效果,通常由多个LED灯组成,通过控制每个LED灯的亮灭顺序和时间,实现花样流水灯的效果。在51单片机中,可以使用定时器和端口控制来实现这一效果。

3430

【MyBatis 源码拆解系列】JVM 级别缓存能力设计:MyBatis 的一、二级缓存如何设计?

最后在总结一下,MyBatis 设计了一级缓存、二级缓存,虽然二级缓存我们并不使用,但是可以了解他的一个设计原理,这里总结一下重点内容:二级缓存:二级缓存是跨 SqlSession 级别的,因此是在 MappedStatement 中存储,MappedStatement 是解析 xml 文件时构建的,因此生命周期和 MyBatis 是一直的,因此可以跨 SqlSession二级缓存的能力支持是通过来完成的,用到了装饰器模式,BaseExecutor 提供一些公有的一级缓存能力。

qq_45260619的博客 1567

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

mybatis的一级二级缓存详解及源码解剖

一级缓存是指在同一个SqlSession中,对于相同的查询语句和参数,第一次查询的结果会被缓存到内存中,后续的查询会直接从缓存中获取结果,而不会再次查询数据库。一级缓存是MyBatis默认开启的,可以通过在SqlSession中调用clearCache()方法来清空缓存。二级缓存是指在多个SqlSession中,对于相同的查询语句和参数,第一次查询的结果会被缓存到内存中,后续的查询会直接从缓存中获取结果,而不会再次查询数据库。二级缓存是需要手动开启的,可以通过在Mapper.xml文件中添加标签来开启。

qq_48157004的博客 3004

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Mybatis 一、二级缓存源码解析

Mybatis缓存一. 什么是Mybatis缓存二. 一级缓存2.1 一级缓存演示2.2 一级缓存是什么?2.3 一级缓存什么时候被创建的?三. 二级缓存3.1 二级缓存演示 一. 什么是Mybatis缓存 缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库进行交互,进而提高响应速度。 mybatis也提供了对缓存的支持,分为一级缓存和二级缓存,可以通过下图来理解: ①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对

weixin_44304847的博客 969

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

MyBatis源码分析の配置文件解析

本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装,屏蔽了传统JDBC与数据库进行交互,组装参数,获取查询结果并自己封装成对象的繁琐过程。原生MyBatis首先需要配置> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!> <!</> <

西岭千秋雪的博客 731

Mybatis源码系列5-二级缓存

在二级缓存的设计上,MyBatis大量地运用了装饰者模式,如CachingExecutor, 以及各种Cache接口的装饰器

2017年 430

MyBatis 源码解析——SQL解析

不想被抓取,随便写点.....

k7c9。 2010

mybatis 源码刨析扩展之 二级缓存

目录一、复习一下二级缓存内容1、启动二级缓存1)开启全局二级缓存配置2)在需要使用二级缓存的 Mapper 配置文件中配置标签3)在具体 CURD 标签上配置 useCache=true二、源码刨析1、配置初始化,标签 的解析1)XMLConfigBuilder2)我们来看看解析 Mapper.xml3)看看是如何构建 Cache 对象的4)buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 将 Cach

jason559的博客 436

MyBatis执行一条sql语句的流程(源码解析)

加载配置文件:通过类加载器ClassLoaderWrapper以及一些其他的类加载器加载mybatis配置文件获得输入流。SqlsessionFactory的创建。

m0_71338251的博客 1864
上一篇: Asp .Net Core 系列:Asp .Net Core 配置 System.Text.Json
下一篇: Mybatis 源码系列:领略设计模式在 Mybatis 其中的应用
Code技术分享
博客等级 码龄7年 3327粉丝 87原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值