Mybatis-获取SqlSession源码解析(一)

一、大致流程 

public static void main(String[] args) {
        String resource = "config/mybatis-config.xml";
        InputStream inputStream;
        SqlSession session = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            // 构建sqlSession工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 获取sqlSession
            session = sqlSessionFactory.openSession();
            // 方法1
            String statement = "com.mapper.IStudentMapper.getAll";
            List student = session.selectList(statement);
            System.out.println(student);

            // 方法2
            IStudentMapper sessionMapper = session.getMapper(IStudentMapper.class);
            List list = sessionMapper.getAll();
            System.out.println(list);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            session.close();
        }
    }

上述代码基本上就是Mybatis的整个流程了,接下来,我们来结合源码来看下,Mybatis是如何初始化,如何获取SqlSession的。

  • 通过mybatis的Resources来读取全局配置文件,然后以流的形式作为SqlSessionFactoryBuilder的build方法的参数传入;
  • SqlSessionFactoryBuilder根据参数创建XMLConfigBuilder对象,然后调用XMLConfigBuilder的parse方法生成Configuration对象;
  • 然后SqlSessionFactoryBuilder根据Configuration对象作为参数,调用重载的另一个方法build,生成DefaultSqlSessionFactory对象。
  • 最终,返回DefaultSqlSessionFactory对象。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 根据输入流等参数生成XMLConfigBuilder对象,而XMLConfigBuilder用来解析全局配置
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 通过parser.parse方法生成Configuration对象,然后再调用重载方法
      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.
      }
    }
}
/**
 * 最后调用该方法生成factory,所以这里我们也可以手动传入Configuration对象来构建SessionFactory
 */
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

上述过程中,涉及到以下几个对象:

  • XMLConfigBuilder 该对象的目的就是负责解析配置文件的数据,解析成Configuration对象;
  • Configutation,该对象是全局配置文件mybatis-config.xml所对应的Java对象;
  • SqlSessionFactoryBuilder,该对象很明显就是为了构建sessionFactory的构造器。

Conguration对象

  这里可以注意下,XMLConfigBuilder在这里使用了设计模式中的建造者模式,基本建造者基类是BaseBuilder,XMLConfigBuilder是BaseBuilder类的实现之一,就是具体建造者的角色,所负责的就是解析mybatis-config.xml这个配置文件。

public class XMLConfigBuilder extends BaseBuilder {
    //标识是否已经解析过配置文件 
    private boolean parsed;
    // 用于解析配置文件的解析对象
    private XPathParser parser;
    // 对应配置文件中的配置
    private String environment;
    private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
}

而XMLConfigBuilder的解析是通过parseConfiguration来进行的。

public Configuration parse() {
    // 判断是否已经解析过
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 在配置文件中查找节点,然后通过调用parseConfiguration方法来解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
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"));
        // 解析节点
        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);
    }
}

  可以看到,parseConfiguration方法将每个节点的解析都封装成来一个个具体的方法,还是很好理解的,不过有一点,我没看太明白,就是对reflectionFactory节点的解析,我记得节点中是没有该节点的,但这里解析的是哪个节点呢???

我们拿其中的一个 typeAliases 解析的方法来看一下实现即可,剩余的如有兴趣,大家可自行查看其源码。先看一下配置:


    
    
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      // 遍历全部子节点
      for (XNode child : parent.getChildren()) {
        // 处理package节点
        if ("package".equals(child.getName())) {
          // 获取包名
          String typeAliasPackage = child.getStringAttribute("name");
          // 通过TypeAliasRegistry扫描指定包中的所有的类,并解析@Alias注解,完成别名注册
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          // 处理typeAlias节点,获取指定别名
          String alias = child.getStringAttribute("alias");
          // 获取别名对应的类型
          String type = child.getStringAttribute("type");
          try {
            // 通过名称加载对应的类
            Class clazz = Resources.classForName(type);
            if (alias == null) {
              // 扫描@Alias注解,完成别名注册
              typeAliasRegistry.registerAlias(clazz);
            } else {
              // 注册别名
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
}

SqlSession

  在获取到Configuration对象之后,SqlSessionFactoryBuilder就可以通过build方法进行构建工厂对象了。
  其实SqlSessionFactoryBuilder很简单,目的就是构建SqlSessionFactory工厂对象,然后从工厂里取出SqlSession,该类提供了各种参数的build的重载方法,大家可以根据需要自行调用来学习。
接下来,我们首先来了解一下SqlSession及SqlSessionFactory的各自的继承关系。

Mybatis-获取SqlSession源码解析(一)_第1张图片

从继承关系上我们可以看到,这里使用了工厂方法的设计模式。我们所使用的SqlSession的实现大多是DefaultSqlSession,同样,SqlSessionFactory的实现也是DefaultSqlSessionFactory。我们先看下SessionFactory的源码:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();
}
  • 首先,SqlSeesionFactory目的就是创建SqlSession,从源码中我们可以看到,SqlSeesionFactory包含了多个openSession的重载方法。可以通过其参数来指定事务级别,底层使用的Executor的类型,是否自动提交事务等等。
  • 而在SqlSession中我们可以看到,SqlSession中定义的全是对数据库增删改查的各种方法。这些方法我们稍后会挨个学习使用。

DefaultSqlSession

public class DefaultSqlSession implements SqlSession {
  // 全局配置解析后的对象
  private Configuration configuration;
  // 底层依赖的Executor对象
  private Executor executor;
  // 是否自动提交事务
  private boolean autoCommit;
  // 当前缓存中是否有脏数据
  private boolean dirty;
  private List> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
}

DefaultSqlSession实现类SqlSession的方法,并且提供了多种重载方法。但真正对数据库操作却是通过Executor对象来进行的。并且这里使用到了策略模式。
还有一点需要注意,DefaultSqlSession不是线程安全的。

DefaultSqlSessionFactory

DefaultSqlSessionFactory是工厂类的具体实现,该类实现了多种openSession重载方法,但最终都是基于以下两种方式来创建DefaultSqlSession:

  1. 一种是通过数据源获取数据库连接,并创建Executor对象及DefaultSqlSession对象。
  2. 另一种是用户提供数据库连接对象,然后DefaultSqlSessionFactory使用该连接进行创建操作。

我们分别来看下源码实现。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取mybati-config.xml中配置的environment对象
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂对象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 根据配置创建Executor对象
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建DefaultSqlSession对象
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}

来看第二种方式:

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        // 当前事务提交方式是否是自动提交
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        // 如果当前数据库驱动提供的连接不支持事务
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 根据connction创建事务Transaction
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}

 SqlSessionManager

    SqlSessionManager同时实现类SqlSessionFactory和SqlSession两个接口,那么它也就同时具有来创建SqlSession以及使用SqlSession处理数据库的功能。
