Mybatis-Sqlsession 创建过程

435 阅读3分钟

背景

Sqlsession 主要包含数据源,执行器,事务管理等。

入口

Sqlsession可以通过SqlSessionFacotry#openSessionFromDataSource 得到。

org...defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  //获取数据源环境
  final Environment environment = configuration.getEnvironment();
  //创建事务工厂
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  //创建事务
  tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  //创建执行器
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);//构建sqlsession
}

创建事务工厂&数据源工厂

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
}

事务工厂从environment 中取出,我们回到environment创建的入口看看(environment 对象的解析在 XMLConfigBuilder的environmentsElement 方法中),

private void environmentsElement(XNode context) throws Exception {
  for (XNode child : context.getChildren()) {
    String id = child.getStringAttribute("id");
    if (isSpecifiedEnvironment(id)) {
    //transactionManagerElement 和 dataSourceElement 方法中根据配置的type 从 typeAliasRegistry(类别名管理器) 中拿到对应的类型
      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());
    }
  }
}

事务工厂类型和数据源类型别名在Configuration 初始化过程中就已经添加

public Configuration() {//2中事务工作
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    //三种数据源工厂
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

我们demo中配置的数据源type为POOLED,代表池化的数据源,也就是有线程池维护连接提高网络io效率,具体内容后面我们单独分析。

创建执行器

执行器是帮助sqlsession去和数据库交互的执行者,默认有三种执行器 batch,reuse,simple,默认用的是simplebatch主要是批处理和存储过程使用,reuse会用map缓存statment 而不会每次都创建新的。但是我们看到这三者都实现自Executor接口,而Executor的实现类却有4个,多了一个CachingExecutor,我们看看这个类的实现。

public class CachingExecutor implements Executor {
  private final Executor delegate;
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
  }

很显然类似包装器,就像dubbo里面wrapper,CachingExecutor实现了缓存的效果,比如从db里面query一行数据,将他缓存起来,后面根据查询条件去缓存里面取,有数据则直接返回,具体的包装逻辑在Configuration#newExecutor()中。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    Executor executor;
    if (ExecutorType.BATCH == executorType) { //...
    } else { executor = new SimpleExecutor(this, transaction);}
    if (cacheEnabled) {//包装CachingExecutor
      executor = new CachingExecutor(executor);}
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

方法代码中还有一行关键代码interceptorChain.pluginAll(executor),这块代码便是我们熟悉的mybatis-plugins,平时常用的pagehelper 也是通过此功能实现的,下面我们重点看看这个拦截器的来龙去脉(这块逻辑有点绕,大家要多调试)。 interceptors 属性是在加载xml时初始化的。

#mybatis-config.xml
<plugins>
    <plugin interceptor="package.Class1"></plugin>
    <plugin interceptor="package.Class2"></plugin>
</plugins>
#
private void pluginElement(XNode parent) throws Exception {
  for (XNode child : parent.getChildren()) {//遍历plugin 所有标签
    String interceptor = child.getStringAttribute("interceptor");
    Properties properties = child.getChildrenAsProperties();
    Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();//实例化插件实现类
    interceptorInstance.setProperties(properties);
    configuration.addInterceptor(interceptorInstance);//添加到configuration
  }
}
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {// 遍历配置的拦截器实现对象
      target = interceptor.plugin(target);
    }
    return target;
}
default Object plugin(Object target) {
   return Plugin.wrap(target, this);
}
public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

分析下,在进入pluginAll 方法时,传入的是CachingExecutor 对象,然后通过 Plugin.wrap()CachingExecutor 对象放入 jdk 动态代理的回调类 Plugin 中,返回动态代理类,此时target 已经是动态代理对象,后面再次进入循环,以此类推,debug 结果如下。 image.png 可以看到 object 经历了2层的jdk动态代理,最后返回的是一个代理对象$Proxy3@22187,我们用框图的形式再次展示下对象组装结构。

image.png

执行器执行流程

我们模拟下调用链路,调用的过程属于责任链模式,顺序应该比较明确就是写在前面的先添加,后执行。

sequenceDiagram
DefaultSqlSession->>$Executor2: query()
$Executor2->>$Executor2: invoke()
$Executor2->>MyPlugin2: intercept()
MyPlugin2->>$Executor1: query()
$Executor1->>$Executor1: invoke()
$Executor1->>MyPlugin1: intercept()
MyPlugin1->>CachingExecutor: query()
CachingExecutor->>db: query()

到这里就分析完了。