在前面三篇关于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