淺談MyBatis 事務(wù)管理
1. 運(yùn)行環(huán)境 Enviroment
當(dāng) MyBatis 與不同的應(yīng)用結(jié)合時,需要不同的事務(wù)管理機(jī)制。與 Spring 結(jié)合時,由 Spring 來管理事務(wù);單獨(dú)使用時需要自行管理事務(wù),在容器里運(yùn)行時可能由容器進(jìn)行管理。
MyBatis 用 Enviroment 來表示運(yùn)行環(huán)境,其封裝了三個屬性:
public class Configuration {
// 一個 MyBatis 的配置只對應(yīng)一個環(huán)境
protected Environment environment;
// 其他屬性 .....
}
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
}
2. 事務(wù)抽象
MyBatis 把事務(wù)管理抽象出 Transaction 接口,由 TransactionFactory 接口的實(shí)現(xiàn)類負(fù)責(zé)創(chuàng)建。
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
public interface TransactionFactory {
void setProperties(Properties props);
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
Executor 的實(shí)現(xiàn)持有一個 SqlSession 實(shí)現(xiàn),事務(wù)控制是委托給 SqlSession 的方法來實(shí)現(xiàn)的。
public abstract class BaseExecutor implements Executor {
protected Transaction transaction;
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
// 省略其他方法、屬性
}
3. 與 Spring 集成的事務(wù)管理
3.1 配置 TransactionFactory
與 Spring 集成時,通過 SqlSessionFactoryBean 來初始化 MyBatis 。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else 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 (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
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);
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
}
}
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);
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 創(chuàng)建 SpringManagedTransactionFactory
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 封裝成 Environment
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
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);
}
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
重點(diǎn)是在構(gòu)建 MyBatis Configuration 對象時,把 transactionFactory 配置成 SpringManagedTransactionFactory,再封裝成 Environment 對象。
3.2 運(yùn)行時事務(wù)管理
Mapper 的代理對象持有的是 SqlSessionTemplate,其實(shí)現(xiàn)了 SqlSession 接口。
SqlSessionTemplate 的方法并不直接調(diào)用具體的 SqlSession 的方法,而是委托給一個動態(tài)代理,通過代理 SqlSessionInterceptor 對方法調(diào)用進(jìn)行攔截。
SqlSessionInterceptor 負(fù)責(zé)獲取真實(shí)的與數(shù)據(jù)庫關(guān)聯(lián)的 SqlSession 實(shí)現(xiàn),并在方法執(zhí)行完后決定提交或回滾事務(wù)、關(guān)閉會話。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
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;
// 因?yàn)?SqlSession 接口聲明的方法也不少,
// 在每個方法里添加事務(wù)相關(guān)的攔截比較麻煩,
// 不如創(chuàng)建一個內(nèi)部的代理對象進(jìn)行統(tǒng)一處理。
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
public int update(String statement) {
// 在代理對象上執(zhí)行方法調(diào)用
return this.sqlSessionProxy.update(statement);
}
// 對方法調(diào)用進(jìn)行攔截,加入事務(wù)控制邏輯
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 獲取與數(shù)據(jù)庫關(guān)聯(lián)的會話
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 執(zhí)行 SQL 操作
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 如果 sqlSession 不是 Spring 管理的,則要自行提交事務(wù)
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.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) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}
SqlSessionUtils 封裝了對 Spring 事務(wù)管理機(jī)制的訪問。
// SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 從 Spring 的事務(wù)管理機(jī)制那里獲取當(dāng)前事務(wù)關(guān)聯(lián)的會話
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
// 已經(jīng)有一個會話則復(fù)用
return session;
}
// 創(chuàng)建新的 會話
session = sessionFactory.openSession(executorType);
// 注冊到 Spring 的事務(wù)管理機(jī)制里
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
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) {
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// 重點(diǎn):注冊會話管理的回調(diào)鉤子,真正的關(guān)閉動作是在回調(diào)里完成的。
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
// 維護(hù)會話的引用計(jì)數(shù)
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 {
}
}
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
// 從線程本地變量里獲取 Spring 管理的會話
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// Spring 管理的不直接關(guān)閉,由回調(diào)鉤子來關(guān)閉
holder.released();
} else {
// 非 Spring 管理的直接關(guān)閉
session.close();
}
}
SqlSessionSynchronization 是 SqlSessionUtils 的內(nèi)部私有類,用于作為回調(diào)鉤子與 Spring 的事務(wù)管理機(jī)制協(xié)調(diào)工作,TransactionSynchronizationManager 在適當(dāng)?shù)臅r候回調(diào)其方法。
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
private final SqlSessionHolder holder;
private final SqlSessionFactory sessionFactory;
private boolean holderActive = true;
public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
this.holder = holder;
this.sessionFactory = sessionFactory;
}
public int getOrder() {
return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
}
public void suspend() {
if (this.holderActive) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
}
public void resume() {
if (this.holderActive) {
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
}
}
public void beforeCommit(boolean readOnly) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
try {
this.holder.getSqlSession().commit();
} catch (PersistenceException p) {
if (this.holder.getPersistenceExceptionTranslator() != null) {
DataAccessException translated = this.holder
.getPersistenceExceptionTranslator()
.translateExceptionIfPossible(p);
if (translated != null) {
throw translated;
}
}
throw p;
}
}
}
public void beforeCompletion() {
if (!this.holder.isOpen()) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
this.holderActive = false;
// 真正關(guān)閉數(shù)據(jù)庫會話
this.holder.getSqlSession().close();
}
}
public void afterCompletion(int status) {
if (this.holderActive) {
TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
this.holderActive = false;
// 真正關(guān)閉數(shù)據(jù)庫會話
this.holder.getSqlSession().close();
}
this.holder.reset();
}
}
3.3 創(chuàng)建新會話
// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 獲取事務(wù)工廠實(shí)現(xiàn)
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} 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();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
4. 小結(jié)
- MyBatis 的核心組件 Executor 通過 Transaction 接口來進(jìn)行事務(wù)控制。
- 與 Spring 集成時,初始化 Configuration 時會把 transactionFactory 設(shè)置為 SpringManagedTransactionFactory 的實(shí)例。
- 每個 Mapper 代理里注入的 SqlSession 是 SqlSessionTemplate 的實(shí)例,其實(shí)現(xiàn)了 SqlSession 接口;
- SqlSessionTemplate 把對 SqlSession 接口里聲明的方法調(diào)用委托給內(nèi)部的一個動態(tài)代理,該代理的方法處理器為內(nèi)部類 SqlSessionInterceptor 。
- SqlSessionInterceptor 接收到方法調(diào)用時,通過 SqlSessionUtil 訪問 Spring 的事務(wù)設(shè)施,如果有與 Spring 當(dāng)前事務(wù)關(guān)聯(lián)的 SqlSession 則復(fù)用;沒有則創(chuàng)建一個。
- SqlSessionInterceptor 根據(jù) Spring 當(dāng)前事務(wù)的狀態(tài)來決定是否提交或回滾事務(wù)。會話的真正關(guān)閉是通過注冊在 TransactionSynchronizationManager 上的回調(diào)鉤子實(shí)現(xiàn)的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MongoDB中ObjectId的誤區(qū)及引起的一系列問題
這篇文章主要介紹了MongoDB中ObjectId的誤區(qū)及引起的一系列問題,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12
Java內(nèi)存釋放實(shí)現(xiàn)代碼案例
這篇文章主要介紹了Java內(nèi)存釋放實(shí)現(xiàn)代碼案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12
Springboot打包為Docker鏡像并部署的實(shí)現(xiàn)
這篇文章主要介紹了Springboot打包為Docker鏡像并部署的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java基礎(chǔ)之MapReduce框架總結(jié)與擴(kuò)展知識點(diǎn)
本章,是MapReduce的最終章,我在寫本章的時候,發(fā)現(xiàn)前面忘記介紹MpaTask與ReduceTask了,所以本章補(bǔ)上哈,另外還有兩個擴(kuò)展的知識點(diǎn),講完這些,我會對整個MapReduce進(jìn)行總結(jié)一下,讓大家再次了解MapReduce的工作流程,更加清晰地認(rèn)識MapReduce ,需要的朋友可以參考下2021-05-05
SpringBoot actuator 健康檢查不通過的解決方案
這篇文章主要介紹了SpringBoot actuator 健康檢查不通過的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

