Mybatis深入源码分析之SqlSessionFactoryBuilder

Mybatis是支持定制化SQL、存储过程和高级映射的持久层框架。主要完成两件事:

  • 封装JDBC的操作
  • 利用反射完成Java类和SQL之间的转换

mybatis的主要目的就是管理执行SQL是参数的输入和输出,编写SQL和结果集的映射是mybatis的主要优点

1.Mybatis大体架构流程分析

  1. 读取配置文件并返回Reader对象
  2. 使用SqlSessionFactoryBuilder获取SqlSessionFactory源码分析
  3. 创建session
  4. 获取相对应mapper
    try {
            // 1.mybatis配置文件
            String resources = "mybatis.xml";
            // 2.获取Reader对象
            Reader resourceAsReader = Resources.getResourceAsReader(resources);
            // 3.获取SqlSessionFactoryBuilder
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsReader);
            // 4.创建对应的session
            SqlSession sqlSession = build.openSession();
            // 5.获取对应的mapper
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 6.执行方法
            UserEntity user = userMapper.getUser(1);
            System.out.println("name:" + user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }

1.1类图

Mybatis深入源码分析之SqlSessionFactoryBuilder_第1张图片

2.源码分析

2.1获取Reader对象

   Reader resourceAsReader = Resources.getResourceAsReader(resources);

public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
  }

通过resource(配置文件路径)获取reader对象

2.2获取SqlSessionFactory

 SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsReader);

1.进入到SqlSessionFactoryBuilder().build(resourceAsReader)方法

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

2.进入源码最终执行的方法是

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //XMLConfigBuilder是对mybatis的配置文件进行解析的类,会对myabtis解析后的信息存放在Configuration对象中
      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 {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

3.进入XMLConfigBuilder对象

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;
  }

4.通过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;
  }

通过源码可以知道XMLConfigBuilder只能使用一次,被parsed属性控制执行一次parsed会被设置成true

5.通过parseConfiguration(parser.evalNode("/configuration"))

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析元素properties,保存在variables中
      propertiesElement(root.evalNode("properties"));
      // 解析元素typeAliases,保存在typeAliasRegistry中
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析插元素plugins,保存在interceptorChain中
      pluginElement(root.evalNode("plugins"));
      // 解析元素objectFactory,保存在objectFactory中
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析元素objectWrapperFactory,保存在objectWrapperFactory中
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析元素reflectorFactory,保存在reflectorFactory中
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      // 解析元素settings,保存在configuration的属性中
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析environments 可以配置多个运行环境,但是每个SqlSessionFactory 实例只能选择一个运行环境
      environmentsElement(root.evalNode("environments"));
      //解析databaseIdProvider MyBatis能够执行不同的语句取决于你提供的数据库供应商。许多数据库供应商的支持是基于databaseId映射
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers 当MyBatis设置参数到PreparedStatement 或者从ResultSet 结果集中取得值时,就会使用TypeHandler  来处理数据库类型与java 类型之间转换
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers 主要的crud操作都是在mappers中定义的
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

6. 通过environmentsElement(root.evalNode("environments"))解析

//环境元素解析
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          //事务配置解析,获得事务工厂(JDBC和MANAGED两种)
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

通过源码可以知道该方法读取配置文件数据库配置

7. 通过mapperElement(root.evalNode("mappers"))

//mapper元素解析,获得各种crud操作或者配置文件
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 对应配置方式(4),查找属性name指定包下所有的接口类型,注册到mapperRegistry
          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) {
            // 对应配置方式(1),解析resource属性指定的mapper配置文件并添加配置到mapperRegistry
            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) {
            // 对应配置方式(2),解析url属性指定的mapper配置文件并添加配置到mapperRegistry
            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) {
            // 对应配置方式(3),添加class属性指定的mapper配置到mapperRegistry
            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.");
          }
        }
      }
    }
  }

通过上面源码可以知道mybatis解析mapper的方法一共有四种 package、resource、url、class

8. 通过mapperParser.parse()该方法在XMLMapperBuilder中

 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    //下边三个方法都是存储信息到configuration中。
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

       XMLMapperBuilder这个类主要是用于解析mybatis中的标签里边的内容,怎么一步一步的引用,看下边的分析:
其实XMLMapperBuilder和XMLConfigBuilder的功能比较相似,只是XMLConfigBuilder的作用范围,或者说包括的范围比较大,其中标 签就是其中解析的一部分

通过

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        //得到mapper接口实现类,实现映射
        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属性对应于某个接口,应用程序通过接口访问mybatis时,mybatis会为这个接口生成一个代理对象,这个对象就叫mapper对象,在生成代理对象前mybatis会校验接口是否已注册,未注册的接口会产生一个异常。为了避免这种异常,就需要注册mapper类型。这个步骤是在XMLMapperBuilder的bindMapperForNamespace方法中完成的。它通过调用Configuration对象的addMapper方法完成,而Configuration对象的addMapper方法是通过MapperRegistry的addMapper方法完成的,它只是简单的将namespace属性对应的接口类型存入本地缓存中。另外,我们使用Mapper接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject);

       上述源码中通过configuration.addLoadedResource("namespace:" + namespace)、configuration.addMapper(boundType);将namespace添加到protected final Set loadedResources = new HashSet()集合中

  protected final Set loadedResources = new HashSet();
  public void addLoadedResource(String resource) {
    loadedResources.add(resource);
  }

将mapper添加到protected MapperRegistry mapperRegistry = new MapperRegistry(this)中,mapperRegistry是由private final Map, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>()集合实现

 protected MapperRegistry mapperRegistry = new MapperRegistry(this);
 public  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }

MapperRegistry类

  private final Map, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>();
  public  void addMapper(Class 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(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);
        }
      }
    }
  }

3.总结

  1. 获取本地InputStreamReader对象(mybatis配置文件)
  2. 调用SqlSessionFactoryBuilder 
  3. ###在使用XMLConfigBuilder解析mybatis配置文件,装配到Configuration中。
  4. 将配置文件中的Mapper添加到Configuration mapperRegistry实现注册。

    备注:mapperRegistry存放当前所有的mapper文件

  5. 使用Configuration获取默认的DefaultSqlSessionFactory

你可能感兴趣的:(程序员)