目录
一、事务的特性
事务简单来说是一串连续的原子性操作,事务具有以下特点:
●原子性(A),事务中的操作要么全部完成,要不就全部失败。
●隔离性(I),数据库允许事务并发访问数据库,隔离是为了防止多个事务对同一行数据的交叉修改引起的不一致。根据可忍受不一致的范围,目前的隔离级别有:
○未提交读,有脏读风险。
○已提交读,有不可重复读的风险。
○可重复读,有幻读的风险。
○串行化,最严格,但是会大大降低性能。
● 一致性(C),事务开始前,待变更行都是老数据,事务结束之后,变更行都是新数据,不会存在新老数据混杂的情况。
● 持久性(D),事务结束后,修改对于数据库来说是永久的,如果事务执行途中遇到数据库 crash,重启后数据也是正确的。
二、常见使用
通常我们在系统开发时会遇见很多事务场景,而且Spring框架也提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。
编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,且会存在较多的重复代码,如下示例:
try {
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new InvoiceApplyException("异常失败");
}
声明式事务:基于AOP面向切面开发,它将具体业务与事务处理部分解耦,代码侵入性低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了,通常都是使用注解进行事务管理。如下所示:
//可在注解属性中指定回滚的异常类,超时时间,是否为只读事务,事务隔离级别以及具体某个事务管理器
@Transactional(rollbackFor = Exception.class,timeout = 3000,transactionManager = "dynamicManager", propagation = Propagation.SUPPORTS, readOnly=false)
public String test() {
int insert = cityInfoDictMapper.insert(cityInfoDict);
}
@Transactional 可以作用在接口、类、类方法:
- 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
- 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
- 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
三、常见的注意问题
1. 事务操作中存在查询或者循环等耗时操作
事务其实类似于多线程中的加锁,在事务开始时会将部分资源进行冻结锁定,应该尽量将事务操作的执行时间缩短,像循环耗时或者查询这种与事务无关的操作都应该放在事务外进行;
2. 事务中发送消息
事务中发送消息其实是一个危险行为,抛开耗时问题,当消息发送成功后若事务提交失败则会造成脏消息,因为一般事务操作才是主业务,MQ消息是可人工补偿的分支业务,所以推荐做法是利用spring提供的事务同步管理器,在事务提交后再去进行消息发送:
@Transactional
public void testMQ(String var1){
//insert
//update
mqHelper.sendMsg();
}
public void sendMsg(){
//判断是否开启了事务
if (TransactionSynchronizationManager.isSynchronizationActive()){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
//sendMsg
}
});
}else {
//sendMsg
}
}
3. 事务注解在非public方法上
如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。之所以会失效是因为在Spring AOP 代理时,底层 TransactionInterceptor (事务拦截器)会在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用AbstractFallbackTransactionAttributeSource的
computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息,该方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
4. 调用同一个类中的方法事务会失效
在日常开发中我们经常会遇到方法嵌套的情况,比如一个service类中包含methodA方法和methodB方法,methodA调用了methodB方法,这个时候methodB方法上就算添加了事务注解也没用,因为Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理,而调用同一个类中的方法是使用的this指针调用,this是原有对象,所以事务会失效。通用的解决方案是分裂编写,将methodB抽离至其他类,然后注入调用。
private Integer A() throws Exception {
this.insertB();
/**
* 插入数据
*/
insert = xxxDao.insert(Object);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
xxxDao.insert(Object);
}