1. 事務(wù)的使用
Spring
中的事務(wù)有以下幾種使用方式
- 編程式事務(wù)敬尺;
- 使用XML配置聲明式事務(wù)害幅;
- 使用注解配置聲明式事務(wù)。
在實際應(yīng)用中榴徐,很少通過編程來進(jìn)行事務(wù)管理,一般多使用聲明式事務(wù)匀归,而隨著注解在Spring
中流行開來坑资,所以使用注解配置聲明式事務(wù)會較多。
1.1. 編程式事務(wù)
使用TransactionTemplate
進(jìn)行編程式事務(wù)的開發(fā)穆端,實際開發(fā)中很少使用了袱贮。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 編程式事務(wù)
*/
public void insertUser(boolean hasException){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
});
}
}
1.2. 使用XML配置聲明式事務(wù)
使用XML
進(jìn)行聲明式事務(wù)的配置又可以分為兩種:
- 使用原始的
TransactionProxyFactoryBean
; - 基于
aop/tx
命名空間的配置体啰。
其中TransactionProxyFactoryBean
還是Spring 1.0
時代的遠(yuǎn)古產(chǎn)物字柠,它需要給每個需要使用事務(wù)的類進(jìn)行單獨的配置,比較繁瑣狡赐。我們只需知道有這么個事就可以窑业。
下面看看基于aop/tx
命名空間的配置
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<import resource="classpath:applicationContext-dao.xml"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<aop:config>
<aop:pointcut id="serviceMethod"
expression="execution(* io.zgc.spring.features.tx.usetype.xml.UserService.*(..))"/>
<aop:advisor pointcut-ref="serviceMethod"
advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="insert*" rollback-for="Exception"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
</beans>
可以看到事務(wù)的配置是基于AOP
的配置。
1.3. 使用注解配置聲明式事務(wù)
我們可以在需要開啟事務(wù)的地方使用注解@Transactional
枕屉,該注解可以使用在類或方法上常柄。有兩種方式可以使@Transactional
注解生效
- 使用
xml
配置文件的應(yīng)用中,需要在配置文件中添加下列配置:
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
- 使用配置類的應(yīng)用中搀擂,使用
@EnableTransactionManagement
開啟事務(wù)管理
@EnableTransactionManagement
@ComponentScan("io.zgc.spring.features.tx")
@Configuration
public class TxConfig {
2. 事務(wù)的傳播行為
當(dāng)事務(wù)方法被另外一個事務(wù)方法調(diào)用時西潘,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可以繼續(xù)在現(xiàn)有事務(wù)中運行哨颂,也可以開啟一個新事務(wù)喷市。
事務(wù)的傳播行為可以通過事務(wù)的傳播屬性來指定,Spring
中定義了7種傳播行為威恼。
-
REQUIRED
:如果有事務(wù)在運行品姓,當(dāng)前的方法就使用這個事務(wù)寝并,在這個事務(wù)中運行。否則腹备,就開啟一個新的事務(wù)衬潦,并在新的事務(wù)中運行。 -
REQUIRES_NEW
:當(dāng)前的方法必須開啟新的事務(wù)植酥,并在新的事務(wù)中運行镀岛,如果調(diào)用的方法有事務(wù)在運行,就將它先掛起友驮。 -
SUPPORTS
:如果有事務(wù)在運行漂羊,當(dāng)前的方法就在這個事務(wù)內(nèi)運行,否則它可以不運行在事務(wù)中卸留。 -
NOT_SUPPORTS
:當(dāng)前的方法不應(yīng)該運行在事務(wù)中拨与,如果當(dāng)前有運行的事務(wù),就將它掛起艾猜。 -
MANDATORY
:當(dāng)前方法運行在調(diào)用方法的事務(wù)中,如果不存在運行中的事務(wù)就拋出異常捻悯。 -
NEVER
:當(dāng)前的方法不應(yīng)該運行在事務(wù)中匆赃,如果有運行的事務(wù),就拋出異常今缚。 -
NESTED
:如果有事務(wù)運行算柳,當(dāng)前的方法就應(yīng)該在這個事務(wù)的嵌套事務(wù)內(nèi)運行,否則就啟動一個新的事務(wù)姓言,并在自己的事務(wù)內(nèi)運行瞬项。
常用的傳播行為:REQUIRED
、REQUIRES_NEW
何荚,其中Spring
默認(rèn)的傳播行為為:REQUIRED
囱淋。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private UserService self;
@Transactional
public void insertUser(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
/**
* 一個用戶都不會插入,事務(wù)的傳播行為是REQUIRED
* 兩個self.insertUser方法共用insetBatchUserWithRequred方法開啟的事務(wù)
*
* 事務(wù)最終由于self.insertUser(true);執(zhí)行拋出異常餐塘,而回滾
*/
@Transactional
public void insetBatchUserWithRequred() {
self.insertUser(false);
self.insertUser(true);
}
/**
* 會插入1個用戶妥衣,因為事務(wù)的傳播行為為REQUIRES_NEW
* self.insertUserRn(false); 開啟單獨的事務(wù),并成功執(zhí)行
* self.insertUserRn(true); 開啟單獨的事務(wù)戒傻,執(zhí)行失敗
*
* insetBatchUserWithRequredNew的事務(wù)不會受self.insertUserRn(true)開啟的事務(wù)影響
*/
@Transactional
public void insetBatchUserWithRequredNew() {
self.insertUserRn(false);
self.insertUserRn(true);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertUserRn(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
}
使用事務(wù)的一些坑
日常開發(fā)過程中税手,遇到聲明式事務(wù)(@Transactional
)和其他形式的事務(wù)時一定要親自驗證一波,確保事務(wù)是可以生效的需纳。下面來看看關(guān)于事務(wù)常見的一些坑
private的方法中使用聲明式事務(wù)
首先需要知道的是:在private方法中使用聲明式事務(wù)是不會生效的
芦倒。除非特殊配置(比如使用 AspectJ
靜態(tài)織入實現(xiàn) AOP
),否則只有定義在 public
方法上的 @Transactional
才能生效不翩。原因是兵扬, Spring
默認(rèn)通過動態(tài)代理的方式實現(xiàn) AOP
麻裳,對目標(biāo)方法進(jìn)行增強(qiáng),private
方法無法代理到周霉,Spring
自然也無法動態(tài)增強(qiáng)事務(wù)處理邏輯掂器。
/**
* @Transactional 不能放在private方法上,不然事務(wù)不生效
* @param hasException
*/
@Transactional
private void insertUserPrivate(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
上面的insertUserPrivate
方法俱箱,即使拋出異常事務(wù)也不會回滾国瓮,這是因為它是個private
方法,而@Transactional
注解不能修飾private
方法狞谱。
通過this調(diào)用事務(wù)方法
要想@Transactional
生效乃摹,必須是通過代理的類調(diào)用目標(biāo)方法,否則事務(wù)將不會生效跟衅。請看如下事務(wù)失效代碼:
/**
* 自調(diào)用(this調(diào)用)事務(wù)方法孵睬,事務(wù)不會生效
* @param hasException
*/
public void insetUserWrong2(boolean hasException) {
insertUserPublic(hasException);
}
@Transactional
public void insertUserPublic(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
事務(wù)在拋出異常時依然不會回滾,這是因為是通過this
調(diào)用了事務(wù)方法伶跷,而this
指向的是目標(biāo)對象掰读,非代理對象,沒有進(jìn)行事務(wù)增加叭莫,所以事務(wù)不會起作用蹈集。
這種情況下,要想事務(wù)能夠生效的話雇初,可以將代理對象注入其中拢肆,并通過代理對象來調(diào)用事務(wù)方法。
@Service
public class UserService {
@Autowired
private UserDao userDao;
/** 要想事務(wù)生效靖诗,必須通過代理類來調(diào)用事務(wù)方法 */
@Autowired
private UserService self;
public void insertUserRight(boolean hasException) {
self.insertUserPublic(hasException);
}
@Transactional
public void insertUserPublic(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
}
異常處理不當(dāng)導(dǎo)致事務(wù)失效
要想事務(wù)能夠生效(即在拋出異常時郭怪,事務(wù)能夠回滾),默認(rèn)情況下需要滿足兩個條件:
- 事務(wù)方法在執(zhí)行失敗時刊橘,需要將異常拋出去鄙才;
- 拋出的異常需要是非受檢異常(
RuntimeException
)。
當(dāng)然上面兩個條件是默認(rèn)情況下的讓事務(wù)生效的條件促绵,不是說必須得是這樣咒循,是可以被打破的,下面舉例來說明下
@Transactional
public void insertUserWrong3(boolean hasException){
try {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
} catch (Exception e) {
System.out.println("------------------");
/* 異常被吃掉了绞愚,事務(wù)無法回滾 */
e.printStackTrace();
}
}
上面代碼的事務(wù)之所以沒法回滾是因為方法中對異常進(jìn)行了捕獲并沒有繼續(xù)拋出新的異常叙甸,所以效果和正常運行了代碼的效果是一樣的,不會將事務(wù)回滾位衩。針對這種情況裆蒸,如何讓事務(wù)能夠生效呢?除了繼續(xù)拋出新的異常之外糖驴,還可以在catch
住異常后僚祷,手動回滾事務(wù)(TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
)佛致,代碼如下:
@Transactional
public void insertUserRight3(boolean hasException){
try {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
} catch (Exception e) {
e.printStackTrace();
// 手動設(shè)置讓當(dāng)前事務(wù)處于回滾狀態(tài)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
另外一個例子就是拋出了受檢異常了,例如FileNotFoundException
辙谜,默認(rèn)情況下俺榆,事務(wù)也是不生效的,除非設(shè)置rollbackFor
為Exception
装哆。
@Transactional(rollbackFor = Exception.class)
public void insertUserRight4() throws FileNotFoundException {
userDao.insert();
System.out.println("...");
throw new FileNotFoundException();
}
4. 事務(wù)源碼分析
4.1. 說明
本次源碼分析使用Spring
的聲明式事務(wù)罐脊,版本基于Spring 5.1.5.RELEASE
4.2. 示例程序
TxConfig.java
@EnableTransactionManagement
@ComponentScan("io.zgc.spring.features.tx")
@Configuration
public class TxConfig {
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean
public PlatformTransactionManager transactionManager () throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}
}
Client.java
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();
}
}
UserService.java
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void insertUser(){
userDao.insert();
System.out.println("...");
int i = 10/0;
}
}
4.3. @EnableTransactionManagement
我們先來看看開啟聲明式事務(wù)的注解@EnableTransactionManagement
,它的源碼如下:
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
TransactionManagementConfigurationSelector
是個ImportSelector
,默認(rèn)情況下蜕琴,它會給容器導(dǎo)入兩個組件AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
萍桌。
4.4. AutoProxyRegistrar
AutoProxyRegistrar
是一個ImportBeanDefinitionRegistrar
,它會檢測導(dǎo)入者類上的某個注解是否帶有屬性mode
和proxyTargetClass
凌简,如果檢測到這些屬性上炎,在mode
為PROXY
時,它會給容器中注冊一個InfrastructureAdvisorAutoProxyCreator
組件(APC
雏搂、auto proxy creator
)藕施。
InfrastructureAdvisorAutoProxyCreator
是個APC,它的繼承體系如下圖所示:
從圖中我們可以得出的結(jié)論:
-
InfrastructureAdvisorAutoProxyCreator
是個Bean后置處理器凸郑。 - 它和
AOP
源碼中的AnnotationAwareAspectJAutoProxyCreator
類類似裳食,都繼承自AbstractAutoProxyCreator
,所以原理類似线椰。
InfrastructureAdvisorAutoProxyCreator
會利用后置處理器機(jī)制在對象創(chuàng)建以后,包裝對象尘盼,返回一個代理對象(增強(qiáng)器)憨愉,代理對象執(zhí)行方法利用攔截器鏈進(jìn)行調(diào)用;
4.5. ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration
是個配置類卿捎,它會給容器中注冊事務(wù)增強(qiáng)器配紫,主要包括三個組件:BeanFactoryTransactionAttributeSourceAdvisor
、TransactionAttributeSource
午阵、TransactionInterceptor
躺孝。
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
1)、BeanFactoryTransactionAttributeSourceAdvisor
是事務(wù)的advisor
底桂。
2)植袍、事務(wù)advisor
要用事務(wù)注解的信息,通過AnnotationTransactionAttributeSource
解析事務(wù)注解籽懦。
3)于个、TransactionInterceptor
是個advice
,它實現(xiàn)了 MethodInterceptor
;保存了事務(wù)屬性信息暮顺,事務(wù)管理器厅篓;
4.6. TransactionInterceptor
TransactionInterceptor
繼承了TransactionAspectSupport
類秀存,實現(xiàn)MethodInterceptor
接口。源碼如下:
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) {
setTransactionManager(ptm);
setTransactionAttributeSource(tas);
}
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
攔截方法invoke
最終會請求TransactionAspectSupport
的 invokeWithinTransaction
方法羽氮,這個方法就是處理事務(wù)的邏輯
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
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);
}