Spring事务注意事项
Spring有两种事务操作方式。
1.采用编程式事务。
2.采用注解声明式事务。
编程式事务依赖于Spring事务管理的两个核心类。分别式:
1. PlatformTransactionManager
2. TransactionTemplate(推荐使用)
PlatformTransactionManager的使用
1. 先注入DataSource到事务管理器中。
<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" />
</bean>
2. 使用
@Resource private PlatformTransactionManager txManager;
......
//定义事务隔离级别,传播行为,
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
TransactionStatus status = txManager.getTransaction(def);
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中记录总数:"+i);
try {
jdbcTemplate.update(INSERT_SQL, "1");
txManager.commit(status); //提交status中绑定的事务
} catch (RuntimeException e) {
txManager.rollback(status);
//回滚
}
TransactionTemplate的使用该类继承了接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模板类定义,
主要是通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,
通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate模板类使用的回调接口:
1. TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
TransactionCallbackWithoutResult:继承TransactionCallback接口,
2. 提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
//构造函数初始化TransactionTemplate TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//重写execute方法实现事务管理
template.execute(new TransactionCallbackWithoutResult()
{ @Override protected void doInTransactionWithoutResult(TransactionStatus status)
{ jdbcTemplate.update(INSERT_SQL, "饿死");
//字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
}} );
声明式事务声明式事务操作简单,无侵入式,与业务代码分开。声明式事务也有两种实现方式。
1. 采用Spring的<tx:advice>定义事务通知与AOP配置实现。
2. 通过@Transactional实现。
<!--
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
<tx:method>拦截方法,其中参数有:
name:方法名称,将匹配的方法注入事务管理,可用通配符
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
-->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
<!-- 支持,如果有就有,没有就没有 -->
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 -->
<aop:config>
<aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
注意点:
1.@Transactional 注解由于原理决定了他只能作用于public方法中,而这里改为private,就完全被忽略无视了。
2.必须要将方法写到另一个类中,而且要通过spring的注入方式进行调用才可以,因为因为Spring是基于CGLIB等AOP方法实现,可以通过生成动态代理类或者@Resource引用自己。
3.<tx:annotation-driven>一共有四个属性如下,
mode:指定Spring事务管理框架创建通知bean的方式。可用的值有proxy和aspectj。前者是默认值,表示通知对象是个JDK代理;后者表示Spring AOP会使用AspectJ创建代理
proxy-target-class:如果为true,Spring将创建子类来代理业务类;如果为false,则使用基于接口的代理。(如果使用子类代理,需要在类路径中添加CGLib.jar类库)
order:如果业务类除事务切面外,还需要织入其他的切面,通过该属性可以控制事务切面在目标连接点的织入顺序。
transaction-manager:指定到现有的PlatformTransaction Manager bean的引用,通知会使用该引用
4.如果想某个方法单独一个事务提交,可以将事务的传播级别设置为:
@Transactional(propagation = Propagation.REQUIRES_NEW)
5.类上的Transactional注解是全量被方法上的注解替换的,所以,以下方式是不会抛出异常的。因为Spring的事务默认只有在发生运行时异常即:RunTimeException时才会发生事务,如果一个方法抛出Exception或者Checked异常Spring的事务并不会回滚。
@Slf4j
@Service
@Transactional(rollbackFor = {Exception.class}, transactionManager = "xdTransactionManager")
public class WcPayServiceImpl implements WcPayService {
@Resource
private WxPayService wxService;
@Transactional(transactionManager = "xdTransactionManager")
@Override
public Object invoke(JfappTransRequest request) throws Exception {
throw new Exception("test");
}
6. 如果想在当前类中让@Transactional生效,可以自己生成代理类。也可以注入当前类,如下所示。
@Slf4j
@Service
public class WcPayServiceImpl implements WcPayService {
//注入当前类
@Resource
private WcPayService wcPayService;
@Transactional(rollbackFor = Exception.class)
@Override
public String handlerOrderNotifyResult(WxPayOrderNotifyResult notifyResult, boolean isNotify) {
}
//当前类中使用
String strRet = wcPayService.orderNotifyResultHandler(notifyResult);
}
7. TransactionSynchronizationManager先提交事务,后异步操作,记得只有先有事务才能提交事务,要加入@Transactional
最开始需求,在修改之后提交一个异步请求,最开始的处理方式,伪代码
@Transactional
public void createActivity(){
createActivity();//新增操作
updateOrder();//修改操作
activityOrderAsyncService.activityOrderPayCallBack(activityReportOrder);
}
@Service
public class ActivityOrderAsyncServiceImpl implements ActivityOrderAsyncService {
@Async
public void activityOrderPayCallBack(ActivityReportOrder activityReportOrder) throws Exception {
...........报错了
}
}
注入代码以及接口省略......
按照常理来说一切都没问题,但报错,因为有事务,所以全部回滚了。目的是不管异步处理是否报错,新增操作和修改操作都可以成功
解决办法,提交当前事务并发送异步处理
@Transactional
public void createActivity(){
createActivity();//新增操作
updateOrder();//修改操作
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
try {
activityOrderAsyncService.activityOrderPayCallBack(activityReportOrder);
} catch (Exception e) {
log.error("操作失败", e);
}
}
});
}
@Transactional
@Async
以上两个注解如果同时使用,在同一个类中@Async不会生效,@Async是基于动态代理的,需要代理对象。在@Transactional 方法中如果调用@Async的方法,如果两个方法都在同一个类中,那么调用@Async方法的是一个真实对象(this.xxxxxx()),this就是当前的对象,而非代理对象,@Async就不会生效,可以直接获取代理对象再调用@Async方法。或者把@Async的方法放在其他类中,注入调用方法的页是代理对象。