我们先看一下继承结构及属性:

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
  // 底层封装的SqlSessionFactory对象
  private final SqlSessionFactory sqlSessionFactory;
  // SqlSession的代理对象,在该类初始化时,会使用JDK动态代理的方式为localSqlSession创建代理对象
  private final SqlSession sqlSessionProxy;
  // 与当前线程绑定的SqlSession对象
  private ThreadLocal localSqlSession = new ThreadLocal();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    // 创建代理类,通过内部类SqlSessionInterceptor来实现
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
}

SqlSessionManager提供了两种模式:

  1. 第一种与DefaultSqlSessionFactory相同,同一线程每次通过SqlSessionManager对象访问数据库时,都会创建新的DefaultSqlSession对象来完成对数据库的操作;
  2. 第二种是SqlSessionManager通过localSqlSession变量,记录来与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一线程多次创建SqlSession对象带来的性能损失。

SqlSessionManager特性:

  1. 构造方法私有,我们只能通过newInstance来获取对象;
  2. SqlSessionManager的openSession方法全是借助于底层的sqlSessionFactory的openSession方法来创建SqlSession的。
  3. 该类所实现的SqlSession的方法,如select,update等方法全是借助于内部的SqlSession的代理类来实现的。

我们来看一下SqlSessionInterceptor的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 获取当前线程绑定的SqlSession对象
  final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
  // 如果不等于空,则是第二种模式
  if (sqlSession != null) {
    try {
      // 调用真正的sqlSession来完成相应的操作
      return method.invoke(sqlSession, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  // 否则是第一种模式
  } else {
    // 如果当前线程未绑定SqlSession对象,则创建新的SqlSession对象
    final SqlSession autoSqlSession = openSession();
    try {
      // 通过新建的对象来完成对应的操作
      final Object result = method.invoke(autoSqlSession, args);
      // 提交事务
      autoSqlSession.commit();
      return result;
    } catch (Throwable t) {
      // 回滚事务
      autoSqlSession.rollback();
      throw ExceptionUtil.unwrapThrowable(t);
    } finally {
      // 关闭连接
      autoSqlSession.close();
    }
  }
}

通过对以上代码分析可知,第一种模式中的SqlSession在使用完成后会立即进行关闭。而第二种模式中,与当前线程绑定的sqlSession对象需要先通过startManagedSession方法进行设置:

public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }

当需要提交,回滚事务或是关闭localSqlSession中记录的SqlSession对象时,需要通过SqlSessionManager.commit,rollback以及close方法来完成。
  所以说,SqlSessionManager可以看作是DefaultSqlSessionFactory的一个优化类了,它所做的就是通过SqlSessionManager自己维护的一个ThreadLocal,实现session的复用,所以也是一种线程安全的实现方式。所以我们前面的例子也可以改成如下:

inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSession工厂
//SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionManager sessionManager = SqlSessionManager.newInstance(inputStream);
sessionManager.startManagedSession();
String statement = "com.mapper.IStudentMapper.getAll";
List student = sessionManager.selectList(statement);

总结

对于获取SqlSession,我们可以稍微总结一下:

  1. 获取SessionFactory的方式有两种,一种是通过SqlSessionFactoryBuilder来进行构建,获取DefaultSessionFactory,另一种是通过SqlSessionManager来进行获取,其实,SqlSessionManager获取SessionFactory底层也是通过SqlSessionFactoryBuilder的build方法来获取的,不过多封装了一层。SqlSessionManager优于Builder的方面在于session复用和线程安全,所以我们还是建议使用后者;
  2. 无论使用哪种方式获取SessionFactory,获取SqlSession的方式都只有一种,就是通过SessionFactory的openSession方法来获取;
  3. 仅仅获取SqlSession这一块就使用了工厂方法模式,建造者模式,代理模式,策略模式等多种设计模式,值得我们了解和学习。

你可能感兴趣的:(mybatis)