mybatis源码分析-环境搭建 一文中,我们的测试代码如下:

public static void main(String[] args) throws IOException { 
    String resource = "mybatis-config.xml";  
    InputStream inputStream = Resources.getResourceAsStream(resource);  
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    try {  
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);  
        List<Dept> deptList = deptMapper.getAllDept();  
        System.out.println(deptList);  
    } finally {  
        sqlSession.close();  
    }  
 }  

其中如何生成 InputStream 对象在 mybatis源码分析-配置文件加载 已经讲解。本次将探究 SqlSessionFactory 对象的生成,也就是下面这行代码执行了什么。

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  

源码分析

首先上面那行代码,创建了一个 SqlSessionFactoryBuilder 对象,源码如下:

public class SqlSessionFactoryBuilder {  
    public SqlSessionFactoryBuilder() {  
    }
    //省略其它代码
}

其次 SqlSessionFactoryBuilder 对象调用 build 方法,该方法源码如下:

public SqlSessionFactory build(InputStream inputStream) {  
    return this.build((InputStream)inputStream, (String)null, (Properties)null);  
}

这个方法看来有重载方法,我们继续看其重载方法:

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 parser = new XMLConfigBuilder(inputStream, environment, properties);  

这个构造函数将我们的配置文件转换为 XMLConfigBuilder 对象,这里面的逻辑十分复杂,我们暂且不深究。

    return build(parser.parse());  

这里有两处注意的地方,parser.parse() 返回一个Configuration 对象,这个对象保罗万千,暂时也不深究。此外build方法的实现如下:

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

它返回了 DefaultSqlSessionFactory 对象,并且将 Configuration 对象赋值其属性,有源码为证:

public class DefaultSqlSessionFactory implements SqlSessionFactory {  
  
  private final Configuration configuration;  
  
  public DefaultSqlSessionFactory(Configuration configuration) {  
    this.configuration = configuration;  
  }
  //省略其它代码
}

由于 DefaultSqlSessionFactory 实现了 SqlSessionFactory 接口,那么

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  

就水到渠成了。


本节不想讨论

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  

    return build(parser.parse());  

这两行代码的实现,因为它们太复杂了。目前只需要理解:

  • 第一行通过配置文件创建了一个 XMLConfigBuilder 对象
  • 第二行通过 XMLConfigBuilder 对象的一个 parse 方法将 XMLConfigBuilder 对象转换为 Configuration 对象,而该对象是 DefaultSqlSessionFactory 必须要的属性。只有通过这个属性,才能实现 SqlSessionFactory 中定义的接口方法。

源码设计思想

如果我们来实现上面的功能,一般人会怎么处理呢?

  • 首先定义接口
package com.yefengyu.mybatis;  
  
public interface SqlSessionFactory {  
    void test();  
}
  • 其次编写实现类
package com.yefengyu.mybatis;  
  
import org.apache.ibatis.builder.xml.XMLConfigBuilder;  
import org.apache.ibatis.session.Configuration;  
  
import java.io.InputStream;  
  
public class DefaultSqlSessionFactory implements SqlSessionFactory {  
  
    private Configuration configuration = null;  
  
    public DefaultSqlSessionFactory(InputStream inputStream) {  
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);  
        this.configuration = parser.parse();  
    }  
  
    @Override  
    public void test() {  
        //使用 configuration 完成相关功能  
    }  
}
  • 测试
package com.yefengyu.mybatis;  
  
import org.apache.ibatis.io.Resources;  
  
import java.io.IOException;  
import java.io.InputStream;  
  
public class Main {  
    public static void main(String[] args) throws IOException {  
        String resource = "mybatis-config.xml";  
        InputStream inputStream = Resources.getResourceAsStream(resource);  
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(inputStream);  
        sqlSessionFactory.test();  
    }  
  
}

这三段代码想模拟mybatis创建SqlSessionFactory对象的过程,比如SqlSessionFactory有一些接口 test,其实现类 DefaultSqlSessionFactory 实现此方法需要 Configuration 对象,需要从 InputStream 通过构造函数传入并解析为 Configuration对象。测试代码中直接使用 DefaultSqlSessionFactory 创建 SqlSessionFactory 对象。

这种方式的缺点

  • 首先如果有多个 SqlSessionFactory 实现的话,把 InputStream 转换为 Configuration的过程在每个构造函数都会有。
  • 其次客户端,也就是测试代码那块,必须要清楚 SqlSessionFactory 有哪些实现类,每个实现类的功能是什么,没有达到接口与实现的分离。

指尖改变世界
27 声望6 粉丝