mybatis-spring
是 MyBatis
的一個子項目恰力,用于幫助開發(fā)者將 MyBatis
無縫集成到 Spring
中凭疮。它允許 MyBatis
參與到 Spring
的事務(wù)管理中歧譬,創(chuàng)建映射器 mapper
和 SqlSession
并注入到 Spring bean
中燕雁。
SqlSessionFactoryBean
在 MyBatis
的基礎(chǔ)用法中岖妄,是通過 SqlSessionFactoryBuilder
來創(chuàng)建 SqlSessionFactory
宫仗,最終獲得執(zhí)行接口 SqlSession
的蜒犯,而在 mybatis-spring
中组橄,則使用 SqlSessionFactoryBean
來創(chuàng)建。其使用方式如下:
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// 設(shè)置配置文件路徑
bean.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
// 別名轉(zhuǎn)化類所在的包
bean.setTypeAliasesPackage("com.wch.domain");
// 設(shè)置數(shù)據(jù)源
bean.setDataSource(dataSource);
// 設(shè)置 mapper 文件路徑
bean.setMapperLocations(new ClassPathResource("mapper/*.xml"));
// 獲取 SqlSessionFactory 對象
return bean.getObject();
}
SqlSessionFactoryBean
實現(xiàn)了 FactoryBean
接口罚随,因此可以通過其 getObject
方法獲取 SqlSessionFactory
對象玉工。
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@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 {
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);
}
// 設(shè)置對象創(chuàng)建工廠、對象包裝工廠淘菩、虛擬文件系統(tǒng)實現(xiàn)
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 以包的維度注冊別名轉(zhuǎn)換器
if (hasLength(this.typeAliasesPackage)) {
// 掃描之類包下的符合條件的類對象
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
// 過濾匿名類
.filter(clazz -> !clazz.isAnonymousClass())
// 過濾接口
.filter(clazz -> !clazz.isInterface())
// 過濾成員類
.filter(clazz -> !clazz.isMemberClass()).
// 注冊別名轉(zhuǎn)換器
forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 以類的維度注冊別名轉(zhuǎn)換器
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
// 注冊類對象到別名轉(zhuǎn)換器
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
// 設(shè)置插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
// 以包的維度注冊類型轉(zhuǎn)換器
if (hasLength(this.typeHandlersPackage)) {
// 掃描指定包下 TypeHandler 的子類
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().
// 過濾匿名類
filter(clazz -> !clazz.isAnonymousClass())
// 過濾接口
.filter(clazz -> !clazz.isInterface())
// 過濾抽象類
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
// 注冊類對象到類型轉(zhuǎn)換器
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
// 以類的維度注冊類型轉(zhuǎn)換器
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
// 注冊類對象到類型轉(zhuǎn)換器
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
// 注冊腳本語言驅(qū)動
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);
// 配置數(shù)據(jù)庫產(chǎn)品識別轉(zhuǎn)換器
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);
}
}
// 設(shè)置緩存配置
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
// 如果設(shè)置了配置文件路徑遵班,則解析并加載到全局配置中
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();
}
}
// 設(shè)置數(shù)據(jù)源環(huán)境
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// 解析 xml statement 文件
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.");
}
// 創(chuàng)建 sql 會話工廠
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
buildSqlSessionFactory
方法會分別對配置文件、別名轉(zhuǎn)換類潮改、mapper
文件等進行解析狭郑,逐步配置全局配置對象,并最終調(diào)用 SqlSessionFactoryBuilder
創(chuàng)建 SqlSessionFactory
對象汇在。
SqlSessionTemplate
在前章分析 MyBatis
接口層時說到 SqlSessionManager
通過 JDK
動態(tài)代理為每個線程創(chuàng)建不同的 SqlSession
來解決 DefaultSqlSession
的線程不安全問題翰萨。mybatis-spring
的實現(xiàn)與 SqlSessionManager
大致相同,但是其提供了更好的方式與 Spring
事務(wù)集成糕殉。
SqlSessionTemplate
實現(xiàn)了 SqlSession
接口亩鬼,但是都是委托給成員對象 sqlSessionProxy
來實現(xiàn)的。sqlSessionProxy
在構(gòu)造方法中使用 JDK
動態(tài)代理初始化為代理類阿蝶。
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
sqlSessionProxy
的代理邏輯如下雳锋。
private class SqlSessionInterceptor implements InvocationHandler {
@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 {
// 執(zhí)行原始調(diào)用
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 如果事務(wù)沒有交給外部事務(wù)管理器管理,進行提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// 異常為 PersistenceException羡洁,使用配置的 exceptionTranslator 來包裝異常
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) {
// 關(guān)閉 sql session
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
獲取 SqlSession
在執(zhí)行原始調(diào)用前會先根據(jù) SqlSessionUtils#getSqlSession
方法獲取 SqlSession
玷过,如果通過事務(wù)同步管理器 TransactionSynchronizationManager
獲取不到 SqlSession
,就會使用 SqlSessionFactory
新建一個 SqlSession
,并嘗試將獲取的 SqlSession
注冊到 TransactionSynchronizationManager
中辛蚊。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// SqlSessionFactory 和 ExecutorType 參數(shù)不可為 null
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 嘗試從事務(wù)同步管理器中獲取 SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 獲取 SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 新建 SqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 將新建的 SqlSession 注冊到事務(wù)同步管理器中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
事務(wù)同步管理器
每次獲取 SqlSession
時是新建還是從事務(wù)同步管理器中獲取決于事務(wù)同步管理器是否開啟粤蝎。事務(wù)同步管理器用于維護當前線程的同步資源,如判斷當前線程是否已經(jīng)開啟了一個事務(wù)就需要查詢事務(wù)同步管理器袋马,以便后續(xù)根據(jù)事務(wù)傳播方式?jīng)Q定是新開啟一個事務(wù)或加入當前事務(wù)诽里。Spring
支持使用注解開啟事務(wù)或編程式事務(wù)。
注解開啟事務(wù)
在 Spring
工程中可以通過添加 EnableTransactionManagement
注解來開啟 Spring
事務(wù)管理飞蛹。EnableTransactionManagement
注解的參數(shù) mode = AdviceMode.PROXY
默認指定了加載代理事務(wù)管理器配置 ProxyTransactionManagementConfiguration
,在此配置中其默認地對使用 Transactional
注解的方法進行 AOP
代理灸眼。在代理邏輯中卧檐,會調(diào)用 AbstractPlatformTransactionManager#getTransaction
方法獲取當前線程對應(yīng)的事務(wù),根據(jù)當前線程是否有活躍事務(wù)焰宣、事務(wù)傳播屬性等來配置事務(wù)霉囚。如果是新創(chuàng)建事務(wù),就會調(diào)用 TransactionSynchronizationManager#initSynchronization
方法來初始化當前線程在事務(wù)同步管理器中的資源匕积。
編程式事務(wù)
編程開啟事務(wù)的方式與注解式其實是一樣的盈罐,區(qū)別在于編程式需要手動開啟事務(wù),其最終也會為當前線程在事務(wù)同步管理器中初始化資源闪唆。
// 手動開啟事務(wù)
TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// invoke...
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
SqlSession 注冊
如果當前方法開啟了事務(wù)盅粪,那么創(chuàng)建的 SqlSession
就會被注冊到事務(wù)同步管理器中。SqlSession
會首先被包裝為 SqlSessionHolder
悄蕾,其還包含了 SqlSession
對應(yīng)的執(zhí)行器類型票顾、異常處理器。
// ...
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// ...
隨后 SqlSessionHolder
對象通過 TransactionSynchronizationManager#bindResource
方法綁定到事務(wù)同步管理器中帆调,其實現(xiàn)為將 SqlSessionFactory
和 SqlSessionHolder
綁定到 ThreadLocal
中奠骄,從而完成了線程到 SqlSessionFactory
到 SqlSession
的映射。
事務(wù)提交與回滾
如果事務(wù)是交給 Spring
事務(wù)管理器管理的番刊,那么Spring
會自動在執(zhí)行成功或異常后對當前事務(wù)進行提交或回滾含鳞。如果沒有配置 Spring
事務(wù)管理,那么將會調(diào)用 SqlSession
的 commit
方法對事務(wù)進行提交芹务。
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 未被事務(wù)管理器管理蝉绷,設(shè)置提交
sqlSession.commit(true);
}
而 SqlSessionTemplate
是不允許用來顯式地提交或回滾的,其提交或回滾的方法實現(xiàn)為直接拋出 UnsupportedOperationException
異常锄禽。
關(guān)閉 SqlSession
在當前調(diào)用結(jié)束后 SqlSessionTemplate
會調(diào)動 closeSqlSession
方法來關(guān)閉 SqlSession
潜必,如果事務(wù)同步管理器中存在當前線程綁定的 SqlSessionHolder
,即當前調(diào)用被事務(wù)管理器管理沃但,則將 SqlSession
的持有釋放掉磁滚。如果沒被事務(wù)管理器管理,則會真實地關(guān)閉 SqlSession
。
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// 被事務(wù)管理器管理垂攘,釋放 SqlSession
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
// 真實地關(guān)閉 SqlSesion
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
映射器
單個映射器
在 MyBatis
的基礎(chǔ)用法中维雇,MyBatis
配置文件支持使用 mappers
標簽的子元素 mapper
或 package
來指定需要掃描的 mapper
接口。被掃描到的接口類將被注冊到 MapperRegistry
中晒他,通過 MapperRegistry#getMapper
方法可以獲得 Mapper
接口的代理類吱型。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 生成 mapper 接口的代理類
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
通過代理類的方式可以使得 statement
的 id
直接與接口方法的全限定名關(guān)聯(lián),消除了 mapper
接口實現(xiàn)類的樣板代碼陨仅。但是此種方式在每次獲取 mapper
代理類的時候都需要指定 sqlSession
對象津滞,而 mybatis-spring
中的 sqlSession
對象是 SqlSessionTemplate
代理創(chuàng)建的,為了適配代理邏輯灼伤,mybatis-spring
提供了 MapperFactoryBean
來創(chuàng)建代理類触徐。
@Bean
public UserMapper userMapper(SqlSessionFactory sqlSessionFactory) throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean.getObject();
}
MapperFactoryBean
繼承了 SqlSessionDaoSupport
,其會根據(jù)傳入的 SqlSessionFactory
來創(chuàng)建 SqlSessionTemplate
狐赡,并使用 SqlSessionTemplate
來生成代理類撞鹉。
@Override
public T getObject() throws Exception {
// 使用 SqlSessionTemplate 來創(chuàng)建代理類
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
批量映射器
每次手動獲取單個映射器的效率是低下的,MyBatis
還提供了 MapperScan
注解用于批量掃描 mapper
接口并通過 MapperFactoryBean
創(chuàng)建代理類颖侄,注冊為 Spring bean
鸟雏。
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
// ...
}
MapperScan
注解解析后注冊 Spring bean
的邏輯是由 MapperScannerConfigurer
實現(xiàn)的,其實現(xiàn) 了 BeanDefinitionRegistryPostProcessor
接口的 postProcessBeanDefinitionRegistry
方法览祖。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// ...
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
掃描邏輯由 ClassPathMapperScanner
提供孝鹊,其繼承了 ClassPathBeanDefinitionScanner
掃描指定包下的類并注冊為 BeanDefinitionHolder
的能力。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 掃描指定包并注冊 bean definion
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 掃描指定包已經(jīng)獲取的 bean 定義
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 {
// 增強 bean 配置
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// ...
// 設(shè)置 bean class 類型為 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// ...
}
}
獲取到指定 bean
的定義后展蒂,重新設(shè)置 beanClass
為 MapperFactoryBean
惶室,因此在隨后的 bean
初始化中,這些被掃描的 mapper
接口可以創(chuàng)建代理類并被注冊到 Spring
容器中玄货。
映射器注冊完成后皇钞,就可以使用引用 Spring bean
的配置來使用 mapper
接口。
小結(jié)
mybatis-spring
提供了與 Spring
集成的更高層次的封裝松捉。
-
SqlSessionFactoryBean
遵循Spring FactoryBean
的定義夹界,使得SqlSessionFactory
注冊在Spring
容器中。 -
SqlSessionTemplate
是SqlSession
另一種線程安全版本的實現(xiàn)隘世,并且能夠更好地與Spring
事務(wù)管理集成可柿。 -
MapperFactoryBean
是生成mapper
接口代理類的SqlSessionTemplate
版本實現(xiàn)。 -
MapperScannerConfigurer
簡化了生成mapper
接口代理的邏輯丙者,指定掃描的包即可將生成mapper
接口代理類并注冊為Spring bean
复斥。