什么是事务?
我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。
事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。
快速入门
在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。
事务详解
上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如: @Transactional(value="transactionManagerPrimary")。
异常回滚:
默认遇到throw new RuntimeException(“…”);会回滚
需要捕获的throw new Exception(“…”);不会回滚
指定回滚
@Transactional(rollbackFor=Exception.class) public void methodName() { // 不会回滚 throw new Exception("..."); }
指定不回滚
@Transactional(noRollbackFor=Exception.class) public ItimDaoImpl getItemDaoImpl() { // 会回滚 throw new RuntimeException("注释"); }
隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
我们可以看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)
readOnly
readOnly=true只读,不能更新,删除
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)
设置超时时间
@Transactional (propagation = Propagation.REQUIRED,timeout=30)
这个位置有坑,请看https://www.gaoyaxuan.net/blog/401.html
指定事务管理器
关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。
你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 PlatformTransactionManager 接口的哪个实现类。
打印项目事务管理器
@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为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。
代码如下:
指定事务管理器
@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()<<<<<<<<"); } }
注意:事务无效的情况
需要在启动类上面添加允许事务的注解@EnableTransactionManagement,类似<tx:annoation transactionmanger/>。
在SpringBoot中使用jpa,表是通过对应的实体直接建立,默认情况下,表的引擎engine=MyISAM性能更好,但不提供事务支持,如果需要使用事务,需要将JPA建表的默认引擎修改为:InNoDB,直接修改配置文件,如下:
# application.xml spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
或者
# application.yml spring: jpa: database-platform: org .hibernate.dialect.MySQL5InnoDBDialect #InnoDB才能支持事务
在service业务层抛出异常但是被try,catch了,异常被吃掉,事务无法捕捉异常,就不回滚了。
还没有评论,来说两句吧...