mybatis 配置初始化过程(1)

开篇

 过去一周忙着上线一个线上服务,没时间阅读源码,幸好服务已经顺利上线,可以抽空开始mybatis的系列了,没错这周开始准备开启mybatis的整个系列,欢迎大家订阅。

 按照我现在粗浅的理解,从mybatis的使用过程来看基本可以分为三大步骤,分别是:

  • 配置加载 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  • 会话建立 SqlSession sqlSession = sqlSessionFactory.openSession();
  • 执行查询 User user = sqlSession.selectOne("userTest.selectUser", 1);

 这篇博文主要讲解下配置加载的过程,由于配置加载涉及很多标签的加载,这篇文章暂时只分析宏观的过程,细节每个标签的加载后面文章再分析,核心需要记住mybatis的核心是 all in configuration

补充一点大家不要把mybatis和spring使用mybatis直接混在一起,因为spring和mybatis之间隔了一层spring-mybatis的实现,我们这边只讲解最纯的mybatis。


解析过程概述

 在mybatis的使用过程中,我们构建一个配置的输入reader并通过SqlSessionFactoryBuilder解析配置生成SqlSessionFactory,我们的解析过程就在创建SqlSessionFactory的过程当中。

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        String resource = "configuration.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(reader);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("userTest.selectUser", 1);
        System.out.println(user.getUsername());
        sqlSession.close();
    }
}


配置解析过程

环境配置xml文件

 mybatis的xml配置当中我认为可以分为两大块,分别是environment和mappers,前者主要是指整个mybatis连接的环境配置,后者表示mybatis写的SQL语句,配置的解析过程其实就是解析这块内容的。
 关于mybatis支持的xml标签可以参考文章中的mybatis官网,相信我看了以后你一定不会后悔的,包括:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins 插件
  • environments 环境
  • databaseIdProvider 数据库厂商标识
  • mappers 映射器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost/mybatis?useSSL=false" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="map/query.xml" />
        <mapper resource="map/insert.xml" />
        <mapper resource="map/update.xml" />
        <mapper resource="map/delete.xml" />
    </mappers>
</configuration>    


环境配置解析源码分析

 在XMLConfigBuilder类当中parseConfiguration负责解析上面的xml文件,基本上每个标签对应的都有一个解析类,这里我们就不展开了,每个标签的解析其实都是一个比较复杂的逻辑。
 我们关注下mapperElement方法,这个方法是解析mappers标签引入的对应的SQL语句的xml文件,通过Resources.getResourceAsStream方法读取xml文件内容,通过mapperParser.parse()方法进行解析

private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          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.");
          }
        }
      }
    }
  }


SQL解析

SQL的XML格式

 我们通过标签<mapper resource="map/insert.xml" />引入的SQL文件的内容如下,mapper其实包含的标签个数不是特别多,可以给大家感受下:

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.daoInterface.UserDao">
    <resultMap id="UserMap" type="domain.UserEntity">
        <id property="id" column="uid" />
        <result property="username" column="username" />
        <result property="password" column="password"/>
        <result property="address" column="address"/>
        <result property="createTime" column="createTime"/>
        <result property="updateTime" column="updateTime"/>
        <collection property="campanyEntity" resultMap="dao.daoInterface.CampanyDao.CampanyMap" />
    </resultMap>

    <!-- 可以将sql语句独立出来,然后引用 -->
    <sql id="selectMap">
        u.username, u.address ,c.campany_name
    </sql>

    <!-- 根据id查询用户 -->
    <select id="getUserInfo" parameterType="int" resultMap="UserMap">
            SELECT <include refid="selectMap"/>
            FROM  user u left join campany c
            ON u.username = c.username
            WHERE id = #{id}
            ORDER BY id ASC
    </select>
</mapper>


SQL解析源码分析

 在XMLMapperBuilder类中通过parse进行解析,从源码可以看出来从标签/mapper开始解析,一次解析SQL对应的各类标签,针对每个标签的解析都是比较复杂的,这个暂时不展开进行分析。

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
}

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      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);
    }
  }


配置解析过程流程图

 核心在于XMLConfiggBuilder解析环境变量和XMLMapperBuilder解析SQL变量,这个图应该还算比较清晰,通过解析后核心就生成了Configuration对象,这个是整个mybatis的核心

配置解析时序图


参考文章

mybatis官网

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容