Spring程式設計式事務管理
在基於資料庫的應用中,事務是非常重要的。為了方便使用,Spring提供了基於XML和基於註解的方式配置事務,思路都是使用AOP,在特定的切入點統一開啟事務,以方法為粒度進行事務控制。並且定義了事務的傳播屬性,規定了配置了事務的方法互相巢狀呼叫時的行為準則:
- PROPAGATION_REQUIRED:支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
- PROPAGATION_SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行。
- PROPAGATION_MANDATORY:支援當前事務,如果當前沒有事務,就丟擲異常。
- PROPAGATION_REQUIRES_NEW:新建事務,如果當前存在事務,把當前事務掛起。
- PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
- PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
- PROPAGATION_NESTED:支援當前事務,新增Savepoint點,與當前事務同步提交或回滾。
越是統一的東西,靈活性就越差。事務是業務邏輯的一部分,有時候事務的開啟並不能以方法為粒度進行統一控制,這時候很多開發人員的做法是"將就"基於AOP的事務配置的方法,將需要開啟事務的邏輯單獨拆出方法進行控制,這其實是一種妥協,而且有時候並不見得稱心如意。那麼Spring除了基於AOP,還有別的方式管理事務嗎?答案就是org.springframework.transaction.support.TransactionTemplate。下面我們研究下它的使用。首先看下類圖:
TransactionTemplate繼承了DefaultTransactionDefinition,它是TransactionDefinition的一個實現,TransactionDefinition是Spring事務管理中很重要的一個概念,它是事務配置的入口,可以配置事務的各種屬性,如隔離級別、傳播屬性、超時時間、是否只讀,通過實現這個介面,TransactionTemplate具備了配置事務的能力。另外必須為它指定transactionManager,畢竟它只是負責觸發事務的開啟,並不具備事務管理的能力:
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan"> <list> <!-- 可以加多個包 --> <value>com.cuilei01.mgr.utils</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> </props> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>${jdbc.mgr.url}</value> </property> <property name="username"> <value>${jdbc.mgr.user}</value> </property> <property name="password"> <value>${jdbc.mgr.password}</value> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置transactionTemplate --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> <!--定義事務隔離級別,-1表示使用資料庫預設級別--> <property name="readOnly" value="false"></property> <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property> </bean>
現在就可以在Spring管理的Bean中注入並使用:
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TransactionTest {
private final Logger logger = LoggerFactory.getLogger(TransactionTest.class);
@Resource
private TransactionTemplate transactionTemplate;
@Test
public void testProgrammaticTransaction() {
logger.info("Begin test programmatic transaction!########################");
// 第一個事務
Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus status) {
logger.info("Do in transaction with a return value!#####################################");
// 在事務中執行, 有返回值
return 1;
}
});
logger.info("result:{}", result);
// 第二個事務
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
logger.info("Do in transaction without a return value!#####################################");
// 在事務中執行,沒有返回值
}
});
}
看到TransactionTemplate的使用比較簡單,只需將需要在事務中之行的邏輯封裝成TransactionCallback<T>,這個是帶返回值的,不帶返回值的封裝成TransactionCallbackWithoutResult。
觀察下事務的執行情況,事務work了。
看下它的核心程式碼:
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
需要執行事務的業務邏輯被封裝成action,它所做的也很簡單,在action執行前後進行事務的開啟和提交(或著rollback)。開啟事務時需要transactionManager的getTransaction方法獲取TransactionStatus,而這個方法的引數是TransactionDefinition,前面說過TransactionTemplate本身就是TransactionDefinition的實現,所以將this作為引數傳遞給這個方法就可以。
也可以看到,TransactionTemplate和基於AOP的配置一樣,也是在方法前後執行事務的開啟和提交,只是實現的方式改變了。