1.使用@Transactional的注意点
1.1 默认值
(1)@Transactional默认回滚的是error,而非Exception,所以我们需要如下设置
@Transactional(rollbackFor = Exception.class)
1.2 @Transactional事务在类内部方法调用不生效
spring 事务处理中,同一个类中:A方法(无事务)调B方法(有事务),事务不生效问题
public class Test {
public void a() {
b();
}
@Transactional(rollbackFor = Exception.class)
public void b() {
......
}
}
spring事务处理是通过aop实现的,aop的本质是动态代理,即通过反射将目标方法进行增强,我们通过@Autowired依赖注入的时候,获取到的对象的方法都已经是代理方法了,所以能执行事务控制,而类内部方法调用则是直接调用,调用的是目标方法
解决方法一:手动开启事务,手动提交和回滚
前提是在spring中有事务管理器(不管是哪种类型的事务管理器,父类都是DataSourceTransactionManager )
public class Test {
public void a() {
......
b();
}
@Autowired
private DataSourceTransactionManager dstManager;
public void b() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
TransactionStatus transaction= dstManager.getTransaction(def); // 获得事务状态
try{
......
// 提交
dstManager.commit(transaction);
} catch (Exception e) {
//设置手动回滚
dstManager.rollback(transaction);
}
}
}
注意b方法中的事务管理无法作用到a方法中
解决方法二
通过@Autowired注解获取的Trans类对象被spring管理,它的所有方法都是代理方法,具有事务的能力
public class Test {
@Autowired
private Trans trans;
public void a() {
......
trans.b();
}
}
@service
public class Trans{
@Transactional(rollbackFor = Exception.class)
public void b() {
......
}
}
场景:遍历操作时,我们根据对象的条件修改数据库和bpm中的状态,要求我们保证事务一致性的同时,不影响下一个对象的操作
@Service
public class JobServiceImpl {
@Autowired
private JobMapper jobMapper;
private void cleanJob() {
List<Job> jobs = jobMapper.selectJobs(LocalDate.now().minusMonths(6));
for (StringUtils.isNotEmpty(jobs)) {
for (Job job: jobs) {
cancelJob(job);
}
}
}
private void cancelJob(Job job) {
// 手动开启事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
TransactionStatus transaction= dstManager.getTransaction(def); // 获得事务状态
try {
// 1.修改表的状态,将当前job记录的状态修改为-2
jobMapper.updateStatus(job);
// 2.修改bpm中数据
if (StringUtils.isNotEmpty(job.getInstanceId())) {
R r = audit(job.getInstanceId());
if (!r.isSuccess()) {
// 此处抛异常是为了回滚表中数据
throw new MissingParamException(r.get("msg").toString());
}
}
// 事务提交
dstManager.commit(transaction);
} catch (Exception e) {
// 事务回滚
dstManager.rollback(transaction);
}
}
}
问:为什么修改表中数据的代码在前面
因为对于每一个job,我们都要进行两方面的操作:1修改job表中的状态;2修改bpm中此job的状态,我们需要保持其一致性,但是bpm属于远程服务,我们这里并没有做分布式事务,但所幸如果出了异常bpm是不会进行修改,同时返回结果为false,所以我们可以需要将修改表中状态的代码放前面,这样如果修改表中状态出现了异常就直接回滚了,不会影响到bpm,反之如果bpm放前面,如果bpm执行成功了,但是修改表中内容出现了异常,就无法回滚远程服务的数据了
问:为什么要手动开启事务
因为如果再cancelJob方法上添加@Transactional,cleanJob方法中调用cancelJob,其实是不会走事务的,原理在上面有写,所以我们需要手动开启事务,在执行这个方法的时候开启事务,执行完提交事务(模拟spring控制事务的方式),有异常就回滚
扩展:除了手动开启事务,我们也可以使用@Autowired的方式进行手动回滚
@Service
public class JobServiceImpl {
@Autowired
private JobMapper jobMapper;
@Autowired
private CancelServcie cancelService;
private void cleanJob() {
List<Job> jobs = jobMapper.selectJobs(LocalDate.now().minusMonths(6));
for (StringUtils.isNotEmpty(jobs)) {
for (Job job: jobs) {
cancelJob(job);
}
}
}
}
@Service
public class CancelServcie{
@Transactional(rollbackFor = Exception.class)
private void cancelJob(Job job) {
try {
// 1.修改表的状态,将当前job记录的状态修改为-2
jobMapper.updateStatus(job);
// 2.修改bpm中数据
if (StringUtils.isNotEmpty(job.getInstanceId())) {
// bpm中修改这个状态
R r = audit(job.getInstanceId());
if (!r.isSuccess()) {
// 此处抛异常是为了回滚表中数据
throw new MissingParamException(r.get("msg").toString());
}
}
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
}
}
}
如果没出异常就正常走代码流程,如果出异常就手动回滚,这里使用TransactionAspectSupport.currentTransactionStatus()就必须在方法上添加@Transactional注解(spring管理事务),或者手动开启事务
1.3 @Transactional的作用域
@Transactional注解在方法上,在a方法开启前开启事务,当a方法执行完成后提交事务(反射原理)
public class Test {
@Transactional(rollbackFor = Exception.class)
public void a() {
......
}
}
在一个事务中,对不同的表进行操作是否也能控制住呢?
我们写了两个mapper,在方法中,分别对两张表进行修改操作
@Transactional
public void test() {
UserMapper.insertUser("zhangsan");
RoleMapper.insertRole("lisi");
int i = 1/0;
}
结论:当发生了异常后,这两张表中的数据都被回退了
ps:即使是同一个Mapper中操作不同的两张表也是发生同样的情况
其实我们刚刚的操作,就等同于在mysql中执行以下代码
START TRANSACTION;
insert into `user` (code, status) values ('wxf', 1);
insert into `role` (code, status) values ('wxf', 1);
select * from `user` where code = 'wxf' order by create_date desc;
select * from role where code = 'wxf' order by create_date desc;
ROLLBACK;
事务是作用于数据库的,而非作用到某张表(不然就不能对两张表进行回退)