SpringBoot 中的使用事务
本文内容纲要:
-什么是事务?
-快速入门
-事务详解
转自:https://blog.csdn.net/linzhiqiang0316/article/details/52638039
什么是事务?
我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。
事务管理是Spring框架中最为常用的功能之一,我们在使用SpringBoot开发应用时,大部分情况下也都需要使用事务。
快速入门
在SpringBoot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。
在该样例工程中(若对该数据访问方式不了解,可先阅读该文章),我们引入了spring-data-jpa,并创建了User实体以及对User的数据访问对象UserRepository,在ApplicationTest类中实现了使用UserRepository进行数据读写的单元测试用例,如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
publicclassApplicationTests{@AutowiredprivateUserRepositoryuserRepository;@Testpublicvoidtest()throwsException{//创建10条记录userRepository.save(newUser("AAA",10));userRepository.save(newUser("BBB",20));userRepository.save(newUser("CCC",30));userRepository.save(newUser("DDD",40));userRepository.save(newUser("EEE",50));userRepository.save(newUser("FFF",60));userRepository.save(newUser("GGG",70));userRepository.save(newUser("HHH",80));userRepository.save(newUser("III",90));userRepository.save(newUser("JJJ",100));//省略后续的一些验证操作}}
可以看到,在这个单元测试用例中,使用UserRepository对象连续创建了10个User实体到数据库中,下面我们人为的来制造一些异常,看看会发生什么情况。
通过定义User的name属性长度为5,这样通过创建时User实体的name属性超长就可以触发异常产生。
@Entity
publicclassUser{@Id@GeneratedValueprivateLongid;@Column(nullable=false,length=5)privateStringname;@Column(nullable=false)privateIntegerage;//省略构造函数、getter和setter}
修改测试用例中创建记录的语句,将一条记录的name长度超过5,如下:name为HHHHHHHHH的User对象将会抛出异常。
//创建10条记录
userRepository.save(newUser("AAA",10));userRepository.save(newUser("BBB",20));userRepository.save(newUser("CCC",30));userRepository.save(newUser("DDD",40));userRepository.save(newUser("EEE",50));userRepository.save(newUser("FFF",60));userRepository.save(newUser("GGG",70));userRepository.save(newUser("HHHHHHHHHH",80));userRepository.save(newUser("III",90));userRepository.save(newUser("JJJ",100));
执行测试用例,可以看到控制台中抛出了如下异常,name字段超长:
2016-05-2710:30:35.948WARN2660---[main]o.h.engine.jdbc.spi.SqlExceptionHelper:SQLError:1406,SQLState:220012016-05-2710:30:35.948ERROR2660---[main]o.h.engine.jdbc.spi.SqlExceptionHelper:Datatruncation:Datatoolongforcolumn'name'atrow12016-05-2710:30:35.951WARN2660---[main]o.h.engine.jdbc.spi.SqlExceptionHelper:SQLWarningCode:1406,SQLState:HY0002016-05-2710:30:35.951WARN2660---[main]o.h.engine.jdbc.spi.SqlExceptionHelper:Datatoolongforcolumn'name'atrow1org.springframework.dao.DataIntegrityViolationException:couldnotexecutestatement;SQL[n/a];nestedexceptionisorg.hibernate.exception.DataException:couldnotexecutestatement
此时查数据库中,创建了name从AAA到GGG的记录,没有HHHHHHHHHH、III、JJJ的记录。而若这是一个希望保证完整性操作的情况下,AAA到GGG的记录希望能在发生异常的时候被回退,这时候就可以使用事务让它实现回退,做法非常简单,我们只需要在test函数上添加@Transactional
注解即可。
@Test
@Transactional
publicvoidtest()throwsException{//省略测试内容}
这里主要通过单元测试演示了如何使用@Transactional
注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用@Rollback
注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时,我们通常在service层接口中使用@Transactional
来对各个业务逻辑进行事务管理的配置,例如:
publicinterfaceUserService{@TransactionalUserlogin(Stringname,Stringpassword);}=====================================================
SpringBoot事物的使用
springBoot使用事务非常简单,首先使用注解@EnableTransactionManagement开启事务支持后,然后在访问数据库的Service方法上添加注解@Transactional便可。
关于事务管理器,不管是JPA还是JDBC等都实现自接口PlatformTransactionManager如果你添加的是spring-boot-starter-jdbc依赖,框架会默认注入DataSourceTransactionManager实例。如果你添加的是spring-boot-starter-data-jpa依赖,框架会默认注入JpaTransactionManager实例。
你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是PlatformTransactionManager接口的哪个实现类。
@EnableTransactionManagement//启注解事务管理,等同于xml配置方式的<tx:annotation-driven/>
@SpringBootApplicationpublicclassProfiledemoApplication{@BeanpublicObjecttestBean(PlatformTransactionManagerplatformTransactionManager){System.out.println(">>>>>>>>>>"+platformTransactionManager.getClass().getName());returnnewObject();}publicstaticvoidmain(String[]args){SpringApplication.run(ProfiledemoApplication.class,args);}}
这些SpringBoot为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。
代码如下:
@EnableTransactionManagement
@SpringBootApplication
publicclassProfiledemoApplication{//其中dataSource框架会自动为我们注入@BeanpublicPlatformTransactionManagertxManager(DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}@BeanpublicObjecttestBean(PlatformTransactionManagerplatformTransactionManager){System.out.println(">>>>>>>>>>"+platformTransactionManager.getClass().getName());returnnewObject();}publicstaticvoidmain(String[]args){SpringApplication.run(ProfiledemoApplication.class,args);}}
在Spring容器中,我们手工注解@Bean将被优先加载,框架不会重新实例化其他的PlatformTransactionManager实现类。
然后在Service中,被@Transactional注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。
对于同一个工程中存在多个事务管理器要怎么处理,请看下面的实例,具体说明请看代码中的注释。
@EnableTransactionManagement//开启注解事务管理,等同于xml配置文件中的<tx:annotation-driven/>
@SpringBootApplicationpublicclassProfiledemoApplicationimplementsTransactionManagementConfigurer{@Resource(name="txManager2")privatePlatformTransactionManagertxManager2;//创建事务管理器1@Bean(name="txManager1")publicPlatformTransactionManagertxManager(DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}//创建事务管理器2@Bean(name="txManager2")publicPlatformTransactionManagertxManager2(EntityManagerFactoryfactory){returnnewJpaTransactionManager(factory);}//实现接口TransactionManagementConfigurer方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器@OverridepublicPlatformTransactionManagerannotationDrivenTransactionManager(){returntxManager2;}publicstaticvoidmain(String[]args){SpringApplication.run(ProfiledemoApplication.class,args);}}
@Component
publicclassDevSendMessageimplementsSendMessage{//使用value具体指定使用哪个事务管理器@Transactional(value="txManager1")@Overridepublicvoidsend(){System.out.println(">>>>>>>>DevSend()<<<<<<<<");send2();}//在存在多个事务管理器的情况下,如果使用value具体指定//则默认使用方法annotationDrivenTransactionManager()返回的事务管理器@Transactionalpublicvoidsend2(){System.out.println(">>>>>>>>DevSend2()<<<<<<<<");}}
注:
如果Spring容器中存在多个PlatformTransactionManager实例,并且没有实现接口TransactionManagementConfigurer指定默认值,在我们在方法上使用注解@Transactional的时候,就必须要用value指定,如果不指定,则会抛出异常。
对于系统需要提供默认事务管理的情况下,实现接口TransactionManagementConfigurer指定。
对有的系统,为了避免不必要的问题,在业务中必须要明确指定@Transactional的value值的情况下。不建议实现接口TransactionManagementConfigurer,这样控制台会明确抛出异常,开发人员就不会忘记主动指定。
事务详解
上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。对于不同数据源的事务管理配置可以见《SpringBoot多数据源配置与使用》中的设置。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如:@Transactional(value="transactionManagerPrimary")
。
除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:
隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
我们可以看org.springframework.transaction.annotation.Isolation
枚举类中定义了五个表示隔离级别的值:
publicenumIsolation{
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个表示传播行为的枚举值:
publicenumPropagation{
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)
本文内容总结:什么是事务?,快速入门,事务详解,
原文链接:https://www.cnblogs.com/panchanggui/p/10882749.html