数据库事务
一、隔离级别
数据库事务的4个基本特征,ACID
- Atomic(原子性):事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功、要么全部失败,不会出现部分失败,部分成功的场景。
- Consistency(一致性):在事务完成时,必须使所有的数据保持一致状态
- Isolation(隔离性):由于多个引用程序线程访问同一数据,这样数据库的数据就会在各个不同的事务中被访问。这样会产生丢失更新。为了压制丢失更新的产生,数据库定义了隔离级别的概念,通过它的选择,可以在不同程度上压制丢失更新的发生。
- Durability(持久性):事务结束后,所有的数据会固化到一个地方,如保存到磁盘中。
下面深入讨论隔离性:
初始隔离级别 - 读未提交 read uncommitted
解决问题:脏读(读取了未提交的数据。)
解决方案:隔离级别设置为 - 读已提交 read committed
解决问题:不可重复读(一个事务内两次读取数据不同,由于被另一事务修改并提交)
解决方案:隔离级别设置为 - 重复读 repeatable read(一个事务读取的过程中,另一事物不允许修改)
解决问题:幻读(和不可重复读一样,区别为出现了新增或删除数据,让读取者产生了幻觉,而不是数据不准确)
解决方案:隔离级别设置为 - 序列化serializable (所有事务都顺序话执行,一个事务执行完了,再执行下一个)
二、传播行为
传播行为是方法之间调用事务采取的策略问题 。 在绝大部分的情况下,我们会认为数据库事务要么全部成功 , 要么全部失败。但现实中也许会有特殊的情况。例如,执行一个批量程序,它会处理很多 的交易,绝大部分交易是可以顺利完成的,但是也有极少数的交易因为特殊原因不能完成而发生异常,这时我们不应该因为极少数的交易不能完成而回滚批量任务调用的其他交易,使得那些本能完成的交易也变为不能完成了 。 此时,我们真实的需求是,在一个批量任务执行的过程中,调用多个交易时,如果有一些交易发生异常 ,只是回滚那些出现异常的交易,而不是整个批量任务,这样就能够使得那些没有 问题的交易可以顺利完成,而有问题的交易则不做任何事情 。
在 Spring 中, 当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。
批量任务我们称之为当前方法,那么批量事务就称为当前事务,当它调用单个交易时,称单个交易为子方法,当前方法调用子方法的时候,让每一个子方法不在当前事务 中执行,而是创建一个新的事务去执行子方法,我们就说当前方法调用子方法的传播行为为新建事务。此外 , 还可能让子方法在无事务、独立事务中执行,这些完全取决于你的业务需求。
1.传播行为的定义
在Spring事务机制中对数据库存在7种传播行为。它是通过枚举类Propagation定义的,其源码为
1 pacakage org.springframework.transaction.annotation; 2 /****imports****/ 3 public enum Propagation{ 4 //需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务。否则新建一个事务运行子方法 5 REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), 6 7 //支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则无事务的方式运行子方法 8 SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), 9 10 //须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务 , 就沿用当前事务 11 MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), 12 13 //无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立 14 REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), 15 16 //不支持事务,当前存在事务时,将挂起事务,运行方法 17 NOT_SUPPORTS(TransactionDefinition.PROPAGATION_NOT_SUPPORTS), 18 19 //不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行 20 NEVER(TransactionDefinition.PROPAGATION_NEVER), 21 22 //在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务 23 NESTED(TransactionDefinition.PROPAGATION_NESTED); 24 25 private final int value; 26 Propagetion(int value){this Value=value;} 27 public int value(){ 28 return this.value; 29 } 30 }
三、Spring 声明式事务的使用
Spring AOP 会把我们的代码织入到约定的流程中,同样,同样执行的SQL的代码也可以织入的哦Spring 约定的数据库事务的流程中。首先要掌握这个约定
1.Spring 声明式数据库事务约定
对于事务需要通过标注告诉Spring在什么地方启用数据库事务功能,对于声明式数据库,是使用@Transactional进行标注的。
@Transactional 这个注解可以标注类和方法上,当它标注在类上时,代表这个类所有公共(public)非静态的方法东将启用事务功能。在@Transactonal 中还可以进行事务的隔离级别和传播行为,异常类型的配置。这些配置,是在Sprng IoC容器在加载时就会将这些配置信息解析出来,然后帮这些喜喜存储到事务定义器(TransactonDefinition接口实现的类)里,并且记录了那些类或者方法需要启动事务的功能,采取什么策略去执行事务。在这个过程中我们所需要做的就是给需要事务的类和方法标注@Transactional并配置属性
spring的事务处理机制
Spring 通过对注解@Transactional 属性配置去设置数据库事务 , 跟着 Spring 就会
开始调用开发者编写 的业务代码 。 执行开发者 的业务代码,可能发生异常,也可能不发生异常 。 在Spring 数据库事务 的流程 中,它会根据是否发生异常采取不同的策略 如果都没有发生异常, Spring 数据库拦截器就会帮助我们提交事务 , 这点也并不需要我们干预 。如果发生异常,就要判断一次事务定义器内的配置,如果事务定义器己经约定了该类型的异常不回段事务就提交事务 , 如果没有任何配置或者不是配置不回滚事务的异常,则会回滚事务,并且将异常抛出 , 这步也是由事务拦截器完成的。论发生异常与否, Spring 都会释放事务资源,这样就可以保证数据库连接池正常可用了,这
也是由 Spring 事务拦截器完成的内容 。
1 public class UserServiceImpl implements UserService{ 2 3 @Autowired 4 private UserDao userDao=null; 5 6 @Override 7 @Transactional 8 public int insertUser(User user){ 9 return userDao.insertUser(user); 10 } 11 }