Mybatis源码学习之全局配置文件和映射文件解析

港控/mmm° 2024-03-16 13:44 112阅读 0赞

全局配置文件和映射文件解析

全局配置文件解析

  1. public static void main(String[] args) throws IOException {
  2. // 读取配置文件
  3. InputStream is = Resources.getResourceAsStream("org/apache/ibatis/builder/MapperConfig1.xml");
  4. // 创建SqlSessionFactory工厂
  5. SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  6. SqlSessionFactory factory = sqlSessionFactoryBuilder.build(is);
  7. // 使用工厂生产SqlSession对象
  8. SqlSession session = factory.openSession();
  9. //使用SqlSession创建Dao接口的代理对象
  10. AuthorMapper authorMapper = session.getMapper(AuthorMapper.class);
  11. List<Author> authors = authorMapper.selectAllAuthors();
  12. //释放资源
  13. session.close();
  14. is.close();
  15. }

下面我们来进行源码分析。

配置文件的解析&创建SqlSessionFactory

配置文件的解析主要涉及到的类如下:XMLConfigBuilder、XPathParser、XPath、XNode,其中XPath、XNode是对
1、build方法内部首先会根据输入流等信息创建XMLConfigBuilder类的实例对象,然后调用XMLConfigBuilder实例的parse方法对配置文件进行解析;这里需要注意的是parse方法最后返回的是一个Configuration对象
在这里插入图片描述

2、parse方法则是调用了XPath对象的evalNode方法对配置文件中的configuration节点进行解析,会把节点内容放在XNode对象中然后返回;
在这里插入图片描述

3、parseConfiguration方法会对configuration节点解析出来的内容再进行解析,会把解析出来的内容放在configuration对象中;实际上配置文件中的内容解析出来后都会存到Configuration中
\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjYs8014-1685886063612)(Pasted%20image%2020230223222648.png)\]

4、parseConfiguration方法中主要做的事如下:

  • 解析 properties节点

    /**

    1. * 解析 properties节点
    2. * <properties resource="mybatis/db.properties" />
    3. * 解析到org.apache.ibatis.parsing.XPathParser#variables
    4. * org.apache.ibatis.session.Configuration#variables
    5. */
    6. // issue #117 read properties first
    7. propertiesElement(root.evalNode("properties"));
  • 解析settings节点

    /**

    1. * 解析我们的mybatis-config.xml中的settings节点
    2. * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
    3. * <settings>
    4. <setting name="cacheEnabled" value="true"/>
    5. <setting name="lazyLoadingEnabled" value="true"/>
    6. <setting name="mapUnderscoreToCamelCase" value="false"/>
    7. <setting name="localCacheScope" value="SESSION"/>
    8. <setting name="jdbcTypeForNull" value="OTHER"/>
    9. ..............
    10. </settings>
    11. *
    12. */
    13. Properties settings = settingsAsProperties(root.evalNode("settings"));
  • 解析

    /**

    1. * 基本没有用过该属性
    2. * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
    3. Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
    4. 解析到:org.apache.ibatis.session.Configuration#vfsImpl
    5. */
    6. loadCustomVfs(settings);
  • 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

    /**

    1. * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
    2. * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
    3. * 解析到org.apache.ibatis.session.Configuration#logImpl
    4. */
    5. loadCustomLogImpl(settings);
  • 解析别名

    /**

    1. * 解析别名
    2. * <typeAliases>
    3. <typeAlias alias="Author" type="cn.tulingxueyuan.pojo.Author"/>
    4. </typeAliases>
    5. <typeAliases>
    6. <package name="cn.tulingxueyuan.pojo"/>
    7. </typeAliases>
    8. 解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
    9. */
    10. typeAliasesElement(root.evalNode("typeAliases"));
  • 解析插件

    /**

    1. * 解析我们的插件(比如分页插件)
    2. * mybatis自带的
    3. * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    4. ParameterHandler (getParameterObject, setParameters)
    5. ResultSetHandler (handleResultSets, handleOutputParameters)
    6. StatementHandler (prepare, parameterize, batch, update, query)
    7. 解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
    8. */
    9. pluginElement(root.evalNode("plugins"));
  • 设置settings

    // 设置settings 和默认值

    1. settingsElement(settings);
  • 解析mybatis环境

    /**

    1. * 解析mybatis环境
    2. <environments default="dev">
    3. <environment id="dev">
    4. <transactionManager type="JDBC"/>
    5. <dataSource type="POOLED">
    6. <property name="driver" value="${jdbc.driver}"/>
    7. <property name="url" value="${jdbc.url}"/>
    8. <property name="username" value="root"/>
    9. <property name="password" value="Zw726515"/>
    10. </dataSource>
    11. </environment>
    12. <environment id="test">
    13. <transactionManager type="JDBC"/>
    14. <dataSource type="POOLED">
    15. <property name="driver" value="${jdbc.driver}"/>
    16. <property name="url" value="${jdbc.url}"/>
    17. <property name="username" value="root"/>
    18. <property name="password" value="123456"/>
    19. </dataSource>
    20. </environment>
    21. </environments>
    22. * 解析到:org.apache.ibatis.session.Configuration#environment
    23. * 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
    24. */
    25. // read it after objectFactory and objectWrapperFactory issue #631
    26. environmentsElement(root.evalNode("environments"));
  • 解析数据库厂商

    /**

    1. * 解析数据库厂商
    2. <databaseIdProvider type="DB_VENDOR">
    3. <property name="SQL Server" value="sqlserver"/>
    4. <property name="DB2" value="db2"/>
    5. <property name="Oracle" value="oracle" />
    6. <property name="MySql" value="mysql" />
    7. </databaseIdProvider>
    8. * 解析到:org.apache.ibatis.session.Configuration#databaseId
    9. */
    10. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  • 解析类型处理器

    /**

    1. * 解析我们的类型处理器节点
    2. * <typeHandlers>
    3. <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    4. </typeHandlers>
    5. 解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
    6. */
    7. typeHandlerElement(root.evalNode("typeHandlers"));
  • 解析mapper文件

    /**

    1. * 解析mapper文件(SQL映射文件)
    2. *
    3. resource:来注册我们的class类路径下的
    4. url:来指定我们磁盘下的或者网络资源的
    5. class:
    6. 若注册Mapper不带xml文件的,这里可以直接注册
    7. 若注册的Mapperxml文件的,需要把xml文件和mapper文件同名 同路径
    8. -->
    9. <mappers>
    10. <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
    11. <mapper class="com.tuling.mapper.DeptMapper"></mapper>
  1. <package name="com.tuling.mapper"></package>
  2. -->
  3. </mappers>
  4. * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
  5. 2.
  6. */
  7. mapperElement(root.evalNode("mappers"));

