Mybatis 深入浅出 -- 初始化篇

Mybatis

本文从mybatis 初始化流程开始,根据源码逐步剖析

首先先思考一下这几个问题

  1. Mybatis解决了什么问题? 无非是简化数据库操作、实现封装、让程序员更关注SQL本身、维护便利
  2. 它是如何解决这些问题的?
  3. Mybatis是运行在什么样的环境下的?
  4. 它如何读取解析用户定义的配置信息?即如何初始化的
  5. 它的环境结构是什么样的?
  6. 在这一环境下如何 实现做增删查改
  7. 如何执行SQL
  8. 如何执行动态SQL
  9. 如何拼接查询参数等等

我知道了这些原理后,我能做些什么?

例如用 Mybatis来扩展一个分页插件
Mybatis存在哪些我觉得可以改进的地方


正经的分割线


带着这些问题,我们从一个TestCase开始

传统jdbc流程

  1. 注册驱动
  2. 获取连接
  3. 准备SQL执行statement
  4. 设置SQL参数
  5. 执行SQL,获取到结果集
  6. 解析结果集
  7. 依次关闭结果集,statement,数据库连接
    Class.forName(Driver.class.getName());
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?characterEncoding=utf-8", "root", "root");
    PreparedStatement preparedStatement = connection.prepareStatement("select * from test where id=?");
    preparedStatement.setString(1,"1");
    ResultSet resultSet = preparedStatement.executeQuery();
    while (resultSet.next()) {
      String columnName1 = resultSet.getMetaData().getColumnName(1);
      String columnName2 = resultSet.getMetaData().getColumnName(2);
      System.out.println(columnName1+":"+resultSet.getString(1));
      System.out.println(columnName2+":"+resultSet.getString(2));
    }
    resultSet.close();
    preparedStatement.close();
    connection.close();

Mybatis初始化,主体流程

  1. 解析config.xml,读取主配置文件,解析<configuration>及下所有内容
  2. 获取 设置的 别名对象工厂对象反射信息
  3. 解析 各个mappers 信息, 包括:xml注解形式, 封装成 MapperedStatement 放入Map中待用, 解析信息包括:
  4. 自定义的缓存标签设置解析
  5. 自定义的参数映射解析
  6. resultMap标签映射配置
  7. sql标签解析
  8. 解析各个CRUD 标签

先从一段测试代码开始,以xml配置为例(注解方式流程解析思想都相同)

注意 阅读 序号 (1)、(2)、(3)、(4)、(5)、(6)
Test.java

    (1)通过SqlSessionFactoryBuilder构建mybatis初始化组件,见下一代码栏 解释
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(“xxx/mybatis.xml”);
    // 使用一个SqlSession作为此次连接, 主要讲这儿,初始化流程
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 动态代理 执行SQL
    CustomMapper mapper = sqlSession.getMapper(DemoMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("id","1");
    // 自定义了一个select方法,xml的形式
    System.out.println(mapper.select(map));
    sqlSession.close();

SqlSessionFactoryBuilder

    
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //解析config.xml(这里解析使用的是  java dom)
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parse(): 解析config.xml里面的节点
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

XMLConfigBuilder


  (2)如果解析过就不能再次解析mybatis主文件,将mybatis主文件中,configuration标签及其下的所有内容解析出来,见(3)
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  (3)
  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 将xml中定义的别名,存入当前builder的typeAliasRegistry -- Map中
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      // 设置对象工厂,自定义返回对象的哪种实例
      objectFactoryElement(root.evalNode("objectFactory"));
      //MateObject 方便反射操作实体类的对象
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析mapper到map存储, 重点解析这儿,见(4)
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

XMLMapperBuilder

  (4)
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper.xml 见(5)
      configurationElement(parser.evalNode("/mapper"));
      // 记录加载过的resource
      configuration.addLoadedResource(resource);
      //绑定Namespace里面的Class对象
      bindMapperForNamespace();
    }

    //重新解析之前解析不了的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  
  (5)
  // 解析mapper文件里面的节点,封装成MapperedStatement
  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"));
      // resultMap映射解析
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // sql语句解析
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析各个 CRUD 标签节点了 下面代码紧接着
      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);
    }
  }

  // 接上面
  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) {
    // 遍历所有CRUD节点
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析xml中的节点, 见下面(6)
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
        // 比如insert中依赖了 某个 select标签的,但是insert是按照先后顺序解析的,就会造成insert解析时,缺少select的MapperedStatement,从而抛出异常
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder

  (6)
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否是select命令
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 如果是查询则不刷新,不是则刷
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 如果是查询则使用二级缓存,是则不使用,即同一个namespace都共享缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 是否需要处理嵌套查询结果 将sql语句处理成嵌套形式的
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 替换Includes标签为对应的sql标签里面的值
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 解析自定义的多语言配置
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    // 解析selectKey标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //设置主键自增规则
    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))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    //解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // 解析单个mapper.xml 结束 
    // 添加到 Configuration中的 mappedStatements 集合中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

Mybatis初始化,主体流程

结合上面文字描述的流程,这里给出一张简图,能快速总结:


Mybatis初始化流程.png

至此 Mybatis 的初始化解析结束

下篇文章将逐步剖析 Mybtis的执行原理

下篇文章传送门

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