@Transactional介紹
@Transactional是spring中聲明式事務管理的注解配置方式,相信這個注解的作用大家都很清楚。@Transactional注解可以幫助我們把事務開啟、提交或者回滾的操作,通過aop的方式進行管理。
通過@Transactional注解就能讓spring為我們管理事務,免去了重復的事務管理邏輯,減少對業務代碼的侵入,使我們開發人員能夠專注於業務層面開發。
我們知道實現@Transactional原理是基於spring aop,aop又是動態代理模式的實現,通過對源碼的閱讀,總結出下面的步驟來了解實際中,在spring 是如何利用aop來實現@Transactional的功能的。
@Transactional作用
首先是@Transactional,作用是定義代理植入點。我們知道代理對象創建的通過BeanPostProcessor的實現類AnnotationAwareAspectJAutoProxyCreator的postProcessAfterInstantiation方法來實現個,如果需要進行代理,那么在這個方法就會返回一個代理對象給容器,同時判斷植入點也是在這個方法中。
那么下面開始分析,在配置好注解驅動方式的事務管理之后,spring會在ioc容器創建一個BeanFactoryTransactionAttributeSourceAdvisor實例,這個實例可以看作是一個切點,在判斷一個bean在初始化過程中是否需要創建代理對象,都需要驗證一次BeanFactoryTransactionAttributeSourceAdvisor是否是適用這個bean的切點。如果是,就需要創建代理對象,並且把BeanFactoryTransactionAttributeSourceAdvisor實例注入到代理對象中。
前文我們知道在AopUtils#findAdvisorsThatCanApply中判斷切面是否適用當前bean,可以在這個地方斷點分析調用堆棧,AopUtils#findAdvisorsThatCanApply一致調用,最終通過以下代碼判斷是否適用切點。
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute(Method method, Class<?> targetClass) 這里可以根據參數打上條件斷點進行調試分析調用棧,targetClass就是目標class …一系列調用
最終SpringTransactionAnnotationParser#parseTransactionAnnotation(java.lang.reflect.AnnotatedElement)
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
//這里就是分析Method是否被@Transactional注解標注,有的話,不用說BeanFactoryTransactionAttributeSourceAdvisor適配當前bean,進行代理,並且注入切點
//BeanFactoryTransactionAttributeSourceAdvisor
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
上面就是判斷是否需要根據@Transactional進行代理對象創建的判斷過程。@Transactional的作用一個就是標識方法需要被代理,一個就是攜帶事務管理需要的一些屬性信息。
動態代理邏輯實現
【aop實現原理分析】中知道,aop最終的代理對象的代理方法是DynamicAdvisedInterceptor#intercept,所以我們可以在這個方法斷點分析代理邏輯。
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we
// "own" the target, in case it comes from a pool...
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
//follow
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
通過分析 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)返回的是TransactionInterceptor,利用TransactionInterceptor是如何實現代理邏輯調用的?
跟蹤new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
發現最終是調用TransactionInterceptor#invoke方法,並且把CglibMethodInvocation注入到invoke方法中,從上面可以看到CglibMethodInvocation是包裝了目標對象的方法調用的所有必須信息,因此,在TransactionInterceptor#invoke里面也是可以調用目標方法的,並且還可以實現類似@Around的邏輯,在目標方法調用前后繼續注入一些其他邏輯,比如事務管理邏輯。
TransactionInterceptor–最終事務管理者
下面看代碼
TransactionInterceptor#invoke
@Override
public Object invoke(final 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, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
繼續跟蹤invokeWithinTransaction,下面的代碼中其實就可以看出一些邏輯端倪,就是我們猜想的實現方式,事務管理。
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//開啟事務
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
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);
}
//提交事務
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
// Check result: It might indicate a Throwable to rethrow.
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
分析源碼后
介紹一下Spring事務的傳播行為:
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
- TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
失效:
-
未被spring管理的類。
-
final修復方法,會失效。
@Service
public class OrderServiceImpl {
@Transactional
public final void cancel(OrderDTO orderDTO) {
// 取消訂單
cancelOrder(orderDTO);
}
}
OrderServiceImpl的cancel取消訂單方法被final修飾符修飾,Spring事務底層使用了AOP,也就是通過JDK動態代理或者cglib,幫我們生成了代理類,在代理類中實現的事務功能。但如果某個方法用final修飾了,那么在它的代理類中,就無法重寫該方法,從而無法添加事務功能。這種情況事務就會在Spring中失效。
- try catch內部捕獲異常,沒有正常拋出異常情況失效。
- 在@Transactional注解的方法中,再調用本類中的其他方法method2時,那么method2方法上的@Transactional注解是不!會!生!效!的!但是加上也並不會報錯,拿圖片簡單幫助理解一下吧。這一點也是面試中會問到的事務失效的場景。
- 方法必須是public修飾符。
- 同一個類中的方法相互調用。
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
public ResponseEntity submitOrder(Order order) {
// 保存生成訂單信息
long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_" + orderNo);
orderMapper.insert(order);
// 扣減庫存
this.updateProductStockById(order.getProductId(), 1L);
return new ResponseEntity(HttpStatus.OK);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductStockById(Integer num, Long productId) {
productMapper.updateProductStockById(num, productId);
}
}
- 方法的事務傳播類型不支持事務。
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ResponseEntity submitOrder(Order order) {
long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_" + orderNo);
orderMapper.insert(order);
// 扣減庫存
this.updateProductStockById(order.getProductId(), 1L);
return new ResponseEntity(HttpStatus.OK);
}
/**
* 扣減庫存方法事務類型聲明為NOT_SUPPORTED不支持事務的傳播
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateProductStockById(Integer num, Long productId) {
productMapper.updateProductStockById(num, productId);
}
}
- 數據庫不支持事務。
- 未配置開啟事務。
- 多線程調用。
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Autowired
private MessageService messageService;
@Transactional
public void orderCommit(orderModel orderModel) throws Exception {
orderMapper.insertOrder(orderModel);
new Thread(() -> {
messageService.sendSms();
}).start();
}
}
@Service
public class MessageService {
@Transactional
public void sendSms() {
// 發送短信
}
}