Mybatis SQL执行流程(一)

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。Mybatis SQL执行流程(一),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

在前面三篇关于Mybatis配置文件解析的文章中,已经介绍过了,Mybatis会把全局配置文件和SQL配置文件解析的结果全部存在Configuration类的实例中

而我们在使用Mybatis执行SQL是,首先需要创建一个SqlSessionFactory的工厂实例,在SqlSessionFactoryBuilder的build(Configuration config)方法中,就会把解析得到的配置作为入参,构造一个DefaultSqlSessionFactory实例

// 到这里配置文件已经解析成了Configuration
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

然后通过DefaultSqlSessionFactory就可以调用openSession()来获得一个SqlSession实例,就可以直接执行SQL了,那么这个SqlSession是一个什么东西,它调用方法执行SQL的流程又是怎样的呢?

一、创建Executor实例

其实Mybatis的SqlSession是采用门面设计模式来实现的,它自身并不具备执行SQL的能力,而是由它内部的Executor实例来执行相关方法。我们通过SqlSessionFactory的openSession()方法创建SqlSession,其实内部就是在创建一个Executor实例,我们具体看源码

在解析数据源配置的时候,就会在Environment实例中设置事务工厂,如果没有配置则使用默认的ManagedTransactionFactory,然后创建一个事务,这个时候只是创建了一个事务实例对象,并没有创建与数据库的连接

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);
    /**
     * 创建一个sql执行器对象
     * 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回
     * 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor
     */
    final Executor executor = configuration.newExecutor(tx, execType);
    // 创建返回一个DefaultSqlSession对象返回
    return new DefaultSqlSession(configuration, executor, autoCommit);
}

1.1 Executor类型

在创建Executor实例的时候,需要指定其类型,Executor提供了三种类型的实现:

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

如果没有指定,就会使用SIMPLE作为默认的类型,这三种类型的Executor有什么区别呢?

SIMPLE:每次执行SQL语句,就单独开启一个Statement对象,用完立即关闭Statement对象

REUSE:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象

BATCH:执行update,将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

注:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

这三类是Mybatis提供的基本的Executor类型,但是在Mybatis中,还有一种特殊的Executor类型,就是CacheExecutor,我们Mybatis的配置文件中,可以通过cacheEnabled属性来开启,如果该属性为true,在创建Executor的时候,就会把基本的Executor封装成一个CacheExecutor

1.2 创建Executor实例

根据指定的Executor类型创建对应的Executor实例,然后判断是否需要封装成CachingExecutor实例

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  //判断mybatis的全局配置文件是否开启缓存
  if (cacheEnabled) {
    //把当前的简单的执行器包装成一个CachingExecutor
    executor = new CachingExecutor(executor);
  }
  // TODO:调用所有的拦截器对象plugin方法  
  //插件: 责任链+ 装饰器模式(动态代理)
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

1.3 创建插件的代理对象

创建完Executor实例之后,接着会去匹配Executor类型的拦截器,遍历所有拦截器的plugin()方法

拦截器的使用在《Mybatis 配置文件解析(一)》的插件配置部分有介绍

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

Interceptor接口中,提供了plugin()方法的默认实现,如果自定义拦截器没有实现plugin()方法,就会调用默认plugin()方法

default Object plugin(Object target) {
  return Plugin.wrap(target, this);
}

Plugin的wrap()方法会获取拦截器上面@Intercepts注解的@Signature注解的信息(拦截的Executor类型,具体的方法)

调用getAllInterfaces()来匹配当前代理类型与@Signature注解指定的类型是否匹配,如果匹配就可以生成一个代理对象,原始对象是Executor实例,当调用代理对象的方法时(也就是执行Executor的方法),会去调用Plugin类的invoke()方法

public static Object wrap(Object target, Interceptor interceptor) {
  // 获得interceptor配置的@Signature的type
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  // 当前代理类型
  Class<?> type = target.getClass();
  // 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

至此就生成了最后的Executor实例,然后对Executor实例进行封装,生成一个DefaultSqlSession实例返回,这是就是使用SqlSession来操作SQL了

二、获取Mapper接口代理对象

《Mybatis 配置文件解析(二)》的生成代理工厂部分,以及详细介绍了创建代理工厂的过程,在我们使用的时候,直接调用SqlSession的getMapper()方法就可以获得Mapper接口的代理对象

我们以UserMapper为例,看一下getMapper()方法的源码是如何实现的

// 创建动态代理
UserMapper mapper = session.getMapper(UserMapper.class);

会去调用Configuration的getMapper()方法,我们前面配置文件解析的文章已经讲过,Mybatis解析得到的配置信息都在configuration里面放着

public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

在Configuration的getMapper()方法中,会调用mapperRegistry的getMapper()方法,这个MapperRegistry里面存放的就是所以Mapper接口对应的代理工厂MapperProxyFactory

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

在MapperRegistry的getMapper()方法中,MapperRegistry的knownMappers属性中缓存了Mapper接口和代理工厂的映射,根据接口类型拿到对应的代理工厂,然后调用代理工厂的newInstance()方法就可以拿到具体的代理对象,关于MapperProxyFactory在前面的文章已经详细讲过了,这里不做过多赘述

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 直接去缓存knownMappers中通过Mapper的class类型去找我们的mapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // 缓存中没有获取到 直接抛出异常
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 通过MapperProxyFactory来创建我们的实例
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153640.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!