转自:https://blog.csdn.net/linzhiqiang0316/article/details/52638039
css
咱们在开发企业应用时,对于业务人员的一个操做实际是对数据读写的多步操做的结合。因为数据操做在顺序执行的过程当中,任何一步操做都有可能发生异常,异常会致使后续操做没法完成,此时因为业务逻辑并未正确的完成,以前成功操做数据的并不可靠,须要在这种状况下进行回退。html
事务的做用就是为了保证用户的每个操做都是可靠的,事务中的每一步操做都必须成功执行,只要有发生异常就回退到事务开始未进行操做的状态。java
事务管理是Spring框架中最为经常使用的功能之一,咱们在使用Spring Boot开发应用时,大部分状况下也都须要使用事务。mysql
在Spring Boot中,当咱们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框 架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。因此咱们不须要任何额外 配置就能够用@Transactional注解进行事务的使用。spring
在该样例工程中(若对该数据访问方式不了解,可先阅读该文章),咱们引入了spring-data-jpa,并建立了User实体以及对User的数据访 问对象UserRepository,在ApplicationTest类中实现了使用UserRepository进行数据读写的单元测试用例,以下:sql
@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("HHH", 80)); userRepository.save(new User("III", 90)); userRepository.save(new User("JJJ", 100)); // 省略后续的一些验证操做 } }
能够看到,在这个单元测试用例中,使用UserRepository对象连续建立了10个User实体到数据库中,下面咱们人为的来制造一些异常,看看会发生什么状况。数据库
经过定义User的name属性长度为5,这样经过建立时User实体的name属性超长就能够触发异常产生。springboot
@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 }
修改测试用例中建立记录的语句,将一条记录的name长度超过5,以下:name为HHHHHHHHH的User对象将会抛出异常。并发
// 建立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));
执行测试用例,能够看到控制台中抛出了以下异常,name字段超长:框架
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
来对各个业务逻辑进行事务管理的配置,例如:
=====================================================
SpringBoot事物的使用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,这样控制台会明确抛出异常,开发人员就不会忘记主动指定。
上面的例子中咱们使用了默认的事务配置,能够知足一些基本的事务需求,可是当咱们项目较大较复杂时(好比,有多个数据源等),这时候须要在声明事务时,指定不一样的事务管理器。对于不一样数据源的事务管理配置能够见 《Spring Boot多数据源配置与使用》 中的设置。在声明事务时,只须要经过value属性指定配置的事务管理器名便可,例如:@Transactional(value="transactionManagerPrimary")
。
除了指定不一样的事务管理器以后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:
隔离级别是指若干个并发的事务之间的隔离程度,与咱们开发时候主要相关的场景包括:脏读取、重复读、幻读。
咱们能够看 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)