mybatis随笔一之SqlSessionFactoryBuilder
SqlSessionFactoryBuilder是构建sqlSessionFactory的入口类

从该类的方法可知,它是通过不同的入参来构造SqlSessionFactory,除了最后一个configuration入参方法外,其余方法最终都调用如下方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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类的实例
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
在这个初始化过程中,首先创建了XMLMapperEntityResolver类的实例,这个类顾名思义是个实体mapper文件实体解析器,里面有个map将mybatis的xml文件与对应的解析文件关系保存起来,初始化后再实例化XPathParser
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }
commonConstructor比较简单,就是给一些类变量赋值,并且初始化了一个xpath对象,这里使用了工厂设计模式。
new InputSource(inputStream)就是将输入流赋予自己内部的一个变量byteStream。
createDocument(new InputSource(inputStream))这里面主要做了这几件事通过DocumentBuilderFactory生成DocumentBuilder,并将entityResolver赋给它的属性字段同时定义了一个处理异常的内部类,
然后通过builder.parse(inputSource)将输入流解析成一个document对象。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
new Configuration()主要是在构造方法里面注册了别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
registerAlias是TypeAliasRegistry类的方法,将别名与类的关系保存在了内部的private final Map<String, Class<?>> TYPE_ALIASES
public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
super(new Configuration())就是将相应的变量保存在父类属性上,方便其它子类使用。
ErrorContext.instance()方法内部采用的threadlocal方式,每个线程都持有一个ErrorContext对象resource("SQL Mapper Configuration")就是一个简单的赋值给resource变量方法。
至此一个XMLConfigBuilder对象就创建出来了,然后就进入了build(parser.parse())。
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
parser.evalNode("/configuration")在初始化过程中已经将mybatis_config.xml文件解析成document对象,这里使用xpath解析工具将configuration节点解析成一个XNode对象。
private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
该方法一看便知是扫描configuration节点下的诸如properties、typeAliases节点做相应的处理。稍微分析其中几个方法
propertiesElement(root.evalNode("properties"))是读取properties节点下的property子元素以及resource指向的资源文件路径,将两者合并为一个properties并保存到内部的vars变量。
typeAliasesElement(root.evalNode("typeAliases"))读取子元素时首先判断是否package,是的话扫描包下的非接口、匿名、内部类注册别名。
pluginElement(root.evalNode("plugins"))将插件加到插件链上。
重点提一下最后的mapperElement方法,这个方法内首先判断子元素是不是package,若是<mapper resource="mapper/demo.xml"/>这种形式的。其中最重要的是下面两行代码
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
构建XMLMapperBuilder对象前面已经分析过了,重点分析parse方法
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
该方法获取mapper文件的namespace来实例化mapper接口,这也就解释了为什么namespace要和mapper接口全路径一致。
其中值得注意的是configuration.addMapper(boundType)方法,该方法调用了mapperRegistry.addMapper(type)方法。
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
MapperProxyFactory是个代理接口方法工厂,后面会使用到它,就是通过它来给没有实现类的mapper接口代理。
重点看其中的parser.parse()方法
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
先得到接口里的methods,然后遍历解析方法,查看该方法有没有注释sql语句有的话拼接这些语句,因为现在流行使用xml文件配置方式更加灵活的处理sql语句,因此这里跳过。
parsePendingMethods方法是有些insert或者update的select语句引入了sql片段,但是sql片段还没解析到,因此先将这些方法pend,后面每有新的mapper解析时都会尝试解析完这些pend方法。
同理parsePendingResultMaps(),parsePendingChacheRefs(),parsePendingStatements()三个方法也类似。
现在configuration已经基本解析完成,然后调用
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
返回一个默认的SqlSessionFactory实现,其内部持有一个Configuration变量。
至此SqlSessionFactoryBuilder创建DefaultSqlSessionFactory的过程完成。
mybatis随笔一之SqlSessionFactoryBuilder的更多相关文章
- mybatis随笔三之SqlSession
		
在上一篇文章我们已经得到了DefaultSqlSession,接下来我们对sqlSession.getMapper(DemoMapper.class)这种语句进行分析 @Override public ...
 - mybatis随笔二之SqlSessionFactory
		
