Spring@Transactional事务以及事务间调⽤完整解析,保你看完,反⼿就⼀个
赞!
事物注解⽅式: @Transactional
当标于类前时, 标⽰类中所有⽅法都进⾏事物处理 , 例⼦:
@Transactional
public class TestServiceBean implements TestService {}
当类中某些⽅法不需要事物时:
1 @Transactional
2 public class TestServiceBean implements TestService {
3 private TestDao dao;
4 public void setDao(TestDao dao) {
5 this.dao = dao;
6 }
7 @Transactional(propagation =Propagation.NOT_SUPPORTED)
8 public List getAll() {
9 return null;
10 }
11 }
事物传播⾏为介绍:
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个⽅法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在⼀个已有的事务中执⾏,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在⼀个没有的事务中执⾏,否则抛出异常(与
Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调⽤这个⽅法,在其他bean中声明事务,那就⽤事务.如果其他bean没有声明事务,那就不⽤事务.
事物超时设置:
@Transactional(timeout=30) //默认是30秒
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使⽤
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串⾏化
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
脏读 : ⼀个事务读取到另⼀事务未提交的更新数据
不可重复读 : 在同⼀事务中, 多次读取同⼀数据返回的结果有所不同, 换句话说,
后续读取可以读到另⼀事务已提交的更新数据. 相反, "可重复读"在同⼀事务中多次
读取数据时, 能够保证所读数据⼀样, 也就是后续读取不能读到另⼀事务已提交的更新数据
幻读 : ⼀个事务读到另⼀个事务已提交的insert数据
@Transactional注解中常⽤参数说明
注意的⼏点:
1、@Transactional 只能被应⽤到public⽅法上, 对于其它⾮public的⽅法,如果标记了@Transactional也不会报错,但⽅法没有事务功能.
2、⽤ spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运⾏期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;⽽遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是⾮运⾏时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定⽅式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚:
@Transactional(notRollbackFor=RunTimeException.class)
如下:
1 @Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
2 public void methodName() {
3 throw new Exception("注释");
4 }
5 @Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运⾏期例外(throw new RuntimeException("注释");)会回滚
6 public ItimDaoImpl getItemDaoImpl() {
7 throw new RuntimeException("注释");
8 }
3、@Transactional 注解应该只被应⽤到 public 可见度的⽅法上。 如果你在 protected、private 或者 package-visible 的⽅法上使⽤@Transactional 注解,它也不会报错, 但是这个被注解的⽅法将不会展⽰已配置的事务设置。
4、@Transactional 注解可以被应⽤于接⼝定义和接⼝⽅法、类定义和类的 public ⽅法上。然⽽,请注意仅仅 @Transactional 注解的出现不⾜于开启事务⾏为,它仅仅 是⼀种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务⾏为的beans所使⽤。上⾯的例⼦中,其实正是 元素的出现 开启 了事务⾏为。
5、Spring团队的建议是你在具体的类(或类的⽅法)上使⽤ @Transactional 注解,⽽不要使⽤在类所要实现的任何接⼝上。你当然可以在接⼝上使⽤ @Transactional 注解,但是这将只能当你设置了基于接⼝的代理时它才⽣效。因为注解是不能继承的,这就意味着如果你正在使⽤基于类的代理时,那么事务的设置将不能被基于类的代理所识别,⽽且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使⽤ @Transactional 注解。
注意:
spring 事务处理中,同⼀个类中:A⽅法(⽆事务)调B⽅法(有事务),事务不⽣效问题
问题: 同⼀个类的不同⽅法,A⽅法没有@Transactional,B⽅法有@Transactional,A调⽤B⽅法,事务不起作⽤
⽅法A:⽆事务控制
⽅法B:有事务控制
原因:在⼀个Service内部,事务⽅法之间的嵌套调⽤,普通⽅法和事务⽅法之间的嵌套调⽤,都不会开启新的事务.是因为spring采⽤动态代理机制来实现事务控制,⽽动态代理最终都是要调⽤原始对象的,⽽原始对象在去调⽤⽅法时,是不会再触发代理了!
下⾯是解决⽅案:
1.把⽅法B抽离到另外⼀个XXService中去,并且在这个Service中注⼊XXService,使⽤XXService调⽤⽅法B;
显然,这种⽅式⼀点也不优雅,且要产⽣很多冗余⽂件,看起来很烦,实际开发中也⼏乎没⼈这么做吧?.反正我不建议采⽤此⽅案;
2.通过在⽅法内部获得当前类代理对象的⽅式,通过代理对象调⽤⽅法B
上⾯说了:动态代理最终都是要调⽤原始对象的,⽽原始对象在去调⽤⽅法时,是不会再触发代理了!
所以我们就使⽤代理对象来调⽤,就会触发事务;
综上解决⽅案,我觉得第⼆种⽅式⽅便. 那怎么获取代理对象呢? 这⾥提供两种⽅式:
1.使⽤ ApplicationContext 上下⽂对象获取该对象;
2.使⽤ AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true
这⾥使⽤的是第⼆种解决⽅案,具体操作如下:
springboot启动类加上注解:@EnableAspectJAutoProxy(exposeProxy = true)
⽅法内部获取代理对象调⽤⽅法
完了后再测试,数据顺利回滚,⾄此,问题得到解决!
建议封装⼯具类:
import org.springframework.aop.framework.AopContext;
/**
* @author
* @date 2019-04-08
* @since v1.0.0
*/
public class AopUtils {
private AopUtils() {}
@SuppressWarnings("unchecked")
public static <T> T getAopProxyInstance(T t) {
try {
return (T)AopContext.currentProxy();
} catch (IllegalStateException e) {
return t;
}
}
}
在service内部调⽤:
==========================================================
@Transactional 只能应⽤到 public ⽅法才有效
只有@Transactional 注解应⽤到 public ⽅法,才能进⾏事务管理。这是因为在使⽤ Spring AOP 代理时,Spring 在调⽤在图 1 中的TransactionInterceptor 在⽬标⽅法执⾏前后进⾏拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的intercept ⽅法或 JdkDynamicAopProxy 的 invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取表 1. @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute ⽅法。
清单 6. AbstractFallbackTransactionAttributeSource
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.Modifiers())) {
return null;
}
}
这个⽅法会检查⽬标⽅法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会⽤TransactionInterceptor 来拦截该⽬标⽅法进⾏事务管理。
避免 Spring 的 AOP 的⾃调⽤问题
在 Spring 的 AOP 代理下,只有⽬标⽅法由外部调⽤,⽬标⽅法才由 Spring ⽣成的代理对象来管理,这会造成⾃调⽤问题。若同⼀类中的其他没有@Transactional 注解的⽅法内部调⽤有@Transactional 注解的⽅法,有@Transactional 注解的⽅法的事务被忽略,不会发⽣回滚。见清单 7 举例代码展⽰。
清单 7.⾃调⽤问题举例
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
insertOrder 尽管有@Transactional 注解,但它被内部⽅法 insert 调⽤,事务被忽略,出现异常事务不会发⽣回滚。
上⾯的两个问题@Transactional 注解只应⽤到 public ⽅法和⾃调⽤问题,是由于使⽤ Spring AOP 代理造成的。为解决这两个问题,使⽤ AspectJ 取代 Spring AOP 代理。
需要将下⾯的 AspectJ 信息添加到 xml 配置信息中,或者指定注解EnableTransactionManagement的属性。
AspectJ 的 xml 配置信息
<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
springboot aop<bean
class="ansaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
AspectJ 的 JavaConfig配置信息
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
public class Application {
}
同时在 Maven 的 pom ⽂件中加⼊ spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。
AspectJ 的 pom 配置信息
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论