Mybatis源码系列1-Mybaits初始化

470 阅读11分钟

人非要经历一番不同平时的劫难才能脱胎换骨,成为真正能解决问题的人

简介初始化过程1.解析XML配置文件1.1 Config文件的解析1.2 Mapper文件的解析1.2.1 解析CURD模板1.2.2 绑定Mapper到命名空间2.创建SqlSessionFactory总结

简介

首先我们再回顾下Mybaits的基本使用。

//加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession 的获取
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
//执行sql
User user = sqlSession.selectOne("MyMapper.selectUser"1);//(SQL通过命名空间+SQLID 的格式定位)
}finally {
sqlSession .close();
}

一切都从SqlSessionFactoryBuilder说起。SqlSessionFactoryBuilder是通过builder设计模式来创建一个SqlSessionFactory 工厂。

创建SqlSessionFactory 最主要分为两步,

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // 1.xml配置文件解析器。
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      Configuration config = parser.parse()
      // 2.根据解析到的配置,创建DefaultSqlSessionFactory 
      return build(config);
  }
  //创建默认的sqlsesion工厂。
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

初始化过程

1.解析XML配置文件

SqlSessionFactoryBuilder的第一步就是配置的解析。
配置文件主要分为两种:

  • Conifg文件: 包括数据连接配置,全局设置配置。

  • Mapper文件:用于SQL的统一管理,对SQL的配置。

    在这里插入图片描述
    在这里插入图片描述

1.1 Config文件的解析

Config文件的解析是由XMLConfigBuilder做的。

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());//创建一个Configuration对象
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

我们来看看parse()方法

 public Configuration parse() {
    parsed = true;
    //从根节点开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  private void parseConfiguration(XNode root) {
    try {
      // 解析 properties配置
      propertiesElement(root.evalNode("properties"));
      // 解析 settings配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      //解析environment  配置
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 mapper 配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

XMLConfigBuilder 根据约定,解析config 配置文件中的各个标签。并将相关配置信息放到Configuration 对象中返回。

列如:

Properties settings = settingsAsProperties(root.evalNode("settings"));//解析settings标签
settingsElement(settings);//设置settings配置到configuration 对象。

1.2 Mapper文件的解析

在解析Config配置文件过程中,会伴随Mapper.xml文件的解析。这个解析的工作是由XMLMapperBuilder 完成的。

config中的mapper文件位置配置

<mappers>
    <mapper resource="mappers/UserMapper.xml"/>
</mappers>

Mapper.xml

<mapper namespace="com.wqd.dao.UserMapper">
   <select id="selectUser" resultType="com.wqd.model.User">
      select * from user where id= #{id}
   </select>
</mapper>

XMLMapperBuilder

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        //1.从根标签mapper开始解析mapper文件
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //2.绑定mapper到命名空间
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

//1.解析mapper文件
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"));
      //解析sql标签配置的sql 片段
      sqlElement(context.evalNodes("/mapper/sql"));
       //解析select|insert|update|delete 标签配置的sql 模板(重点)  
     buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
//2.绑定命名空间
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
      }
      if (boundType != null) {//如果有对应的Class
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);//添加到缓存
        }
      }
    }
  }

这里我们重点讲讲XMLMapperBuilder 中有两个重要的点

1.2.1 解析CURD模板

也就是解析select|insert|update|delete代表的SQL 模板。这四种标签配置的SQL模板是我们操作数据库时的SQL执行语句的模板。 我们可以理解为:一个select 标签表示一类动态SQL。

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

每一个select|insert|update|delete由XMLStatementBuilder 解析

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
       //解析"select|insert|update|delete"标签
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder 除了按照约定,解析"select|insert|update|delete"对应的标签属性以及子标签外。最重要的是还会通过MapperBuilderAssistant(构建助手),把解析出来的信息 封装成一个MappedStatement 放入到Configuration.mappedStatements 缓存中

类XMLStatementBuilder
public class XMLStatementBuilder extends BaseBuilder {
    private MapperBuilderAssistant builderAssistant;//构建助手