在上一篇文章我们已经得到了DefaultSqlSessionFactory @Override public SqlSession openSession() { return openSession ...
 - MyBatis随笔
		
前一阵参与了一个项目的搭建,为了快速开发再加上学一些新东西,准备采用React+Spring MVC+MyBatis的架构. 花了一些时间最终把Spring MVC+MyBatis打通. 这里总结下M ...
 - mybatis随笔四之MapperProxy
		
在上一篇文章我们已经得到了mapper的代理对象,接下来我们对demoMapper.getDemo(1)这种语句进行分析.由于返回的mapper是个代理对象,因此会进入invoke方法,接下来我们来看 ...
 - mybatis随笔五之Executor
		
在上一篇文章我们分析到了mapper接口方法的实现实际上是交由代理类来实现的,并最终调用Executor来查询,接下来我们对executor.query(ms, wrapCollection(para ...
 - 关于Mybatis的一些随笔
		
Mapper.xml头文件 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http:/ ...
 - MyBatis 简介
		
MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis ...
 - 32、mybatis
		
第一章回顾jdbc开发 1)优点:简单易学,上手快,非常灵活构建SQL,效率高 2)缺点:代码繁琐,难以写出高质量的代码(例如:资源的释放,SQL注入安全性等) 开发者既要写业务逻辑,又要写对象的创建 ...
 - 如何使用mybatis《三》
		
在前边阐述了单独使用mybatis的方法,在实际开发过程中mybatis经常和spring一起使用,即mybatis和spring进行集成,现在我们来看如何集成. mybatis和spring进行集成 ...
 
随机推荐
- Linux 快捷键使用
			
命令运行时使用CTRL+Z,强制当前进程转为后台,并使之停止. 1. 使进程恢复运行(后台) (1)使用命令bg Example: zuii@zuii-desktop:~/unp/tcpcliserv ...
 - eclipse生成可执行jar包(引入第三方.jar文件)
			
1. eclipse建立普通的java project项目(项目名aa) 2. 项目正常组织通过buildpath加载各种jar包入项目aa比如例子项目里,加入了spring 各种jar包加入各种配置 ...
 - 一篇谈Flink不错的文章
			
精华 : 在执行引擎这一层,流处理系统与批处理系统最大不同在于节点间的数据传输方式.对于一个流处理系统,其节点间数据传输的标准模型是:当一条数据被处理完成后,序列化到缓存中,然后立刻通过网络传输到下一 ...
 - 9.DataGrid数据表格
			
后台获取数据并将其转换为json数组格式: 前台获取数据并显示在数据表格中:
 - python编写producer、consumer
			
自主producer.consumer 首先在不同的终端,分别开启两个consumer,保证groupid一致 ]# python consumer_kafka.py 执行一次producer ]# ...
 - CMUSphinx Learn - Before you start
			
Before you start 开始之前 Before you start the development of the speech application, you need to consid ...
 - ASP.Net MVC OA项目笔记<四>
			
1.1.1 EF线程唯一 在数据层中用到了EF的实例,在数据会话层也用到了,所以在一个请求中只能创建一个EF实例(线程内唯一对象),把它封装成工厂类 1.1.2 为了防止相互引用,循环引用,所以这个工 ...
 - CentOS 7 - 安装MySQL 5.7
			
CentOS 7的默认yum仓库中并没有MySQL5.7,我们需要手动添加,好在MySQL官方提供了仓库的地址,所以我们能够比较简单地安装MySQL. 本文我们将介绍CentOS 7下MySQL5.7 ...
 - 面向对象三大特性编写面向对象程序,self到底是谁
			
一.函数式编程和面向对象的对比 面向过程:根据业务逻辑从上到下写垒代码: 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可: 面向对象:对函数进行分类和封装,让开发“更快更好更强. ...
 - 带你走进二进制-一次APT攻击分析
			
原文:https://osandamalith.com/2017/06/04/apt-attack-in-bangladesh/ 由prison翻译整理,首发i春秋 引言; 这是一次来自遥远国 ...