一、什么是Mybatis

    引用Mybatis文档中的介绍:Mybatis是一款优秀的持久层框架,他支持自定义Sql、存储过程以及高级映射。Mybatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。Mybatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java的POJO为数据库中的记录。

    而对于我自己想学习Mybatis的源码的原因,一是想了解Mybatis的源码,二是想学习下Mybatis中的设计模式,比如责任链、代理、装饰者模式等。

二、传统的JDBC操作

1、加载数据库驱动。

2、通过DriverManager注册驱动。

3、通过DriverManager创建数据库连接Connection。

4、通过Connection创建Statement/PreparedStatement。

5、通过Statement进行数据库操作。

6、处理结果集。

7、关闭资源。

    由于传统的JDBC中,如果每一个线程对数据库进行操作时,都需要进行上述的七步操作,从而导致开发中不停的造轮子,因此我们可以引入Mybatis框架,交给Mybatis来帮我们做这些事,我们只需要关心对应的配置信息和sql的编写即可。

三、SqlSessionFactoryBuilder

    引用Mybatis官方文档,构建SqlSessionFactory时会使用到SqlSessionFactoryBuilder。

java mybatis 执行DDL mybatis执行函数_sql

    因此SqlSessionFactoryBuilder的作用为构建SqlSessionFactory。

·  SqlSessionFactoryBuilder源码

public class SqlSessionFactoryBuilder {
  //...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) {      }
    }
  }public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);  }
}

    build中的源码过程即对parser进行解析,解析完后,生成SqlSessionFactroy对象。

    观察DefaultSqlSessionFactory(config)构造函数,可以发现,SqlSessionFactory类中维护了一个configuration对象,用于存储environment(数据库相关环境信息)、mapperRegistry(mapper或dao层接口映射器信息)、typeHandlerRegistr(类型处理器信息)等。

public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}

·  解析XML文件

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    //解析XML文件
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//...
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);

//调用jdk方法,生成document树

this.document = createDocument(new InputSource(inputStream));}

    引用Mybatis官方文档配置的xml信息为:

java mybatis 执行DDL mybatis执行函数_xml_02

========>

    转换为document树后的结点信息为:

java mybatis 执行DDL mybatis执行函数_xml_03

    解析document的核心代码(理解即可,我没细看):

public boolean scanDocument(boolean complete)throws IOException, XNIException {    //...    int event = next();    do {switch (event) {case XMLStreamConstants.START_DOCUMENT :
                //遇到了开始的document元素                break;            case XMLStreamConstants.START_ELEMENT :
//遇到了开始的element元素
break;
case XMLStreamConstants.CHARACTERS :
    //字符集处理fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity); fDocumentHandler.characters(getCharacterData(),null); break; case XMLStreamConstants.SPACE: //空格处理 break;

//...

case XMLStreamConstants.COMMENT :

//注释处理

fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);                fDocumentHandler.comment(getCharacterData(),null); break; case XMLStreamConstants.DTD : //DTD约束 break;

//...

case XMLStreamConstants.NAMESPACE :
      
 //nameSpace处理
    break;   
 case XMLStreamConstants.ATTRIBUTE :
      
                //attribute处理
    break; case XMLStreamConstants.END_ELEMENT : //element元素结束符号 break; default :throw new InternalError("processing event: " + event); } event = next(); } while (event!=XMLStreamConstants.END_DOCUMENT && complete);

//...
}

·  解析document树

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    //解析XML文件生成document树
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析document树,生成Configuration对象

return build(parser.parse());

//...
}
public Configuration parse() {

  //...

  //1、parser.evalNode,将document树解析成XNode结点对象

//2、解析XNode为Configuration对象
parseConfiguration(parser.evalNode("/configuration"));  return configuration;}

四、parseConfiguration

private void parseConfiguration(XNode root) {try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));    propertiesElement(root.evalNode("properties"));    loadCustomVfs(settings);    typeAliasesElement(root.evalNode("typeAliases"));    pluginElement(root.evalNode("plugins"));    objectFactoryElement(root.evalNode("objectFactory"));    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));    reflectorFactoryElement(root.evalNode("reflectorFactory"));    settingsElement(settings);    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);  }
}

    选择几个我认为是重要的看下是如何解析的。

·  environment

