事务是数据库操作的最基本单元,是逻辑上的一组操作,要么都执行,要么都不执行。
但是我们需要格外注意的是,事务能否生效数据库引擎是否支持事务是关键。比如常用的MySQL数据库默认使用支持事务的innodb引擎。但是,如果数据库引擎变为myisam,那么程序就不支持事务了。
一个经典的例子就是转账问题了,假如lucy要给mary转100块,万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致lucy余额减少而mary的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
public void accountMoney(){
try{
//第一步:开启事务
//第二步:进行业务操作
//lucy少一百
userDao.reduceMoney();
//模拟异常
int i = 10/0;
//mary多一百
userDao.addMoney();
//第三步:没有发生异常,提交事务
}catch(Exception e){
//第四步:出现异常,事务回滚
}
}
接下来我们了解一下事务的四大特性:
-
原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
-
一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
-
隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
-
持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Spring支持两种方式的事务管理,编程式事务管理和声明式事务管理
编程式事务管理我们需要通过通过 TransactionTemplate或者TransactionManager手动管理事务
声明式事务管理是推荐使用的,因为其代码侵入性最小,是通过AOP实现的(基于@Transactional 的全注解方式使用最多)。
我们看一下Spring事务管理相关最重要的3个接口:
PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心。TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。TransactionStatus: 事务运行状态。
我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。
PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
这三个接口的具体讲解请看文末链接。
事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.xxx)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.xxx)
public void bMethod {
//do something
}
}
正确的事务传播行为可能的值如下 : 注解使用:
@Transactional(propagation = Propagation.REQUIRED)
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,
Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 如果外部方法开启事务并且被
Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。 我们画个图简单理解一下:
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
3.
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。也就是说:
- 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
- 如果外部方法无事务,则单独开启一个事务,与
PROPAGATION_REQUIRED类似。 4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
事务的隔离级别
注解使用:
@Transactional(isolation = Isolation.REPEATABLE_READ)
-
TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的READ_COMMITTED隔离级别. -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 -
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 -
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 -
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
脏读:一个未提交的事务读取到了另一个未提交的事务
不可重复读:一个未提交的事务读取到另一提交事务的修改数据
幻读:一个未提交的事务读取到另一事务已添加的事务
事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。
事务只读属性
读:查询操作
写:添加修改删除操作
readOnly默认值false,表示可以查询,可以添加修改删除操作
设置readOnly值是true,设置成true之后,只能查询
事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
rollbackFor:回滚
设置出现那些异常进行事务回滚
noRollbackFor:不回滚
设置出现那些异常不进行事务回滚