mybatis专栏https://blog.csdn.net/worn_xiao/category_6530299.html?spm=1001.2014.3001.5482
这里强烈建议有欲望阅读本文的同学,先对我上面写的博文进行阅读,或者是先阅读一下mybatis的源代码,要不然有可能看不懂奥,可以的话spring事务相关的源代码了解一点。
一 问题背景
1 mybatis configuration何时加载问题
2 mybatis mapper文件何时加载,创建mapper代理
3 mybatis 插件注册问题
4 mybatis 事务委托管理问题
5 mybatis sqlsession的线程安全如何保证
如上面所示提出了5个问题,就是我们spring整合mybatis的时候我们主要需要做的核心问题。解决了这5个核心问题可以说你对spring整合mybatis的原理就很清晰了
二 spring整合mybatis
2.1 使用
(1) jdbc.properties文件
• jdbc.driver=com.mysql.jdbc.Driver
• jdbc.url=jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=UTF-8
• jdbc.uid=root
• jdbc.password=123456
(2) mybatis-config.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="bean.User"/>
</typeAliases>
<!--<environments default="development">-->
<!--<environment id="development">-->
<!--<transactionManager type="JDBC"/>-->
<!--<dataSource type="POOLED">-->
<!--<property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=UTF-8"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="123456"/>-->
<!--</dataSource>-->
<!--</environment>-->
<!--</environments>-->
<mappers>
<!-- // power by http://www.yiibai.com -->
<mapper resource="xml/User.xml"/>
</mappers>
</configuration>
如上配置所示,如果在没有spring的情况下。那么我们为mybatis配置数据库连接,就在这个mybatis-config.xml文件中配置enviroments元素的数据库连接。有spring了我们就在数据源交给spring来初始化加载了。单独踢到配置文件里面了。
(3) application.xml文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans ">
<!-- 加载db.properties文件中的内容,db.properties文件中的key要有一定的特殊规则 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源,使用dbcp连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="30"/>
<property name="maxIdle" value="5"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
</bean>
<!-- 配置Mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描包路径,如果需要扫描多个包中间用半角逗号隔开 -->
<property name="basePackage" value="com.hanson.ssm.mapper"/>
<!-- 这边不能使用ref="sqlSessionFactory"原因是因为上面加载配置文件导致这边引用会报错 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 事务管理器 对mybatis操作数据库事务控制,spring使用jdbc的事务控制类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 dataSource在applicationContext-dao.xml中配置了 -->
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
如上所示是spring集成mybatis的配置。我相信很多同学都这么配置,但是却不知道为什么要这么配置,接下来我会把原理的东西跟大家讲一下。
2.2 原理
2.1 configuration代码分析
我们知道mybatis有一个所有配置的大管家,就是configuration,它是mybatis在使用之前必须通过工厂先进行初始化的类,接下来我们看一下在没有spring的时候configuration我们怎么使用的.
@BeforeClass
public static void setup() throws Exception {
createBlogDataSource();
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
sqlMapper = new SqlSessionFactoryBuilder().build(reader);
}
我相信这段代码大家都有见过。这个初始化过程我的另一篇博客里面已经有记载了,大家可以去看我的另一篇博客我就不仔细说了。那么spring和mybatis要整合,这个初始化的过程肯定是交给spring进行启动加载了。正如上面的配置所示,SqlSessionFactoryBean 里面配置了,mybatis-config.xml的配置文件。那么我们来看一下spring启动的时候,这个类做了什么。
首先我们看一下类定义
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean,ApplicationListener<ApplicationEvent>
可以看到实现了FactoryBean和spring的生命周期类 InitializingBean,启动监听器。好的那么接下来我们看一下具体的核心方法
@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();
}
如上代码所示在这里对configlocation的配置路径做了一个校验,目的就是校验mybatis-config.xml的配置有没有配
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
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 xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("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.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
如上代码所示可以看到
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
这里就是我们targetConfiguration创建的代码,如上如果已经有configuration了就用已经有的。如果没有的话就通过configLocation配置的mybatis-config.xml去生成一个具体的configuration类,这里怎么解析的可以去看我对Mybatis模块初始化的博文。实际上mybatis初始化就干了一件事情,就是通过.mybatis-config.xml文件生成一个configuration类。再往下看的话就是往configurantion类里面塞一些插件,类型映射器什么的,这个实际上就是定义好类,通过spring配置到对应的地方就可以了
2.2 mapper代理生成
先说一下配置方式,一种我们可以在SqlsessionFactoryBean中配置mapperlocation的映射路径,一种是配置configLocation然后加上mybatis-config.xml,然后在里面配置mapper的映射路径,那么为什么嗯。这里也是要看SqlSessionFactoryBean的源码来分析一下了。
先说第一种配置了mapperLocation的
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 xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("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.");
}
这段代码是在sqlSessionFactionBean中的,就会加载mapperLocation的路径,然后解析生成 MapperProxyBean,添加到configuration类的映射中.
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
如下代码所示
private void bindMapperForNamespace() {
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException var4) {
}
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);
}
}
}
这里实际上会做双重校验,如上代码片段所示就是加载命名空间对应的mapper类添加到Configuration中,实际上这里是优先走xml配置,然后通过hasMapper()方法判断这个方法是否有添加进来,如果没有再通过名称空间如下代码所示以类的方式进行加载.
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
{
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}}}}
如上代码所示就是通过对mapper进行MapperProxyFactory的包装。添加到configuration中进行缓存。到时候执行的时候就执行mapper代理了。好了本来只打算说一下怎么集成的。回归正题,这个就是第一种方式加载mapperlocation。另一种就是通过mybatis-config.xml来配置的其实是
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
可以看到xmlConfigBuilder.parse()这个解析类,实际上就是这个代码里有解析mapper的代码
这里我就不深入讨论了。大家可以自行去看。看到这里我们不难发现我们已经把MapperProxyFactory生成好了,可是有一个问题也是值得关注的,那么Spring集成了Mybatis以后,我们平时使用Mapper的时候为什么加一个@Autoware就可以使用了呢?可能很多同学都没有深入的去思考这样的问题吧。那我们现在就来探究一下是为什么?我们先分析一下如果说要把MapperProxyFactory注入到Spring中就得把MybaperProxyFactory注册成Bean定义然后,放到Spring的容器中,然后从容器中捞起来,对@autoware的Mapper接口进行实例化。那么到底是不是这样呢?开始我们的表演吧
<!-- 配置Mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描包路径,如果需要扫描多个包中间用半角逗号隔开 -->
<property name="basePackage" value="com.hanson.ssm.mapper"/>
<!-- 这边不能使用ref="sqlSessionFactory"原因是因为上面加载配置文件导致这边引用会报错 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
很多同学都配置过这个吧,那么为什么需要配置这个呢?这个的作用是什么,很多同学估计都只是配置了,而不知道它背后真正的原因吧。好了我们来跟踪一下源代码吧
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
如上代码片段所示,一如既往实现了spring的bean的后置处理器。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
那么这里我们可以以看到就是把用户配置的东西先装到scanner中,好的那么核心代码应该是后两行了,这里先说一下作用,就是扫描所有的指定包下面的mapper文件,然后通过这些个Mapper接口文件生成MapperFactoryBean类,放到spring容器中进行bean管理。MapperFactoryBean实现了FactoryBean方法,重写了getObjet()方法,也是Spring的getBean方法的实现。这里就可以获取到一个Mapper的实现类,例如
跟如上所示的代码类似,通过mapperFactoryBean.getObject()方法就可以获取Mapper接口实现类。具体这个Spring怎么实现的大家可以下去研究一下。我这里先说它是怎么把mapperFactoryBean注入到spring容器的。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
这里可以看到它是通过调用spring的类扫描父类来扫描的。那么spring的类扫描是怎么工作的呢
首先得看一下这里MyBatis其实自定义了一个自己的类扫描映射类,为什么需要自定义类扫描映射类呢。这里我先提个头,Spring并没有规定扫描@Mapper能够把这个注解对应的类加载到容器,那么这里是怎么实现的 ?先看spirng的类扫描
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
Iterator var8 = candidates.iterator();
while(var8.hasNext()) {
BeanDefinition candidate = (BeanDefinition)var8.next();
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
if (this.checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
核心代码Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);它是怎么通过扫描包来实现把包下面的类包装成beanDefinition的呢?
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
return this.componentsIndex != null && this.indexSupportsIncludeFilters() ? this.addCandidateComponentsFromIndex(this.componentsIndex, basePackage) : this.scanCandidateComponents(basePackage);
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
LinkedHashSet candidates = new LinkedHashSet();
try {
String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = this.logger.isTraceEnabled();
boolean debugEnabled = this.logger.isDebugEnabled();
Resource[] var7 = resources;
int var8 = resources.length;
for(int var9 = 0; var9 < var8; ++var9) {
Resource resource = var7[var9];
if (traceEnabled) {
this.logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
if (this.isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
if (debugEnabled) {
this.logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else if (debugEnabled) {
this.logger.debug("Ignored because not a concrete top-level class: " + resource);
}
} else if (traceEnabled) {
this.logger.trace("Ignored because not matching any filter: " + resource);
}
} catch (Throwable var13) {
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, var13);
}
} else if (traceEnabled) {
this.logger.trace("Ignored because not readable: " + resource);
}
}
return candidates;
} catch (IOException var14) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", var14);
}
}
核心代码candidates.add(sbd),如上代码所示,通过吗包路径下面的类,加载进来,然后或者类或者类对应的注解信息调用如下所示的两个方法来决定要不要把对应包下面的类加载到spring容器中。那么我们来看看spring是怎么写这两个类的
this.isCandidateComponent(metadataReader)
isCandidateComponent((AnnotatedBeanDefinition)sbd)
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
Iterator var2 = this.excludeFilters.iterator();
TypeFilter tf;
do {
if (!var2.hasNext()) {
var2 = this.includeFilters.iterator();
do {
if (!var2.hasNext()) {
return false;
}
tf = (TypeFilter)var2.next();
} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));
return this.isConditionMatch(metadataReader);
}
tf = (TypeFilter)var2.next();
} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));
return false;
}
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && (metadata.isConcrete() || metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()));
}
如上代码段所示第一个是通过用户的配置来定义哪些类需要包含进来,哪些类不需要包含进来。第二个是判断注解有没有什么没达到的限制掉件。所以通过spring的源代码分析。原来spring扫描一个包控制哪些类要包装成bean定义,哪些不需要是通过规则来的。那么spring自己的规则是什么呢
excludeFilters
includeFilters
这个时候我们就对这两个容器产生了关注。Spring都识别哪些注解啊?为什么
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(ClassUtils.forName("javax.annotation.ManagedBean", cl), false));
this.logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
} catch (ClassNotFoundException var4) {
}
try {
this.includeFilters.add(new AnnotationTypeFilter(ClassUtils.forName("javax.inject.Named", cl), false));
this.logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
} catch (ClassNotFoundException var3) {
}
}
接下来我们看到了原来在includeFilters里面Component,ManagedBean,Named奥原来在这里定义了需要扫描进行Spring容器的注解类.好了接下来我们明白了spring对注解的扫描原理以后就可以开始看怎么整合mybatis了,这里补充一点@service @repository实际内部都有表示@componet类的。所以也是可以扫描到的。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
可以看到mybatis是集成spring的,那么是怎么做的呢,如果换我们我们肯定也会重写过滤规则,接收@Mapper注解对吧?
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
好了核心代码,果然Mybatis自定义了扫描类,并且把Mapper注解放进去了,当然这里其实应该仔细阅读以下,其实它说的是如果使用了@Mapper的注标识,或者配置了mapper接口就只扫描对应该的Mapper,默认如果不配置acceptAllInterfaces=true它是会把所有包下面的mapper都扫描的,好了这回懂了原来这里我们是可以配置到底是扫描@Mapper注解还是不,如果不配置的话默认是全部扫描的。好了这就是Mybatis自定义的类扫描机制。接下来还有怎么把接口解析成mapperFactioryBean的呢?
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
好了接下来我们看一下
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
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
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
如上代码段所示 definition.setBeanClass(this.mapperFactoryBeanClass);重写了beanClass方法,设置了bean定义。又改变了AutowareByte类,原来如此。其实是在这里生成了mapperFactoryBean然后实际接口是Mapper接口。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
再看一下getObject方法,好了明白了。其实在spring初始化SqlSessionFactory并的时候已经通过解析mapperlocation或者configrationLoaction的方式生成了mapperProxy到configuration中了,spring只是定义了一个获取bean的工厂,从Confiuration中获取到mapperProxy对应的对象,这里或者的参数正好是mapper接口。这回明白了,其实这里就是扫描mapper生成MapperFactoryBean定义重写了getObject()方法,从spring的configuration大管家中获取到mapperProxy好了说到这里已经说完了。Mybatis整合spring的mapper整合原理到此结束。
2.3 plugin注册问题
这里我就不写关于Mybatis插件的实现原理了,请关注我对mybatis插件原理讲解的那篇文章去看Mybatis的插件原理。
Mybatis插件注册的地方有两个,一个可以放到mybatis-config.xml中,加上这一条配置就可
<!-- 配置分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
以了,另一个插件注册的方式是放到spring中根SqlSessionFactory一起进行配置。比如
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.example.model" />
<property name="plugins">
<array>
<bean class="com.example.mybatis.plugins.XXXPlugin">
<property name="properties">
<value> property-key=property-value </value>
</property>
</bean>
</array>
</property>
</bean>
第二种配置方式,如上代码片段所示,我们可以通过Spring在初始化的时候直接初始化,其实这个源代码我就不多做解释了,为什么要提出来呢,主要是后面我还会梳理一下Springboot集成Mybatis的一个配置情况。所以需要提一下。Springboot中插件的注册方式相对比较特殊。
2.4 事务托管问题
好了接下来Spring整合mybaits的核心还有一个很重要的点,那就是Mybatis如何把事务交给Spring进行管理的,相信很多人都跟我有一样的疑问吧。这里来梳理一下吧。要保证Spring的事务跟Mybatis的事务是一个事务。那么就意味着我们要让Spring和Mybatis共用一个数据库连接Connect。这样才能保证事务的一致性。
好了接下来我们从源代码层面来分析一下事务的管理。首先看一下配置
<!-- 事务管理器 对mybatis操作数据库事务控制,spring使用jdbc的事务控制类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 dataSource在applicationContext-dao.xml中配置了 -->
<property name="dataSource" ref="dataSource" />
</bean>
这里很简单就是把数据源注入到Spring的事务管理类中。好了那么我们来看一下Spring的核心事务类
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
如上代码是Spring TransactionInterceptor的代码如果,用了@Trancation注解或者是用切面通知都会走如上所示的代理代码。再接下来就是
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
}
}
如上就是Spring事务的核心代码。我们知道事务最终转化的结果无非就是获取Connnection那么我们就看看Spirng是从哪里获取Connection的。
@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
如上代码我们接着往下看PlatformTransactionManager tm.getTransaction这样的,我们再点进去
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
实际上它是一个事务接口,交给AbstractPlatformTransactionManager去进行实现的,
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
如上源代码所示,我们看到实际上它这里获取事务信息是 doGetTransaction()方法来获取的,那我们可以看看这里是从哪里获取怎么获取的。
protected abstract Object doGetTransaction() throws TransactionException;
是一个抽象类。那么就是从子类里面获取。子类就是我们配置的那个类。这里我不细讲了有兴趣的同学可以去看一下Spring的事务框架,其实我们可以配置两种数据源事务管理,一种是单数据源的,一种是JTA分布式事务的数据源。
@SuppressWarnings("serial")
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
好了我们发现Spirng获取连接的地方应该就在我们配置数据源的这里了。所以为什么我们要配置数据源。这次大家应该能理解了吧
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
如上代码所示是获取事务的代码,那么我就分析一下实际上是通过TransactionSynchronizationManager 核心类,管理了线程对应的资源,也就是线程对应的数据库连接,保证了线程和数据库连接的绑定关系.通过获取到连接用ConnectionHolder 进行包装之后,封装了数据库连接到 DataSourceTransactionObject中了,好的那么我们再来看一下TransactionSynchronizationManager代码吧。
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
如上代码所示是这个核心类的线程变量,它就是通过线程绑定资源的方式定义的。我们看一下获取资源的代码
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
/**
* Actually check the value of the resource that is bound for the given key.
*/
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
那么我们一开始来线程资源池中通过datasource这个key来取连接肯定是没有的,那么我们是什么时候创建的事务对象,后又放到这个资源池的呢,让我们回到刚刚的代码。
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
回到这里,我们进去到dobegin()方法,这里就到了我们的子类里面
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
如上代码所示 DataSourceTransactionObject 先对我们刚才从资源池中的,以datasource作为key的来获取到的transaction进行判断。判断这个transaction有没有,如果没有再通过datasource创建一个新的CONNECTION绑定到connnerHolder中,然后调用下面的核心代码,通过ThreadLocal绑定数据源与连接的关系。
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
好了讲到这里大家已经明白了,实际上Spring对事物的管理,本质上如果是同一个线程就获取ThreadLocal绑定中帮定的同一个数据源的连接对象。也就是我们通过线程,数据源唯一确定一个连接。这就是Spring对事物的管理过程。好了看Spring事物到这里。我们就在想要让Spring和Mybatis同一个事务,同一个连接到底要怎么做呢。其实我们想到的就是在 mybatis创建数据库连接的时候,如果也能用同一个数据源,修改 Mybatis的事务管理器,让它用Spring的这个事务管理资源池,也一样绑定到一个线程,那不就实现了Spirng事务和Mybatis事务的一致性了吗。好了这里呢我希望同学们先去看一下我对Mybatis事务的讲解的代码。
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);
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();
}
}
如上代码所示,我们直接看Mybatis中实际上就是通过 TransactionFactory.newTransaction来够构造一个事务管理对象的,那么如果我们重写这个工厂,然后这里我们直接从Spring的线程资源池中获取不就可以了么。好我们看看它是怎么做的。这里我们要回到SqlSessionFactoryBean的代码中去了。
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
在Spring初始化SqlSessionFactoryBean 构造Mybatis的configuration的时候,就创建了一个事务管理工厂。那么如果我们通过SpringManagedTransactionFactory的工厂创建的事务,不就是Sping和Mybatis就可以公用一个事务了。 接下来我们看一下
SpringManagedTransactionFactory 这个类的源代码。
public class SpringManagedTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
@Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}}
可以看到 SpringManagedTransactionFactory 这个工厂类通过newTransaction创建了一个SpringManagedTransaction 事务,也就是Spring和Mybatis实现共同事务的媒介。那么我们看一下 SpringManagedTransaction的源代码。
public class SpringManagedTransaction implements Transaction {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
private boolean isConnectionTransactional;
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
@Override
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
this.connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
this.connection.rollback();
}
}
@Override
public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
@Override
public Integer getTimeout() throws SQLException {
ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (holder != null && holder.hasTimeout()) {
return holder.getTimeToLiveInSeconds();
}
return null;
}
}
我们来看一下getConnection()的时候,就是获取数据源连接的时候,那么这里一个类 DataSourceUtils。通过数据源做为参数获取到连接。那么这个类到底做了什么呢?
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}
catch (IllegalStateException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
}
}
实际上这个类就是dogetConnection(),再往下看,接下来就是我们的核心代码了呢。我们接下来会看到Spring的代码。实际上准确的说DatasourceUtils也是Spring的代码。由于代码量比较大就不贴出来了。主要还是看一下doGetConnnection()这个典型的例子。
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
Connection con = fetchConnection(dataSource);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
如上代码所示是核心代码,TransactionSynchronizationManager 从Spring的资源管理类中根据线程和dataSource就可以获取到一个绑定好的连接。如果没有就创建一个连接然后包装进ConnectionHolder中,再绑定到资源管理器中。这就是Spring整合Mybatis以后实现事务管理的方式。这就使得针对同一个线程,同一个数据源获取到的Connection对象是一个。实现Mybatis的事务托管。
2.5 sqlSession线程安全
由于操作数据库的会话,要交给Spring来进行管理,那么就有一个SqlSession的线程安全需要解决,也就是说一个线程只能拥有一个sqlSession的会话。那么如果是通过Mapper代理还工作,如何解决这个线程安全的问题呢。我们知道Mapper最终会被Spring通过mybatis自定义的类扫描机制,扫描了以MapperFactoryBean的方式保存在容器中。最后通过getObject的方法获取到Mapper的代理类。那么这里会话是如何管理的呢?我们来看一下源代码。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里有一个getSqlSession()方法。我们来看一下这个方法做了什么。
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
原来这里是用了sqlSessionTemplate来获取Mapper对象的。好的再看一下
sqlSessionTemplate的源代码。
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
大家可以看到第二个参数this就是SqlSessionTemplate public class SqlSessionTemplate implements SqlSession 嗯实际上SqlSessionTemplate是SqlSession的子类,那么为什么Spring不直接用Sqlsession来执行,而是要用SqlsessionTemplate来执行呢。我们来读一下代码。
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
正如上代码所示,所有的执行语句都是走的一个sqlSessionProxy来的。那么这个代理是做什么的?其实就是实现线程安全的。它是怎么实现线程安全的呢。看一看
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
看一下代理的方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
核心代码如上所示,获取sqlsession。好了再进入看一下。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
如上代码所示TransactionSynchronizationManager Spring线程资源管理类。这回明白了从线程资源管理类中获取唯一资源通过 sessionFactory,然后没有就创建一个注入到线程资源管理类中。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
如上代码所示 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory)
判断是不是 SpringManagedTransactionFactory 如果是就绑定SqlSession到线程资源中,交给Spring来进行管理。好了这回看明白了。原来Spring集成Mybatis的线程安全问题是通过Sping的资源管理类来实现的。好了关于Sping跟Mybatis整合的源代码的原理我就分析到这里了。后续我还将讲述一下Springboot是如何跟mybatis整合的。期待我的下一篇博文。