????????MyBatis-Spring版本:2.1.0-SNAPSHOT。
????????源码注释:https://github.com/MonkeyOneCool/mybatis-spring-sourcecode-study/tree/master。
1 简介
????????在我们使用MyBatis时,大部分情况下都不是单独使用的,都是需要和Spring进行集成后再使用。那么下面就来看下MyBatis是如何与Spring进行集成的(我之前写过对MyBatis源码进行分析的文章《较真儿学源码系列-MyBatis核心流程源码分析》,建议看一下,因为我下面的分析都是基于此之上再进行分析的)。
2 SqlSessionFactoryBean
????????我们在单独使用MyBatis的时候,是通过SqlSessionFactoryBuilder的build方法生成的SqlSessionFactory,而在和Spring集成后,是通过SqlSessionFactoryBean来生成的:
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setMapperLocations(new ClassPathResource("org/mybatis/spring/demo/xml/UserMapper.xml"));
return factoryBean.getObject();
}
? ? ? ? 可以看到,这里将数据源和mapper文件传给SqlSessionFactoryBean了。SqlSessionFactoryBean本身是个FactoryBean,熟悉Spring源码的同学应该知道,FactoryBean的getObject方法的返回值会被做成bean放进IoC容器中(我之前有写过对Spring源码的解析《较真儿学源码系列-Spring IoC核心流程源码分析》,感兴趣的话可以查看)。那么下面就来看下其getObject方法的实现:
/**
* SqlSessionFactoryBean:
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* 第7行代码处:
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
//...
if (xmlConfigBuilder != null) {
try {
//之前在分析MyBatis的过程中已经解释过了,通过XMLConfigBuilder的parse方法来解析配置文件的内容,放到Configuration里
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
/*
这里会把SpringManagedTransactionFactory赋值给transactionFactory,所以最终用到的Transaction会是Spring事务管理器创建的Transaction;
最终用到的connection会是事务管理器创建好的连接
*/
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//之前也说过,创建XMLMapperBuilder并调用其parse方法,会对mapper文件进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
//调用build方法,将Configuration放进SqlSessionFactoryBuilder中
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
????????至此就明白了SqlSessionFactoryBean的作用就是将MyBatis的配置信息放到Configuration对象中。
3 Spring如何去管理Mapper接口
3.1?MapperFactoryBean
?????????如果想把一个类交给Spring容器来管理的话,我们只需要在这个类上加上@Component或@Service或@Controller注解即可。但是在MyBatis中我们却不能在这样做,因为mapper是接口。如果是接口或者抽象类的话,是不能被Spring所实例化的:
/**
* ClassPathScanningCandidateComponentProvider:
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
/**
* ClassMetadata:
* 第6行代码处:
*/
default boolean isConcrete() {
return !(isInterface() || isAbstract());
}
? ? ? ? 这个时候就需要MapperFactoryBean登场了,它会对mapper接口进行包装:
@Bean
public MapperFactoryBean userMapper() throws Exception {
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(UserMapper.class);
mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
return mapperFactoryBean;
}
????????同理,因为是个FactoryBean,所以查看其getObject方法实现:
/**
* MapperFactoryBean:
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* SqlSessionTemplate:
*/
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
????????可以看到,最终就是通过调用sqlSession的getMapper方法来创建出代理类,之前在分析MyBatis的文章中已经讲过了。
3.2?并发控制
? ? ? ? 这里需要对SqlSessionTemplate单独做一些分析。我们之前对MyBatis源码进行分析的时候,如果细心的话可以发现DefaultSqlSession乃至整个MyBatis源码中都很少有加锁的控制。那么可以认为DefaultSqlSession是线程不安全的(这点其实没毛病,MyBatis的宗旨就是简单轻量化)。那么现在与Spring集成后,就不得不考虑这点了。SqlSessionTemplate是mybatis-spring项目单独封装的类,下面来看下它的构造器:
/**
* SqlSessionTemplate:
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//这里是在动态代理,生成代理类。重点关注下最后一个参数,之后在调用sqlSessionProxy的方法时会调用到SqlSessionInterceptor的invoke方法
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
/**
* SqlSessionInterceptor:
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//重点看下是如何获取到sqlSession的
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//目标方法的调用
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//...
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
/**
* SqlSessionUtils:
* 第33行代码处:
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
/*
获取同一个线程之前有没有创建过sqlSession
Spring事务相关的获取资源的方法,这里不详细去看了,底层就是在使用ThreadLocal
*/
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//通过sqlSessionHolder获取sqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
//如果之前存在sqlSession的话,就直接返回
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//之前不存在sqlSession,说明当前线程是第一次获取,此时通过openSession方法获取到sqlSession
session = sessionFactory.openSession(executorType);
//并放进ThreadLocal中(如果事务同步激活的话)
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
? ? ? ? 可以看到,Mybatis集成Spring后,是通过ThreadLocal来保证的并发安全(我之前写过对ThreadLocal进行源码分析的文章《较真儿学源码系列-ThreadLocal(逐行源码带你分析作者思路)》,感兴趣的话可以查看)。这样的话,每个线程都有自己单独的sqlSession。
? ? ? ? 需要注意的一点是:如果事务没有开启的话,ThreadLocal中是不会放入值的。这样造成的结果就是每次请求(注意这里的措辞,不是同一个线程)都是new出来的新的sqlSession。这样虽然不会有并发问题,但是会使缓存失效。还记得MyBatis的一级缓存是sqlSession级别的吗?所以这也就是为什么没有开启事务,MyBatis一级缓存失效的原因。因为每次请求都会创建一个新的sqlSession,不管在不在同一个线程内。之前sqlSession的缓存都作废了。
? ? ? ? 至于为什么会这么做,为什么事务不开启的时候不往ThreadLocal里存值。猜测是为了Spring事务的概念。如果一个方法里面有多条查询SQL的语句的话,那么可以认为这几条查询语句是相互独立的,而如果在方法上加上@Transactional注解的话,则代表这里面的所有查询SQL是一个整体,所以需要共用同一个sqlSession、共用一级缓存(?我之前有写过对Spring事务的源码解析《较真儿学源码系列-Spring事务管理核心流程源码分析》,感兴趣的话可以查看)。
3.3?@MapperScan
? ? ? ? 前面讲解了通过MapperFactoryBean包装的方式来创建mapper接口的bean,但是我们不可能每次创建出一个mapper接口,就手动用MapperFactoryBean包装一下,再注入到Spring容器中。这个时候我们就会用到@MapperScan注解,以此来提供统一的注册mapper接口的方式。查看其实现:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
//...
}
? ? ? ? @MapperScan注解上会引用到MapperScannerRegistrar类,因其实现了ImportBeanDefinitionRegistrar接口,所以我们只需要看其registerBeanDefinitions方法的实现就行了:
/**
* MapperScannerRegistrar:
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取@MapperScan注解上的配置信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
/**
* 第10行代码处:
*/
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//...
// for spring-native
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//这里是将MapperScannerConfigurer注册为一个bean定义
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
? ? ? ? 可以看到,最后是注册了MapperScannerConfigurer。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么接下来就来看下其postProcessBeanDefinitionRegistry接口的实现:
/**
* MapperScannerConfigurer:
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//创建扫描器,可以对mapper进行通用的扫描
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//...
scanner.registerFilters();
//进行扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/**
* ClassPathMapperScanner:
* 第13行代码处:
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;
//...
if (acceptAllInterfaces) {
// default include filter that accepts all classes
/*
这里的作用是添加一个includeFilter,使其可以跳过ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader)方法
的校验,之后能跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里
*/
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
/**
* ClassPathBeanDefinitionScanner:
* 第15行代码处:
*/
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
* ClassPathMapperScanner:
* 第51行代码处:
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用原有的扫描逻辑(在其中会调用到isCandidateComponent方法,也就是会跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里)
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
//这里是ClassPathMapperScanner自己改写的内容
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
/**
* 第68行代码处:
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//这里就可以看到:ClassPathMapperScanner改写了Spring默认的实现,使mapper接口可以被注册到Spring容器中
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
/**
* 第75行代码处:
*/
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
//遍历所有的mapper
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
//...
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//给MapperFactoryBean的构造器传参,参数为mapper接口的Class对象
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
// for spring-native
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
//bean定义的类型需要从原始mapper的类型改写为MapperFactoryBean(本行代码和上面给构造器传参的代码的顺序不能换)
definition.setBeanClass(this.mapperFactoryBeanClass);
//...
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
//...
}
}
? ? ? ? 上面最终会把类型为MapperFactoryBean的bean定义注册进去,之后的操作就是调用MapperFactoryBean的getObject方法了,之前在分析MyBatis的过程中已经讲过了。
原创不易,未得准许,请勿转载,翻版必究
|