到这里配置文件就解析完了,mybatis会根据configuration对象创建SqlSessionFactory类的对象。

SQL映射文件解析

上面解析全局配置文件的最后一行代码就是解析SQL映射文件的入口,下面我们来分享一下SQL映射文件的解析。
mapperElement方法中首先判断注册SQL映射的方式(通过package、resource、url还是class),然后再去解析对应的mapper文件。
在这里插入图片描述
parse方法中首先会判断mapper文件是否被加载过,如果被加载过就不需要再次解析了。

  • configurationElement方法中会解析mapper文件中的各个节点;
  • cacheRefElement(context.evalNode(“cache-ref”)):处理缓存;
  • parameterMapElement(context.evalNodes(“/mapper/parameterMap”)):处理sql参数;
  • resultMapElements(context.evalNodes(“/mapper/resultMap”)):处理返回值;
  • sqlElement(context.evalNodes(“/mapper/sql”)):解析sql节点;
  • buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)):解析sql语句(insert/update/delete/select)
    在这里插入图片描述

    /**

    • 方法实现说明:解析我们的节点
    • @param context document节点
      */
      private void configurationElement(XNode context) {

      try {

      //解析namespace属性
      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的缓存引用一致
    //
    // 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
    // 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs

    1. cacheRefElement(context.evalNode("cache-ref"));

    // 解析我们的cache节点
    //
    // 解析到:org.apache.ibatis.session.Configuration#caches
    // org.apache.ibatis.builder.MapperBuilderAssistant#currentCache

    1. cacheElement(context.evalNode("cache"));
    2. //解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
    3. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    4. //解析我们的resultMap节点
    5. //解析到:org.apache.ibatis.session.Configuration#resultMaps
    6. // 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
    7. resultMapElements(context.evalNodes("/mapper/resultMap"));
    8. /**
    9. * 解析我们通过sql节点
    10. * 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
    11. * 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
    12. * 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
    13. */
    14. sqlElement(context.evalNodes("/mapper/sql"));
    15. /**
    16. * 解析我们的select | insert |update |delete节点
    17. * 解析到org.apache.ibatis.session.Configuration#mappedStatements
    18. */
    19. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    20. } catch (Exception e) {
    21. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    22. }

    }

我们看一下buildStatementFromContext方法,这个方法最终会把节点解析成MappedStatement对象
在这里插入图片描述
下面3张图都是一个方法中的代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面一张图中的createSqlSource方法会解析出Sql,接下来我们看下此方法是怎么解析的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后解析出来的sql如下图所示:
在这里插入图片描述
到这里sql就解析完成了。

Mybatis中关于sql解析的一些类:

  • XMLConfigBuilder:解析mybatis中configLocation属性中的全局xml文件,内部会使用XMLMapperBuilder解析各个xml文件
  • XMLMapperBuilder:遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部会使用XMLStatementBuilder处理xml中的每个节点
  • XMLStatementBuilder:解析mapper配置文件中的节点,如select、insert等,内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会放到Configuration的mappedStatements属性中
  • XMLScriptBuilder:解析xml中各个节点sql部分的Builder;

Configuration中使用map存储MappedStatement对象,key是mapper接口中的方法名;MappedStatement中存储的信息如下:
在这里插入图片描述sqlSource是解析出来的sql。
在这里插入图片描述

XMLScriptBuilder类结构如下图所示。这个类中有许多继承了NodeHandler接口的内部类,在解析动态sql的时候,会使用whereHandler去解析where节点,IfHandler解析if节点,其它的类似。
在这里插入图片描述

下面给出的是一条update语句,parseDynamicTags的解析流程如下:

  1. <update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
  2. UPDATE users
  3. <trim prefix="SET" prefixOverrides=",">
  4. <if test="name != null and name != ''">
  5. name = #{name}
  6. </if>
  7. <if test="age != null and age != ''">
  8. , age = #{age}
  9. </if>
  10. <if test="birthday != null and birthday != ''">
  11. , birthday = #{birthday}
  12. </if>
  13. </trim>
  14. where id = ${id}
  15. </update>

parseDynamicTags方法的返回值是一个List,也就是一个Sql节点集合

  1. 首先根据update节点(Node)得到所有的子节点,分别是3个子节点

    • 文本节点 UPDATE users
    • trim子节点
    • 文本节点 \n where id = #{id}
  2. 遍历各个子节点

    • 如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode
    • 如果节点类型是元素,说明该update节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点
  3. 遇到子节点是元素的话,重复以上步骤

参考

  1. Mybatis官方文档
  2. Mybatis视频

发表评论

表情:
评论列表 (有 0 条评论,112人围观)

还没有评论,来说两句吧...

相关阅读