1.声明式事务@Transactional
如果需要添加事务在类上或者public方法上添加@Transactional注解即可,如果某些方法不需要事务可以添加 @Transactional(propagation =Propagation.NOT_SUPPORTED),例如:
@Transactional //开启事务
public class TestServiceBean implements TestService {
private TestDao dao;
public void setDao(TestDao dao) {
this.dao = dao;
}
@Transactional(propagation =Propagation.NOT_SUPPORTED)//不要需要事务
public List getAll() {
return null;
}
}
一般情况下我们都会在Service实现类上添加@Transactional 注解开启事务。
2. 事务的传播行为
我们可以根据自己的业务需要添加不同的事务传播行为:
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(最常用的传播行为)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
3. 事务超时设置
我们还可以设置事务超时的时间,默认超时时间是30秒,可以根据业务情况适当修改时间
@Transactional(timeout=30) //事务超时时间默认是30秒,也可以自定义
4.事务的隔离级别
说到事务的隔离级别就要介绍几个名词:
脏读 | 一个事务读取到另一个事务未提交的数据 |
不可重复读 | 在同一事务中,多次读取发现同一数据返回的结果不同 |
幻读 | 一个事务读取到另一个事务已提交的数据 |
事务的基本要素(ACID):
- 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间 环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转 账,不可能A扣了钱,B却没收到。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
@Transactional中设置事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化
我们MySQL数据库的默认为REPEATABLE_READ级别,可以根据需要自行调整隔离级别,隔离级别越高,越能保证数据的完整性和统一性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读,而且具有较好的并发性能
5.@Transactional注解使用注意要点
1.@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记@Transactional也不会报错,但方法没有事务功能。
2.用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("运行时异常");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
public void methodName() {
throw new Exception("全部异常");
}
@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("运行时异常");
}
3.一般地我们在业务类上方添加@Transactional注解,这样更加清晰方便事务管理
6.Controller层调用多Service层方法并使用try catch捕获异常不能回滚问题
1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)在Controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
这里要清楚3个关于编程式事务的概念:
- PlatformTransactionManager 平台事务管理器,spring要管理事务,必须使用事务管理器
进行事务配置时,必须配置事务管理器。
- TransactionDefinition:事务详情(事务定义、事务属性),spring用于确定事务具体详情,
例如:隔离级别、是否只读、超时时间 等进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。
- TransactionStatus:事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。
代码案例:
//注入编程式事务管理器
@Autowired
private PlatformTransactionManager platformTransactionManager;
//注入事务详情
@Autowired
private TransactionDefinition transactionDefinition;
@Transactional(rollbackFor = Exception.class)
@RequestMapping(value = "/query", method = RequestMethod.POST)
public String getUser(String userId){
//开启事务状态
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
try{
...各种业务逻辑
//业务代码结束后提交事务
platformTransactionManager.commit(transactionStatus);
}catch(Exception e){
e.printStackTrace();
//出现异常事务回滚
platformTransactionManager.rollback(transactionStatus);
}
}