spring中常见事务失效处理分析
写在前面
只是总结,分析
一、事务基础
1.1、定义:
数据库几乎是所有系统的核心模块,它将数据有条理地保存在储存介质(磁盘)中,
并在逻辑上,将数据以结构化的形态呈现给用户。支持数据的增、删、改、查,并在过程中保障数据的正确且可靠。
1.2、四大特性(A-C-I-D)
- 原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
- 隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1和T2谁先结束。
- 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
- 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。
场景分析
如何同时保证上述交易中,A账户总金额减少一个亿,B账户总金额增加一个亿? A
A账户如果同时在和C账户交易(T2),如何让这两笔交易互不影响? I
如果交易完成时数据库突然崩溃,如何保证交易数据成功保存在数据库中? D
如何在支持大量交易的同时,保证数据的合法性(没有钱凭空产生或消失) ? C
1.3、常用事务管理
主要包括,以下几种
- Mysql 事务
- Oracle 事务
- Nosql 事务
- Spring 事务
- 分布式 事务
二、Spring 事务
2.1、Spring中事务基础
Spring中事务基础很简单,两种方式配置事务,XML或者注解方式,以下基于注解方式,介绍
实现原理:基于Spring AOP,可自行学习
七种事务机制
1. TransactionDefinition.PROPAGATION_REQUIRED:
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
3. TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
5. TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。
6. TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
7. TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
2.2、要注意的地方
- 默认事务:
Propagation propagation() default Propagation.REQUIRED
当然,可在注解中,根据需要,配置事务机制
- 声明位置:
类上,所有方法默认事务,不推荐,影响了查询效率(AOP)
方法上,该方法默认事务,但也要注意细节,包括修饰符,嵌套事务,异常处理等等,都会影响事务是否正确配置
- 修饰符
方法必须 public 修饰
- 异常处理
系统中的异常处理方式,很多,主要 return,throw ,try()catch(){}三种方式,只有throw的异常,才会触发事务
- 不同类嵌套事务
不同类之间的方法调用,如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()或b()配置了事务,运行中就会开启事务,产生代理。
若两个方法都配置了事务,两个事务具体以何种方式传播,取决于设置的事务传播特性。
- 同类嵌套事务
@Service
public class TestImpl implements TestService {
@Autowired
private Dao dao;
public void insertUser(User user) {
dao.insertUser(user);
this.selectUser(user.getId());
}
@Transactional
public String selectUser(int id) {
throw new RuntimeException();
}
}
这种同类中,事务方法的调用问题,默认情况下,只有来自外部的方法调用才会被AOP代理捕获,即是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰
解决方案:五种
方法1:将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。
方法2:获取本对象的代理对象,再进行调用.
- Spring-content.xml上下文中,增加配置:<aop:aspectj-autoproxy expose-proxy=“true”/>
- 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。
方法3:
很多时候,方法内调用又希望激活事务,是由于同一个方法既有DAO操作又有I/O等耗时操作,不想让耗时的I/O造成事务的太长耗时(比如新增商品同时需要写入库存)。此时,可以将I/O做成异步操作(如加入线程池),而加入线程池的操作即便加入事务也不会导致事务太长,问题可以迎刃而解。
方法4:手写事务
自行学习
方法5:
接口内部方法调用的错误实现,可以用AspectJ 取代 Spring AOP 代理
2.2、常见异常
- Connection is read-only,…操作只读事务异常
- UnexpectedRollbackException: Transaction rolled … 方法嵌套事务异常
- zzz
- xxx
2.3、Spring中常用事务处理操作 / 场景,要注意的地方
2.3.1、 捕捉异常, 导致不能回滚
代码示例,此处事务不会回滚
@Override
@Transactional
public void CatchExceptionCanNotRollback() {
try {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
} catch (Exception ex) {
ex.printStackTrace();
}
}
解决方案 :
- 1、手动处理如下
@Override
@Transactional
public void CatchExceptionCanNotRollback() {
try {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
} catch (Exception ex) {
ex.printStackTrace();
// 手动标记回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
- 2、不捕获
@Override
@Transactional
public void RuntimeExceptionCanRollback() {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
}
2.3.2、 自定义异常,导致事务不回滚
代码示例,无法回滚
@Override
@Transactional
public void NotRuntimeExceptionCanNotRollback() throws CustomException {
try {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
} catch (Exception ex) {
throw new CustomException(ex.getMessage());
}
}
解决方案 :
- 1、异常转换
@Override
@Transactional(rollbackFor = {
CustomException.class})
public void AssignExceptionCanRollback() throws CustomException {
try {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
} catch (Exception ex) {
throw new CustomException(ex.getMessage());
}
}
注意:这样 指定也是可以的
@Override
@Transactional(rollbackFor = {
Exception.class})
public void AssignExceptionCanRollback() throws CustomException {
try {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
} catch (Exception ex) {
throw new CustomException(ex.getMessage());
}
}
2.3.3、 嵌套方法
代码示例,此处事务 不回滚
public void oneSaveMethod() {
extraAdDao.save(new ExtraAd("otii"));
}
@Override
@Transactional
public void RollbackOnlyCanRollback() throws Exception {
oneSaveMethod();
throw new Exception("");
}
解决方案:
- 1、事务回滚 - 异常指定
public void oneSaveMethod() {
extraAdDao.save(new ExtraAd("otii"));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void RollbackOnlyCanRollback() throws Exception {
oneSaveMethod();
throw new Exception("");
}
- 2、配置事务传播机制
public void oneSaveMethod() {
extraAdDao.save(new ExtraAd("otii"));
}
@Override
@Transactional(propagation = Propagation.MANDATORY)
public void RollbackOnlyCanRollback() throws CustomException {
oneSaveMethod();
throw new CustomException("");
}
2.3.4、 同一个类中, 一个不标注事务的方法去调用 transactional 的方法, 事务会失效
代码示例,如下,事务不回滚
@Override
public void NonTransactionalCanNotRollback() {
anotherOneSaveMethod();
}
@Transactional
public void anotherOneSaveMethod() {
extraAdDao.save(new ExtraAd("otii"));
throw new RuntimeException();
}
这里会有奇怪的现象,就是 会莫名 commit
解决方案 :
- 1、调用事务方法时,加上 @Transactional 注解,即可解决
三、Mysql 事务
待记录
四、Oracle 事务
待记录
五、Nosql 事务
六、分布式事务
6.1、出现原因
6.2、JTA
JTA(Java Transaction API)提供了跨数据库连接(或其他JTA资源)的事务管理能力。JTA事务管理则由JTA容器实现,J2ee框架中事务管理器与应用程序,资源管理器,以及应用服务器之间的事务通讯。
还有
待记录
七、总结
待记录