🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在Spring Boot中,当我们使用了`spring-boot-starter-jdbc`或`spring-boot-starter-data-jpa`依赖的时候,框 架会自动默认分别注入`DataSourceTransactionManager`或`JpaTransactionManager`。所以我们不需要任何额外 配置就可以用`@Transactional`注解进行事务的使用。 在该样例工程中,我们引入了spring-data-jpa,并创建了`User`实体以及对User的数据访 问对象`UserRepository`,在`ApplicationTest`类中实现了使用`UserRepository`进行数据读写的单元测试用例. 通过定义User的name属性长度为5,这样通过创建时User实体的name属性超长就可以触发异常产生。 ~~~ @Entity public class User { @Id @GeneratedValue private Long id; @Column(nullable = false, length = 5) private String name; @Column(nullable = false) private Integer age; // 省略构造函数、getter和setter } ~~~ 测试用例 ~~~ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) 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("HHHHHHHHHH", 80)); userRepository.save(new User("III", 90)); userRepository.save(new User("JJJ", 100)); // 省略后续的一些验证操作 } } ~~~ 执行测试用例 ~~~ 2016-05-27 10:30:35.948 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001 2016-05-27 10:30:35.948 ERROR 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column 'name' at row 1 2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000 2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column 'name' at row 1 org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement ~~~ 此时查数据库中,创建了name从AAA到GGG的记录,没有HHHHHHHHHH、III、JJJ的记录。而若这是一个希望保证完整性操作的情况 下,AAA到GGG的记录希望能在发生异常的时候被回退,这时候就可以使用事务让它实现回退,做法非常简单,我们只需要在test函数上添加 `@Transactional` 注解即可。 ~~~ @Test @Transactional public void test() throws Exception { // 省略测试内容 } ~~~ 这里主要通过单元测试演示了如何使用 @Transactional 注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用 `@Rollback` 注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时,我们通常在service层接口中使用 `@Transactional` 来对各个业务逻辑进行事务管理的配置,例如: ~~~ public interface UserService { @Transactional User login(String name, String password); } ~~~ spring Boot 使用事务非常简单,首先使用注解 `@EnableTransactionManagement` 开启事务支持后,然后在访问数据库的Service方法上添加注解 `@Transactional `便可。 关于事务管理器,不管是JPA还是JDBC等都实现自接口 `PlatformTransactionManager` 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 `DataSourceTransactionManager` 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 `JpaTransactionManager` 实例。 你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 `PlatformTransactionManager` 接口的哪个实现类。 ~~~ @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven /> @SpringBootApplication public class ProfiledemoApplication { @Bean public Object testBean(PlatformTransactionManager platformTransactionManager){ System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName()); return new Object(); } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } } ~~~ 这些SpringBoot为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。 代码如下: ~~~ @EnableTransactionManagement @SpringBootApplication public class ProfiledemoApplication { // 其中 dataSource 框架会自动为我们注入 @Bean public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public Object testBean(PlatformTransactionManager platformTransactionManager) { System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName()); return new Object(); } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } } ~~~ 在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。 然后在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。 对于同一个工程中存在多个事务管理器要怎么处理,请看下面的实例,具体说明请看代码中的注释。 @EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的 <tx:annotation-driven /> @SpringBootApplication public class ProfiledemoApplication implements TransactionManagementConfigurer { ~~~ @Resource(name="txManager2") private PlatformTransactionManager txManager2; // 创建事务管理器1 @Bean(name = "txManager1") public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } // 创建事务管理器2 @Bean(name = "txManager2") public PlatformTransactionManager txManager2(EntityManagerFactory factory) { return new JpaTransactionManager(factory); } // 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器 @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return txManager2; } public static void main(String[] args) { SpringApplication.run(ProfiledemoApplication.class, args); } } ~~~ ~~~ @Component public class DevSendMessage implements SendMessage { // 使用value具体指定使用哪个事务管理器 @Transactional(value="txManager1") @Override public void send() { System.out.println(">>>>>>>>Dev Send()<<<<<<<<"); send2(); } // 在存在多个事务管理器的情况下,如果不使用value具体指定 // 则默认使用方法 annotationDrivenTransactionManager() 返回的事务管理器 @Transactional public void send2() { System.out.println(">>>>>>>>Dev Send2()<<<<<<<<"); } } ~~~ >注: 如果Spring容器中存在多个 PlatformTransactionManager 实例,并且没有实现接口 TransactionManagementConfigurer 指定默认值,在我们在方法上使用注解 @Transactional 的时候,就必须要用value指定,如果不指定,则会抛出异常。 >对于系统需要提供默认事务管理的情况下,实现接口 TransactionManagementConfigurer 指定。 >对有的系统,为了避免不必要的问题,在业务中必须要明确指定 @Transactional 的 value 值的情况下。不建议实现接口 TransactionManagementConfigurer,这样控制台会明确抛出异常,开发人员就不会忘记主动指定。 > 除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释: ## 隔离级别 隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。 我们可以看 org.springframework.transaction.annotation.Isolation 枚举类中定义了五个表示隔离级别的值: ~~~ public enum Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); } ~~~ * DEFAULT :这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED 。 * READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。 * READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 * REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。 * SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 指定方法:通过使用 isolation 属性设置,例如: `@Transactional(isolation = Isolation.DEFAULT)` ## 传播行为 所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。 我们可以看 org.springframework.transaction.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)`