private void parseConfiguration(XNode root) {
    //...    environmentsElement(root.evalNode("environments"));
//...
}
private void environmentsElement(XNode context) throws Exception {if (context != null) {
if (environment == null) {
      
        //从上下文中设置默认的environment对象
           environment = context.getStringAttribute("default"); }for (XNode child : context.getChildren()) { //获取mybatis事物管理器对象 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"))

//获取数据库连接dataSource资源对象

DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
      
 DataSource dataSource = dsFactory.getDataSource();

//构造器模式创建environment对象

 Environment.Builder environmentBuilder = new Environment.Builder(id)
 .transactionFactory(txFactory)
 .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build());

    environment比较简单,即加载了配置的事物管理器、数据库资源对象,并通过构造器模式封装成environment对象被加载到configuration中。

·  mapper

private void parseConfiguration(XNode root) {
    //...    mapperElement(root.evalNode("mappers"));
//...
}
public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}
public <T> void addMapper(Class<T> type) {


//mapper必须为接口

 if (type.isInterface()) {
 //...knownMappers.put(type, new MapperProxyFactory<T>(type)); 

//...
}
}

    从addMapper中可以看出,所有的mapper都会被放到mapperRegistry中key-mapper接口,value-动态代理工厂,并且mapper必须为接口类型。

java mybatis 执行DDL mybatis执行函数_sql_04

    因此在Mybatis官方文档示例中的session.getMapper就相当于从mapperRegistry这个map中取出来对应的mapper接口。

    但是此时只解析的mapper接口,而XML中的增删改查的方法还没有被解析出来。

1、根据mapper接口文件获取文件路径名。

public <T> void addMapper(Class<T> type) {
      //...      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);      parser.parse();
//...
}
public MapperAnnotationBuilder(Configuration configuration, Class> type) { 


//获取文件的路径名

 String resource = type.getName().replace('.', '/') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration;   
 this.type = type;

//...

}

2、开始加载mapper.xml

public void parse() {


//获取接口路径名

String resource = type.toString();
      
 //防止重复加载
      
 if (!configuration.isResourceLoaded(resource)) {
      
 //加载mapper.xml
    loadXmlResource(); configuration.addLoadedResource(resource);   
 assistant.setCurrentNamespace(type.getName());
      
 //加载缓存
    parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) {try {   
 if (!method.isBridge()) {
      
 //加载mapper中的sql注解信息
    parseStatement(method); } } catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method)); } }   
 }
      
 //移除掉未完成信息
    parsePendingMethods();}

private void loadXmlResource() {
  //防止重复加载

if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      
 //xml标识,路径名
    String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { }if (inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());

//加载xml

xmlParser.parse();
    } }}

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
      
    //加载xml中的namespace、resultMap、paramterMap、sql、增删改查标签
      
    configurationElement(parser.evalNode("/mapper"));
      
 //将mapper资源,放入到已加载列表中
      
 configuration.addLoadedResource(resource);
      
 //绑定mapper.xml文件和nameSpace:+路径 到队列中
    bindMapperForNamespace(); } //加载完成后移除队列操作处理 parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements();}

    加载mapper.xml的核心方法:

private void configurationElement(XNode context) {
try {
      
//namespace作为标识符,不能为空
   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"));
      
 //parameterMap的初始化
      
 parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      
    //resultMap的初始化
      
 resultMapElements(context.evalNodes("/mapper/resultMap"));
      
    //sql标签的初始化
      
 sqlElement(context.evalNodes("/mapper/sql"));
      
    //增删改查标签的初始化
    buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); }}

    到此刻为止,mapper.xml中的所有信息已经被加载到mapperRegistry中了。

3、开始加载mapper接口中带注解的方法

    通过parseStatement方法进行加载。

public void parse() {
      
        //...
      
parseStatement(method);
    //...}

    mapper元素的加载,其实在于mapperRegistry注册中心。加载mapper元素时会将xml中所有的mapper进行循环遍历,全部放到mapperRegistry中(key-mapper.xml根据路径转换后的class接口,value-该class接口的工厂类,内部其实为一个JDK动态代理类)。

    然后开始先对mapper.xml文件中的元素进行加载。根据顺序加载mybatis缓存、parameterMap、resultMap、sql标签信息、增删改查标签信息进行初始化封装。最后将namespace:+mapper.java文件路径名的信息作为资源名放入到队列中,作为是否重复加载的依据。

    接着再通过parseStatement方法对mapper.java中的增删改查sql注解进行加载。全部加载完毕后,将他们从未加载的队列中移除。

    注意点:阅读源码时发现,至少有两处地方都对同一个class文件进行了对mapperRegistry的注册。

    原因:spring可能无法知道真正的mapper资源是否被加载,因此这里设置了一个特殊的标识符即namespace:+mapper.java文件的路径名放入到队列中作为是否重复加载的依据,所以在最后还需要调用下mapperRegistry.put。

五、SqlSessionFactory

   此时的configuration中已经封装了绝大部分mybatis的信息,最后,SqlSessionFactoryBuilder的build方法执行完毕后,会调用SqlSessionFactory的初始化方法,加载出SqlSessionFactory,并将configuration对象进行赋值。完成最后使命。

public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;  public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;
}

//...


六、总结

    SqlSessionFactoryBuilder类的作用其实就是解析配置文件、创建SqlSessionFactory。

问题一:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession的作用域如何?

    SqlSessionFactoryBuilder被创建出来就是为了解析Mybatis的配置文件、创建SqlSessionFactory,一旦SqlSessionFactory被创建出来,该类就不再被需要了。

    SqlSessionFactory一旦被创建就与服务器应用共存亡,用来创建SqlSession。相当于数据库连接池。

    SqlSession相当于一个数据库中的事务,由每个线程来创建SqlSession,进行数据库操作。因此SqlSession的作用域和线程相关。