@Transactional 同一个类中无事务方法a()内部调用有事务方法b()的问题
Methods should not call same-class methods with incompatible "@Transactional" values
When using Spring proxies, calling a method in the same class (e.g. this.aMethod()) with an incompatible @Transactional requirement will result in runtime exceptions because Spring only "sees" the caller and makes no provisions for properly invoking the callee.
Therefore, certain calls should never be made within the same class:
| From | To |
|---|---|
non-@Transactional | MANDATORY, NESTED, REQUIRED, REQUIRES_NEW |
| MANDATORY | NESTED, NEVER, NOT_SUPPORTED, REQUIRES_NEW |
| NESTED | NESTED, NEVER, NOT_SUPPORTED, REQUIRES_NEW |
| NEVER | MANDATORY, NESTED, REQUIRED, REQUIRES_NEW |
| NOT_SUPPORTED | MANDATORY, NESTED, REQUIRED, REQUIRES_NEW |
REQUIRED or @Transactional | NESTED, NEVER, NOT_SUPPORTED, REQUIRES_NEW |
| REQUIRES_NEW | NESTED, NEVER, NOT_SUPPORTED, REQUIRES_NEW |
| SUPPORTS | MANDATORY, NESTED, NEVER, NOT_SUPPORTED, REQUIRED, REQUIRES_NEW |
Noncompliant Code Example
@Override
public void doTheThing() {
// ...
actuallyDoTheThing(); // Noncompliant
}
@Override
@Transactional
public void actuallyDoTheThing() {
// ...
}
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only↵ at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)↵ at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518)↵ at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)↵ at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)↵ at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)↵ at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:97)↵ at com.chanjet.paas.common.base.interceptor.ExceptionInterceptor.around(ExceptionInterceptor.java:63)↵ at sun.reflect.GeneratedMethodAccessor85.invoke(Unknown Source)↵ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)↵ at java.lang.reflect.Method.invoke(Method.java:498)↵ at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)↵ at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)↵ at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)↵ at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)↵ at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)↵ at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)↵ at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)↵
Spring事务分类
Spring 提供了两种事务管理方式:声明式事务管理和编程式事务管理。
图解SPRING事务
声明式事务
配置方式
注:以下配置代码参考自Spring事务配置的五种方式
根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:
1、每个Bean有一个代理
2、所有Bean共享一个代理基类
3、使用拦截器
4、使用tx标签配置拦截器
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
<aop:config>
<aop:advisor pointcut="execution(* com.*.*.event.local.service.*.*(..))" advice-ref="txAdviceLocalEvent"/>
</aop:config>
<tx:advice id="txAdviceLocalEvent">
<tx:attributes>
<tx:method name="update*"/>
<tx:method name="add*"/>
<tx:method name="del*"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
5、全注解方式
一个声明式事务的实例
注:该实例参考自Spring中的事务管理实例详解
首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
然后是XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="applicationContext-db.xml" />
<context:component-scan
base-package="com.springinaction.transaction">
</context:component-scan>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
使用的类
BookShopDao
package com.springinaction.transaction;
public interface BookShopDao {
// 根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
// 更新书的库存,使书号对应的库存-1
public void updateBookStock(String isbn);
// 更新用户的账户余额:account的balance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl
package com.springinaction.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate JdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@Override
public void updateBookStock(String isbn) {
//检查书的库存是否足够,若不够,则抛出异常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (stock == 0) {
throw new BookStockException("库存不足!");
}
String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
JdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//检查余额是否不足,若不足,则抛出异常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
if (balance < price) {
throw new UserAccountException("余额不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
JdbcTemplate.update(sql, price, username);
}
}
BookShopService
package com.springinaction.transaction;
public interface BookShopService {
public void purchase(String username, String isbn);
}
BookShopServiceImpl
package com.springinaction.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
/**
* 1.添加事务注解
* 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
*
* 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
* 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
* 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true
* 5.使用timeOut 指定强制回滚之前事务可以占用的时间。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
Cashier
package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
public void checkout(String username, List<String>isbns);
}
CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为
package com.springinaction.transaction;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
BookStockException
package com.springinaction.transaction;
public class BookStockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BookStockException() {
super();
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public BookStockException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
UserAccountException
package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UserAccountException() {
super();
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public UserAccountException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
测试类
package com.springinaction.transaction;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTransitionTest {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
{
ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
cashier = ctx.getBean(Cashier.class);
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@Test
public void testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001", "1002"));
}
}
博客探讨了在Spring中,使用@Transactional时同一个类内不兼容事务方法调用导致的UnexpectedRollbackException,并介绍了Spring的声明式事务管理,包括配置方式和一个实际应用示例。


1350

被折叠的 条评论
为什么被折叠?



