我们操作数据库,事务管理是必不可少的一部分。
什么是事务
我们在开发企业应用时,用户的一个操作对应数据库可能是多步操作相结合完成的。在这个多个步骤中,其中的一步,可能出现异常,导致后面的步骤无法向下进行,那么,用户的这个操作,可能就没有进行完,前面已经进行的步骤数据就需要回退。
举个总所周知的栗子:
银行的转账,A给B转账,转1000块钱,A的钱需要扣1000,B的钱需要加1000,而,银行的系统在A扣1000块钱之后,B加1000块钱的时候,出现的异常,A的钱扣了,B的钱没有加,这该怎么办?这就需要用到我们的事务管理了。
事务就是保证用户的每个操作都是可靠的,事务中的每一个步操作都必须成功执行,如果,其中某一个步骤出现了异常,那么就回退到事务开始未进行操作的状态。
事务管理是Spring框架中最为常见的功能之一,我们在SpringBoot开发应用时,大部分情况下也需要使用事务。
事务管理操作步骤
理论上就只有两步:
- @EnableTransactionManagement:标记在启动类
- @Transactional:标记的service层
为什么说理论上只需要两步呢?因为,在SpringBoot中,当我们使用 spring-boot-starter-data-jdbc 或者是 spring-boot-starter-data-jpa 依赖的时候,框架会自动帮我们注入 入DataSourceTransactionManager 或者 JpaTransactionManager 。所以,我们不需要进行任何额外的配置,就直接可以使用 @Transactional 注解进行管理事务。
还有几点需要注意的地方:
- Hibernate创建表,默认类型是 MyISAM, 是非事务安全的,即使你加上了上面的注解,也不起作用。 Innodb 类型的表才是事务安全的。
- 需要在你的配置文件中指定: spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
事务的隔离级别和传播行为
除了指定事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:
隔离级别
隔离级别是指在发生并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读、不可重复读、幻读。
- 脏读:A事务执行的过程中,修改了id=1的数据,这个时候还没有提交,此时,B读取了修改之后id=1的数据,而A事务却回滚了,这样B事务就形成了脏读。 比方说:A的银行卡里原来有1000块钱,买东西花了300,还有700,这个时候A的媳妇查银行卡的余额,一看只剩下700了,然后,A又不喜欢那个东西,把东西退掉了,买东西的钱又退回银行卡了,这个时候,A的媳妇查的钱这个事务就是脏读。回来之后一阵毒打,什么跪键盘,跪榴莲。唉,脏读,真惨。
- 不可重复读:A事务先读取了id=1的数据,然后执行后面的逻辑,这个时候,B事务修改了id=1的数据,A在执行后面逻辑的时候,又读取了一遍id=1的数据,这个时候发现,两次读取的数据不相同,这就是不可重复读。 比方说:A发工资了,银行卡里发了1000块钱,每天早上查一遍,中午查一遍,好安心。早上9点上班的时候查了一遍余额有1000块钱,A的媳妇,十点多的时候买了个包包花了500块钱,A中午查的时候就剩下500了,他就去找他媳妇理论啊,你干啥了,咋就只剩下500了,他媳妇一听就来气啊,每个月挣这么点钱,我买个包包怎么了,然后就是一阵毒打,跪键盘,跪榴莲。唉,不可重复读,真惨。
- 幻读:A事务先根据条件查询到了N条数据,然后,B事务新增了M条复合A事务查询条件的数据,导致A事务再次查询的时候,查询到了N+M条数据,就产生了幻觉。 比方说:A的媳妇查询A名下有多少张银行卡,一开始查询有2张(建行,农行),然后,A换了新工作,需要重新再邮政开工资卡。这一天,A的媳妇又查了一遍A名下的银行卡,咦,怎么多了一张邮政的,长能耐了,背着老娘藏私房钱了,于是,就把A一阵毒打,是跪键盘,跪榴莲。唉,幻读,真惨。
怎么避免上面的三种情况呢?我们可以看 org.springframework.transaction.annotation.Isolation 枚举类中定义了五个表示隔离级
别的值:
- DEFAULT: 这个是默认值,表示使用底层数据库的默认隔离级别,对绝大多数数据库而言,这个值通常就是 READ_COMMITTED。
- READ_UNCOMMITTED: 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。 该级别不
能防止脏读和不可重复读, 因此很少使用该隔离级别。 - READ_COMMITTED: 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。 该级别可以防止脏
读,这也是大多数情况下的推荐值,性能最好。 - REPEATABLE_READ: 该隔离级别表示一个事务在整个执行过程中可以多次重复执行某个查询,并且每次返回的数据都相同,即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。 该级别可以
防止脏读和不可重复读。 - SERIALIZABLE: 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说, 该级别可
以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
1 | //指定方式: |
传播行为
传播行为是指,如果在开始当前事务之前,已经存在一个事务,此时可以指定这个要开始的这个事务的执行行为。
我们可以看 org.springframework.transaction.annotation.Propagation 枚举类中定义了6个表示传播行
为的枚举值:
- REQUIRED:(默认)如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则
该取值等价于 REQUIRED 。
1 | //指定方式: |
关于这个事务的隔离级别和传播行为,我们一般都不用特别的去指定,用默认的就行了,除非有特别的要求,默认的隔离级别和传播行为满足绝大多数要求。