    //解析"select|insert|update|delete"标签
    public void parseStatementNode() {
    String id = context.getStringAttribute("id");
        ...解析
     //助手辅助封装成MappedStatement  
     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
}

类MapperBuilderAssistant
public class MapperBuilderAssistant extends BaseBuilder {
    public MappedStatement addMappedStatement(...){
         id = applyCurrentNamespace(id, false);//id的处理

         //创建一个MappedStatement对象封装每一个SQL模板信息
        MappedStatement statement = statementBuilder.build();
        configuration.addMappedStatement(statement);    
    }
}

//Configuration类
public class Configuration {
    //mappedStatements缓存
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

    //添加MappedStatement
    public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);//把MappedStatement 作为KEY
      }
}

值得一提是id的处理。虽然我们仅仅在select标签配置了id = "selectUser",

 <select id="selectUser" resultType="com.wqd.model.User">

但是在构建MappedStatement时 ,并不是把“selectUser”作为id, 而是经过applyCurrentNamespace方法进行处理。

public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
      return null;
    }
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) {
        return base;
      }
    } else {
      // is it qualified with this namespace yet?
      if (base.startsWith(currentNamespace + ".")) {
        return base;
      }
      if (base.contains(".")) {
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
      }
    }
    return currentNamespace + "." + base;
  }

我们可以看出,此方法会返回 namespacec.id 作为MappedStatement.id ,同时在作为Configuration.mappedStatements 缓存中MappedStatement的Key. 这就是为啥我们是通过namespace.id的形式来定位SQL的原因

最后解析的结果就是每一个SQL 模板都会创建一个MappedStatement对象(封装sql模板相关信息),放入到Configuration.mappedStatements 中。

这样我们就可以 通过namespace.id 定位到对应的SQL了。

<mapper namespace="com.wqd.dao.UserMapper">
   <select id="selectUser" resultType="com.wqd.model.User">
      select * from user where id= #{id}
   </select>
</mapper>
User user = sqlSession.selectOne("com.wqd.dao.UserMapper.selectUser"1);
1.2.2 绑定Mapper到命名空间

这一步也非常重要,如果namerspacer配置为一个Mpper类的全限定名。那么就会以namespace对应的类的Class为KEY,以MapperProxyFactory 为value 放入到 Configuration.MapperRegistry.knownMappers缓存中。

bindMapperForNamespace();
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        //根据命名空间名,查找类Class类
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);//如果找到,就把关联关系放到缓存中
        }
      }
    }
  }

//Configuration类
public class Configuration {
    //mapperRegistry缓存
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

   public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
    }
}
//Mapper注册器
public class MapperRegistry {
    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 {
          //以namespace对应的Class类为KEY。新建一个MapperProxyFactory为Value 
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}

当我们获取通过UserMapper获取Mapper时,MapperProxyFactory 会为我们创建一个代理类,来执行对应CURD操作。

这样我们就可以通过类名获取到Mapper#selectUser了。

<mapper namespace="com.wqd.dao.UserMapper">
   <select id="selectUser" resultType="com.wqd.model.User">
      select * from user where id= #{id}
   </select>
</mapper>
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 

config配置文件与mapper配置文件解析完成后。最终得到了一个Configuration对象。 而这个Configuration对象就是Mybatis 所需要的所有配置信息

2.创建SqlSessionFactory

SqlSessionFactoryBuilder 的第二步就是通过Configuration 对象创建一个默认的SqlSessionFactory工厂出来。

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

DefaultSqlSessionFactory 用于创建Sqlsession

Sqlsession sqlsession  = DefaultSqlSessionFactory.openSession()

总结

Mybatis初始化的过程,其就是Config配置文件,Mapper文件被解析, Configuration对象被创建的过程。

  • 所有的配置信息都包含在Configuration 这个大对象中。
  • 每一个SQL模板信息会被解析成一个MappedStatement对象。根据MappedStatement对象内的SQL模板信息,我们可以生成一类SQL。

如果本文任何错误,请批评指教,不胜感激 !

微信公众号:源码行动
享学源码,行动起来,来源码行动