默认 @Transactional 注解式事务
(1)@EnableTransactionManagement
正常情况下,我们是需要在 ApplicationConfig 类加上 @EnableTransactionManagement 注解才能开启事务管理。通过 DataSource 的研究步骤 spring.factories 里面默认加载 TransactionAutoConfiguration 类,而我们看源码,其里面已经加了此注解,默认采用的 AdviceMode.PROXY,所以默认情况的事务管理机制是代理方式的,通过添加 @Transactional 注解式配置方法,查看 SimpleJpaRepository:
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {...}
所以每个 Respository 的方法是都是有默认的只读事务的。
(2)我们来查看一下 @Transactional 源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};
}
@Transactional 注解中常用参数说明
参数名称 | 功能描述 |
readOnly | |
rollbackFor | |
rollbackForClassName | |
noRollbackFor | |
noRollbackForClassName | |
propagation | |
isolation | |
timeout | |
transactionManager/value |
(3)propagation:传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
可以看 org.springframework.transaction.annotation.Propagation 枚举类中定义了 7 个表示传播行为的枚举值:
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)
(4)Isolation 隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
我们可以看 org.springframework.transaction.annotation.Isolation 枚举类中定义了四个表示隔离级别的值:
public enum Propagation {REQUIRED(0),SUPPORTS(1),MANDATORY(2),REQUIRES_NEW(3),NOT_SUPPORTED(4),NEVER(5),NESTED(6);
}
- DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别,对大部分数据库而言,通常这值就是READ_COMMITTED。
- READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
- READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
- SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读,但是这将严重影响程序的性能。通常情况下也不会用到该级别。
设定方法为通过使用 isolation 属性设置,例如:
@Transactional(isolation = Isolation.DEFAULT)
(5)所以 Spring Boot 的这种默认机制,只需要在我们用事务时,在方法上或者此方法的类上加上 @Transactional注解即可。
而实际工作中,我们一般都要在 Service 层的某些方法上加事务,以保证整个方法的事务。示例如下:
@Transactional(rollbackOn = Exception.class)
public void saveUserInfo() throws Exception {userCustomerRepository.save(new UserCustomerEntity("jackzhang@mail.com","jackzhang"));userRepository.save(new UserInfoEntity("jack_test","name"));throw new Exception("test");......//此方法体有多个repository的调用,模拟异常,事务会回滚的
}
(6)注意的几点
- @Transactional 只能被应用到 public 方法上,对于其他非 public 的方法,如果标记了 @Transactional 也不会报错,但方法没有事务功能。
- 用 spring 事务管理器,由 spring 来负责数据库的打开、提交、回滚,默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其他异常}),如果让 unchecked 例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)。
- @Transactional 注解应该只被应用到 public 可见度的方法上。如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但是这个被注解的方法将不会展示已配置的事务设置。
- @Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的 beans 所使用。上面的例子中,其实正是 元素的出现开启了事务行为。
- Spring 团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受 Spring 团队的建议并且在具体的类上使用 @Transactional 注解。
- 事务有两种配置方法,一种是我们现在说的显式的注解式事务,当我们注解式事务下,不加注解 service 方法上是没有任何事务的。还有一种是隐式事务,ASPECTJ 的思路配置方法,所以不是没有加 @Transactional 注解就一定没有事务。
声明式事务,又叫隐式事务,或者叫 ASPECTJ 事务
配置方法:
在实际工作中,每个方法都让我们加上 @Transactional 注解,可能工作量有点大,也有时候会忘,所以经常看到有开发团队配置拦截式事务。虽然 spring 官方不太推荐。只需要在我们的项目中新增一个类 AspectjTransactionConfig 即可,如下:
@Configuration
@EnableTransactionManagement
public class AspectjTransactionConfig {public static final String transactionExecution = "execution (* com.jackzhang.example..service.*.*(..))";@Autowiredprivate PlatformTransactionManager transactionManager;@Beanpublic DefaultPointcutAdvisor defaultPointcutAdvisor() {//指定一般要拦截哪些类AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(transactionExecution);//配置advisorDefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();advisor.setPointcut(pointcut);//指定不同的方法用不通的策略Properties attributes = new Properties();attributes.setProperty("get*", "PROPAGATION_REQUIRED,-Exception");attributes.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");attributes.setProperty("save*", "PROPAGATION_REQUIRED,-Exception");attributes.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");attributes.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");//创建InterceptorTransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, attributes);advisor.setAdvice(txAdvice);return advisor;}
}
这样我们的 Service 就会自动拥有了事务,可以加 @Transactional 来覆盖全局的配置。