事务控制以及@Transactional的使用

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;

事务是作用于数据库的,而非作用到某张表(不然就不能对两张表进行回退)

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值