SpringBoot2.x基础教程:事务管理⼊门
什么是事务?
我们在开发企业应⽤时,通常业务⼈员的⼀个操作实际上是对数据库读写的多步操作的结合。由于数据操作在顺序执⾏的过程中,任何⼀步操作都有可能发⽣异常,异常会导致
后续操作⽆法完成,此时由于业务逻辑并未正确的完成,之前成功操作的数据并不可靠,如果要让这个业务正确的执⾏下去,通常有实现⽅式:
1. 记录失败的位置,问题修复之后,从上⼀次执⾏失败的位置开始继续执⾏后⾯要做的业务逻辑
2. 在执⾏失败的时候,回退本次执⾏的所有过程,让操作恢复到原始状态,带问题修复之后,重新执⾏原来的业务逻辑
事务就是针对上述⽅式2的实现。事务,⼀般是指要做的或所做的事情,就是上⾯所说的业务⼈员的⼀个操作(⽐如电商系统中,⼀个创建订单的操作包含了创建订单、商品库
存的扣减两个基本操作。如果创建订单成功,库存扣减失败,那么就会出现商品超卖的问题,所以最基本的最发就是需要为这两个操作⽤事务包括起来,保证这两个操作要么都
成功,要么都失败)。
这样的场景在实际开发过程中⾮常多,所以今天就来⼀起学习⼀下Spring Boot中的事务管理如何使⽤!
快速⼊门
在Spring Boot中,当我们使⽤了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会⾃动默认分别注⼊DataSourceTransactionManager或
JpaTransactionManager。所以我们不需要任何额外配置就可以⽤@Transactional注解进⾏事务的使⽤。
我们以之前实现的的⽰例作为基础⼯程进⾏事务的使⽤学习。在该样例⼯程中(若对该数据访问⽅式不了解,可先阅读该前⽂),我们引⼊了spring-data-jpa,并创建了User实
体以及对User的数据访问对象UserRepository,在单元测试类中实现了使⽤UserRepository进⾏数据读写的单元测试⽤例,如下:
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// 省略后续的⼀些验证操作
}
}
可以看到,在这个单元测试⽤例中,使⽤UserRepository对象连续创建了10个User实体到数据库中,下⾯我们⼈为的来制造⼀些异常,看看会发⽣什么情况。
通过@Max(50)来为User的age设置最⼤值为50,这样通过创建时User实体的age属性超过50的时候就可以触发异常产⽣。
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@Max(50)
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
执⾏测试⽤例,可以看到控制台中抛出了如下异常,关于age字段的错误:
2020-07-09 11:55:29.581 ERROR 24424 --- [ main] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [com.didispace.chapter310.User] during persist time for groups [javax.vali List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='最⼤不能超过50', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{ssage}'}
]]
此时查数据库中的User表:
可以看到,测试⽤例执⾏到⼀半之后因为异常中断了,前5条数据正确插⼊⽽后5条数据没有成功插⼊,如果这10条数据需要全部成功或者全部失败,那么这时候就可以使⽤事务
来实现,做法⾮常简单,我们只需要在test函数上添加@Transactional注解即可。
@Test
@Transactional
public void test() throws Exception {
// 省略测试内容
}
再来执⾏该测试⽤例,可以看到控制台中输出了回滚⽇志(Rolled back transaction for test context),
2020-07-09 12:48:23.831 INFO 24889 --- [ main] ansaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@f6efaab testClass = Chapter310ApplicationTests, testInstance = com.didispace.chapte 2020-07-09 12:48:24.011 INFO 24889 --- [ main] ansaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@f6efaab testClass = Chapter310ApplicationTests, testInstance = com.didispace.chapter310
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='最⼤不能超过50', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{ssage}'}
], mergedContextConfiguration = [WebMergedContextConfiguration@3c19aaa5 testClass = Chapter310ApplicationTests, locations = '{}', classes = '{class com.didispace.chapter310.Chapter310Application}', contextInitializerClasses = '[]', activePr 再看数据库中,User表就没有AAA到EEE的⽤户数据了,成功实现了⾃动回滚。
这⾥主要通过单元测试演⽰了如何使⽤@Transactional注解来声明⼀个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独⽴,会使⽤@Rollback注解让每个单
元测试都能在结束时回滚。⽽真正在开发业务逻辑时,我们通常在service层接⼝中使⽤@Transactional来对各个业务逻辑进⾏事务管理的配置,例如:
public interface UserService {
@Transactional
User update(String name, String password);
}
事务详解
上⾯的例⼦中我们使⽤了默认的事务配置,可以满⾜⼀些基本的事务需求,但是当我们项⽬较⼤较复杂时(⽐如,有多个数据源等),这时候需要在声明事务时,指定不同的事
务管理器。对于不同数据源的事务管理配置可以见中的设置。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例
如:@Transactional(value="transactionManagerPrimary")。
除了指定不同的事务管理器之后,还能对事务进⾏隔离级别和传播⾏为的控制,下⾯分别详细解释:
隔离级别
隔离级别是指若⼲个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
我们可以看ansaction.annotation.Isolation枚举类中定义了五个表⽰隔离级别的值:
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);validation框架
}
DEFAULT:这是默认值,表⽰使⽤底层数据库的默认隔离级别。对⼤部分数据库⽽⾔,通常这值就是:READ_COMMITTED。
READ_UNCOMMITTED:该隔离级别表⽰⼀个事务可以读取另⼀个事务修改但还没有提交的数据。该级别不能防⽌脏读和不可重复读,因此很少使⽤该隔离级别。
READ_COMMITTED:该隔离级别表⽰⼀个事务只能读取另⼀个事务已经提交的数据。该级别可以防
⽌脏读,这也是⼤多数情况下的推荐值。
REPEATABLE_READ:该隔离级别表⽰⼀个事务在整个过程中可以多次重复执⾏某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满⾜该查询,这
些新增的记录也会被忽略。该级别可以防⽌脏读和不可重复读。
SERIALIZABLE:所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。
通常情况下也不会⽤到该级别。
指定⽅法:通过使⽤isolation属性设置,例如:
@Transactional(isolation = Isolation.DEFAULT)
传播⾏为
所谓事务的传播⾏为是指,如果在开始当前事务之前,⼀个事务上下⽂已经存在,此时有若⼲选项可以指定⼀个事务性⽅法的执⾏⾏为。
我们可以看ansaction.annotation.Propagation枚举类中定义了6个表⽰传播⾏为的枚举值:
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
REQUIRED:如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
MANDATORY:如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW:创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于REQUIRED。
指定⽅法:通过使⽤propagation属性设置,例如:
@Transactional(propagation = Propagation.REQUIRED)
代码⽰例
本⽂的相关例⼦可以查看下⾯仓库中的chapter3-10⽬录:
Github:
Gitee:
如果您觉得本⽂不错,欢迎Star⽀持,您的关注是我坚持的动⼒!
本⽂⾸发:,转载请注明出处。
欢迎关注我的:程序猿DD,获得独家整理的学习资源和⽇常⼲货推送。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论