上一篇我們分析了 Spring 的 Transaction 處理過程臭胜,這里我們一起看下拙友,MyBatis 是如何跟 Spring 整合剑勾,提供完整的事務處理方案床估。
想要把 MyBatis 跟 Spring 整合弯囊,都需要這樣一個 Jar 包:mybatis-spring-x.x.x.jar,這個 Jar 包可以說是 MyBatis 與 Spring 的通信橋梁胶果,將兩個不相關(guān)的框架可以整合到一起匾嘱,提供完整的 ORM 功能。
在 Spring 配置文件中需要配置如下兩個 Bean:
<!-- mybatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="configLocation" value="classpath:mybatis.xml"></property>
<!-- mybatis配置文件 -->
<property name="mapperLocations" value="classpath:com/blackbread/dao/mapper/*.xml" />
</bean>
<!--mapper scanning -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.blackbread.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
SqlSessionFactoryBean
首先讓我們來看 SqlSessionFactoryBean 類早抠,在這個類初始化的時候霎烙,需要注入 DataSource,而且是需要跟初始化 TransactionManager 時候注入的 DataSource 為同一個蕊连。
SqlSessionFactoryBean 類實現(xiàn)了 InitializingBean 接口悬垃,所以初始化后會執(zhí)行 afterPropertiesSet() 方法,在 afterPropertiesSet() 方法中會執(zhí)行 buildSqlSessionFactory() 方法生成一個 SqlSessionFactory 對象甘苍,讓我們看下 buildSqlSessionFactory() 方法尝蠕。
SqlSessionFactoryBean#buildSqlSessionFactory()
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
// 初始化一個configuration
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
}
}
// 設(shè)置別名
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
}
}
// 裝入插件,mybatis的插件都是以攔截器的形式
// 比如比如分頁插件载庭,這里是載入 spring 中注入的
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
}
}
// 這里將解析mybatis.xml文件看彼,載入所有配置,插件昧捷、setting等
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 這個很重要闲昭,這里定義了用的transactionFactory為SpringManagedTransactionFactory
// 這個在獲取 connection 等地方都有用到罐寨,是mybatis跟spring的主要鏈接類
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 新建一個Environment對象靡挥,并將新建的transactionFactory放入其中
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 這里主要是解析配置的sql mapper配置文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
} else {
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
比較長的一段代碼,其實這里做的工作就是解析配置文件生成 Configuration 對象而已鸯绿。在 XmlMapperBuilder#parse() 方法中跋破,這里將解析 Sql Mapper 文件中的映射關(guān)系生成 MappedStatement 對象,并執(zhí)行 Configuration#addMappedStatement() 方法瓶蝴,將其放入到 Configuration 對象中毒返,有興趣的同學可以仔細看下。
這里最需要注意的一塊就是 this.transactionFactory = new SpringManagedTransactionFactory()舷手。SpringManagedTransactionFactory 就是 MyBatis 跟 Spring 的鏈接拧簸。
MapperScannerConfigurer
接著我們看一下 MapperScannerConfigurer 對象的初始化過程,這個對象實現(xiàn)了BeanDefinitionRegistryPostProcessor 接口男窟,所以看 postProcessBeanDefinitionRegistry() 方法盆赤,在這個方法中初始化一個對象 ClassPathMapperScanner,并執(zhí)行 scan() --> doScan() 方法歉眷。
ClassPathMapperScanner#doScan()
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
// 實際就是將掃描到的接口包裝成MapperFactoryBean的實現(xiàn)類
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 注入sqlSessionFactory對象牺六,這個也很重要
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) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
return beanDefinitions;
}
這段代碼其實主要就是根據(jù) basePackage 屬性的配置,掃描相應的接口類汗捡,并且注冊到 Spring 中淑际,并且定義此對象的 FactoryBean 為:MapperFactoryBean ,而 MapperFactoryBean 將返回如下對象。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
最終其實就是生成 Handler 為 MapperProxy春缕,接口為 mapperInterface 屬性指定(業(yè)務的 Mapper 接口)的代理類盗胀。同時添加屬性:sqlSessionFactory,這個操作很重要锄贼,在后面有核心應用读整。
MapperFactoryBean
這里讓我們看下 MapperFactoryBean 類,這個類繼承自 SqlSessionDaoSupport 在 SqlSessionDaoSupport 中有如下方法:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
也就是上面調(diào)用的添加 sqlSessionFactory 屬性的 set 操作咱娶,在這個方法中初始化了 SqlSession 對象米间,用的是 SqlSessionTemplate 實現(xiàn)類。接下來讓我們看下 SqlSessionTemplate 的初始化過程:
SqlSessionTemplate
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());
}
SqlSessionTemplate 其實實現(xiàn)了 SqlSession 接口的膘侮,在初始化的時候?qū)⑸梢粋€接口為 SqlSession屈糊,名為 sqlSessionProxy 代理對象,可以看到 SqlSessionTemplate 里面的所有與數(shù)據(jù)庫相關(guān)的操作都是通過sqlSessionProxy 這個代理對象實現(xiàn)的琼了。
接著看下 sqlSessionProxy 代理對象的實際 handler:
SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
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 {
// 實際 SQL 的執(zhí)行過程
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
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);
}
}
}
}
invoke() 方法中首先需要獲取一個 SqlSession 對象:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
holder.requested();
return holder.getSqlSession();
}
SqlSession session = sessionFactory.openSession(executorType);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
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) {
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
}
return session;
}
這里將首先去判斷下 SqlSessionHolder 是否已經(jīng)存在逻锐,如果不存在將會初始化一個新的,我們這里只分析第一次調(diào)用過程雕薪,也就是將會執(zhí)行到 SessionFactory#openSession() 方法昧诱,這個方法里面接著會調(diào)用 openSessionFromDataSource() 方法。
SessionFactory#openSession()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
這里與 Spring 事務管理關(guān)聯(lián)起來的核心代碼所袁,讓我們仔細分析下盏档,首先這里將通過 getTransactionFactoryFromEnvironment() 方法獲取 TransactionFactory。這個操作將得到我們之前初始化時候注入的 SpringManagedTransactionFactory 對象燥爷。然后將執(zhí)行 TransactionFactory#newTransaction() 方法蜈亩,初始化 MyBatis 的 Transaction。
再下面將通過 Configuration.newExecutor() 生成一個 Executor前翎。由于在之前指定了 execType 為Simple稚配,所以在這里將生成一個 SimpleExecutor: executor = new SimpleExecutor(this, transaction),并將剛初始化的 Transaction 加入屬性港华。
實際語句執(zhí)行
到這里SqlSession的初始化也就完成了道川,接下來就是看下實際方法的執(zhí)行了,也就是 Object result = method.invoke(sqlSession, args)立宜。
以一個 update() 方法執(zhí)行來舉例:
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
首先將從 Configuration 中根據(jù)操作的 statement 獲取映射內(nèi)容 MappedStatement冒萄。getMappedStatement() 方法中其實就是從 Map 對象中根據(jù) key 取出之前緩存的數(shù)據(jù)。
Executor#update()
接著將執(zhí)行 Executor#update() 方法赘理,也就是實際的數(shù)據(jù)庫操作了宦言,記得之前初始化的 Executor 么,這里就是那個 SimpleExecutor商模。
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
clearLocalCache();
return doUpdate(ms, parameter);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
主要是看 prepareStatement() 方法奠旺,看到 Connection 的獲取了吧蜘澜?
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
然后接著看 getConnection() 方法:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog);
} else {
return connection;
}
}
到這里終于看到了真正的 Connection 獲取過程:Transaction#getConnection(),也就是通過之前注入的Transaction 來獲取 Connection响疚,而這個 Transaction 也就是 SpringManagedTransaction鄙信,他其實調(diào)用了 openConnection() 方法。
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
openConnection() 方法中主要通過調(diào)用 DataSourceUtils#getConnection() 方法來獲取一個Connection忿晕。繼續(xù)看 DataSourceUtils#getConnection() 方法装诡,實際調(diào)用的又是 doGetConnection() 方法。
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
//從TransactionSynchronizationManager中獲取ConnectionHolder
// 這個對象也就是之前我們上一次分析 Spring Transaction 的時候
// 持有ConnectionHolder的對象了
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 由于在前面的切面中已經(jīng)開啟事務
// 并且初始化了ConnectionHolder所以這里將直接返回ConnectionHolder中的connection
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
Connection con = dataSource.getConnection();
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 Transaction 中也是通過這個類中的 resources 屬性(ThreadLocal對象)對 ConnectionHolder 進行持有的咕幻。
在這里將獲取到 Spring 開啟事務時候持有的 ConnectionHolder 對象渔伯,自然獲取到的 Connection 對象也是 Srping 開啟事務時候創(chuàng)建的對象,這樣就保證了Spring Transaction 中控制的 Connection 跟在 MyBatis 中執(zhí)行 SQL 語句用的 Connection 為同一個 Connection肄程,也就可以通過之前 Spring 事務管理機制進行事務管理了锣吼。
后續(xù)的對數(shù)據(jù)的操作有興趣的可以自己讀一下,感覺 MyBatis 的源碼沒有 Spring 的那么清晰蓝厌,還是需要仔細分析下才能看的明白玄叠。
其他
注意事項
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
//從TransactionSynchronizationManager中獲取ConnectionHolder
// 這個對象也就是之前我們上一次分析 Spring Transaction 的時候
// 持有ConnectionHolder的對象了
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 由于在前面的切面中已經(jīng)開啟事務
// 并且初始化了ConnectionHolder所以這里將直接返回ConnectionHolder中的connection
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
Connection con = dataSource.getConnection();
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;
}
我們再看下 DataSourceUtils#doGetConnection() 方法,如果 conHolder 為空拓提,也就是沒有開啟事務情況下读恃,將執(zhí)行 Connection con = dataSource.getConnection(),也就是說沒有開啟事務情況下崎苗,每次的數(shù)據(jù)庫操作都將從連接池拿一個新的 Connection狐粱,而不是第一次獲取后就與線程綁定。
這樣做可能是從系統(tǒng)并行度方面考慮的胆数,因為連接是比較稀缺的資源,在不開啟事務情況下互墓,應遵循隨用隨拿必尼、用完即還的原則。如果連接跟線程綁定篡撵,當線程執(zhí)行完數(shù)據(jù)庫操作判莉,又執(zhí)行了其他耗時操作,那么與其綁定的連接將無法得到復用育谬,大大降低了系統(tǒng)的并行度(受連接池中連接數(shù)量限制)券盅。