“本文已经收录进 Github 75k+ Star 的Java项目JavaGuide 。JavaGuide项目地址 : github.com/Snailclimb/… 。强烈建议学习 Java的朋友看看!html
你们好,我是 Guide 哥,前段答应读者的 Spring 事务分析总结终于来了。这部份内容比较重要,不管是对于工做仍是面试,可是网上比较好的参考资料比较少。 相关阅读:V2.0 版本的 《JavaGuide面试突击版》来啦!带着它的在线阅读版原本啦!java
若是本文有任何不对或者须要完善的地方,请帮忙指出!Guide 哥感激涕零!mysql
事务是逻辑上的一组操做,要么都执行,要么都不执行。git
Guide 哥:你们应该都能背上面这句话了,下面我结合咱们平常的真实开发来谈一谈。github
咱们系统的每一个业务方法可能包括了多个原子性的数据库操做,好比下面的 savePerson()
方法中就有两个原子性的数据库操做。这些原子性的数据库操做是有依赖的,它们要么都执行,要不就都不执行。web
public void savePerson() {
personDao.save(person); personDetailDao.save(personDetail); } 复制代码
另外,须要格外注意的是:事务可否生效数据库引擎是否支持事务是关键。好比经常使用的 MySQL 数据库默认使用支持事务的innodb
引擎。可是,若是把数据库引擎变为 myisam
,那么程序也就再也不支持事务了!面试
事务最经典也常常被拿出来讲例子就是转帐了。假如小明要给小红转帐 1000 元,这个转帐会涉及到两个关键操做就是:算法
将小明的余额减小 1000 元spring
将小红的余额增长 1000 元。sql
万一在这两个操做之间忽然出现错误好比银行系统崩溃或者网络故障,致使小明余额减小而小红的余额没有增长,这样就不对了。事务就是保证这两个关键操做要么都成功,要么都要失败。
public class OrdersService {
private AccountDao accountDao; public void setOrdersDao(AccountDao accountDao) { this.accountDao = accountDao; } @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = -1) public void accountMoney() { //小红帐户多1000 accountDao.addMoney(1000,xiaohong); //模拟忽然出现的异常,好比银行中可能为忽然停电等等 //若是没有配置事务管理的话会形成,小红帐户多了1000而小明帐户没有少钱 int i = 10 / 0; //小王帐户少1000 accountDao.reduceMoney(1000,xiaoming); } } 复制代码
另外,数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。
再提醒一次:你的程序是否支持事务首先取决于数据库 ,好比使用 MySQL 的话,若是你选择的是 innodb 引擎,那么恭喜你,是能够支持事务的。可是,若是你的 MySQL 数据库使用的是 myisam 引擎的话,那很差意思,从根上就是不支持事务的。
这里再多提一下一个很是重要的知识点: MySQL 怎么保证原子性的?
咱们知道若是想要保证事务的原子性,就须要在异常发生时,对已经执行的操做进行回滚,在 MySQL 中,恢复机制是经过 回滚日志(undo log) 实现的,全部事务进行的修改都会先先记录到这个回滚日志中,而后再执行相关的操做。若是执行过程当中遇到异常的话,咱们直接利用 回滚日志 中的信息将数据回滚到修改以前的样子便可!而且,回滚日志会先于数据持久化到磁盘上。这样就保证了即便遇到数据库忽然宕机等状况,当用户再次启动数据库的时候,数据库还可以经过查询回滚日志来回滚将以前未完成的事务。
经过 TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中不多使用,可是对于你理解 Spring 事务管理原理有帮助。
使用TransactionTemplate
进行编程式事务管理的示例代码以下:
@Autowired
private TransactionTemplate transactionTemplate; public void testTransaction() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { try { // .... 业务代码 } catch (Exception e){ //回滚 transactionStatus.setRollbackOnly(); } } }); } 复制代码
使用 TransactionManager
进行编程式事务管理的示例代码以下:
@Autowired
private PlatformTransactionManager transactionManager; public void testTransaction() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // .... 业务代码 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); } } 复制代码
推荐使用(代码侵入性最小),实际是经过 AOP 实现(基于@Transactional
的全注解方式使用最多)。
使用 @Transactional
注解进行事务管理的示例代码以下:
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod { //do something B b = new B(); C c = new C(); b.bMethod(); c.cMethod(); } 复制代码
Spring 框架中,事务管理相关最重要的 3 个接口以下:
PlatformTransactionManager
: (平台)事务管理器,Spring 事务策略的核心。
TransactionDefinition
: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
TransactionStatus
: 事务运行状态。
咱们能够把 PlatformTransactionManager
接口能够被看做是事务上层的管理者,而 TransactionDefinition
和 TransactionStatus
这两个接口能够看做是事物的描述。
PlatformTransactionManager
会根据 TransactionDefinition
的定义好比事务超时时间、隔离界别、传播行为等来进行事务管理 ,而 TransactionStatus
接口则提供了一些方法来获取事务相应的状态好比是否新事务、是否能够回滚等等。
Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是: PlatformTransactionManager
。
经过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager
)、Hibernate(HibernateTransactionManager
)、JPA(JpaTransactionManager
)等都提供了对应的事务管理器,可是具体的实现就是各个平台本身的事情了。
PlatformTransactionManager
接口的具体实现以下:
PlatformTransactionManager
接口中定义了三个方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable; public interface PlatformTransactionManager { //得到事务 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; //提交事务 void commit(TransactionStatus var1) throws TransactionException; //回滚事务 void rollback(TransactionStatus var1) throws TransactionException; } 复制代码
这里多插一嘴。为何要定义或者说抽象出来PlatformTransactionManager
这个接口呢?
主要是由于要将事务管理行为抽象出来,而后不一样的平台去实现它,这样咱们能够保证提供给外部的行为不变,方便咱们扩展。我前段时间分享过:“为何咱们要用接口?”
事务管理器接口 PlatformTransactionManager
经过 getTransaction(TransactionDefinition definition)
方法来获得一个事务,这个方法里面的参数是 TransactionDefinition
类 ,这个类就定义了一些基本的事务属性。
那么什么是 事务属性 呢?
事务属性能够理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了 5 个方面:
TransactionDefinition
接口中定义了 5 个方法以及一些表示事务属性的常量好比隔离级别、传播行为等等。
package org.springframework.transaction;
import org.springframework.lang.Nullable; public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; int TIMEOUT_DEFAULT = -1; // 返回事务的传播行为,默认值为 REQUIRED。 int getPropagationBehavior(); //返回事务的隔离级别,默认值是 DEFAULT int getIsolationLevel(); // 返回事务的超时时间,默认值为-1。若是超过该时间限制但事务尚未完成,则自动回滚事务。 int getTimeout(); // 返回是否为只读事务,默认值为 false boolean isReadOnly(); @Nullable String getName(); } 复制代码
TransactionStatus
接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…)
方法返回一个 TransactionStatus
对象。
TransactionStatus 接口接口内容以下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是不是新的事物 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 } 复制代码
实际业务开发中,你们通常都是使用 @Transactional
注解来开启事务,不少人并不清楚这个参数里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在本身的事务中运行。
举个例子!
咱们在 A 类的aMethod()
方法中调用了 B 类的 bMethod()
方法。这个时候就涉及到业务层方法之间互相调用的事务问题。若是咱们的 bMethod()
若是发生异常须要回滚,如何配置事务传播行为才能让 aMethod()
也跟着回滚呢?这个时候就须要事务传播行为的知识了,若是你不知道的话必定要好好看一下。
Class A {
@Transactional(propagation=propagation.xxx) public void aMethod { //do something B b = new B(); b.bMethod(); } } Class B { @Transactional(propagation=propagation.xxx) public void bMethod { //do something } } 复制代码
在TransactionDefinition
定义中包括了以下几个表示传播行为的常量:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; ...... } 复制代码
不过如此,为了方便使用,Spring 会相应地定义了一个枚举类:Propagation
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition; public enum Propagation { REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), NEVER(TransactionDefinition.PROPAGATION_NEVER), NESTED(TransactionDefinition.PROPAGATION_NESTED); private final int value; Propagation(int value) { this.value = value; } public int value() { return this.value; } } 复制代码
正确的事务传播行为可能的值以下 :
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为,咱们平时常用的@Transactional
注解默认使用就是这个事务传播行为。若是当前存在事务,则加入该事务;若是当前没有事务,则建立一个新的事务。也就是说:
Propagation.REQUIRED
修饰的内部方法会新开启本身的事务,且开启的事务相互独立,互不干扰。
Propagation.REQUIRED
的话,全部
Propagation.REQUIRED
修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
举个例子:若是咱们上面的aMethod()
和bMethod()
使用的都是PROPAGATION_REQUIRED
传播行为的话,二者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void aMethod { //do something B b = new B(); b.bMethod(); } } Class B { @Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void bMethod { //do something } } 复制代码
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
建立一个新的事务,若是当前存在事务,则把当前事务挂起。也就是说无论外部方法是否开启事务,Propagation.REQUIRES_NEW
修饰的内部方法会新开启本身的事务,且开启的事务相互独立,互不干扰。
举个例子:若是咱们上面的bMethod()
使用PROPAGATION_REQUIRES_NEW
事务传播行为修饰,aMethod
仍是用PROPAGATION_REQUIRED
修饰的话。若是aMethod()
发生异常回滚,bMethod()
不会跟着回滚,由于 bMethod()
开启了独立的事务。可是,若是 bMethod()
抛出了未被捕获的异常而且这个异常知足事务回滚规则的话,aMethod()
一样也会回滚,由于这个异常被 aMethod()
的事务管理机制检测到了。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void aMethod { //do something B b = new B(); b.bMethod(); } } Class B { @Transactional(propagation=propagation.REQUIRES_NEW) public void bMethod { //do something } } 复制代码
3.TransactionDefinition.PROPAGATION_NESTED
:
若是当前存在事务,则建立一个事务做为当前事务的嵌套事务来运行;若是当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
。也就是说:
Propagation.NESTED
和
Propagation.REQUIRED
做用相同,修饰的内部方法都会新开启本身的事务,且开启的事务相互独立,互不干扰。
Propagation.NESTED
修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务能够单独回滚而不影响外部主事务和其余子事务。
这里仍是简单举个例子:
若是 aMethod()
回滚的话,bMethod()
和bMethod2()
都要回滚,而bMethod()
回滚的话,并不会形成 aMethod()
和bMethod()
回滚。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void aMethod { //do something B b = new B(); b.bMethod(); b.bMethod2(); } } Class B { @Transactional(propagation=propagation.PROPAGATION_NESTED) public void bMethod { //do something } @Transactional(propagation=propagation.PROPAGATION_NESTED) public void bMethod2 { //do something } } 复制代码
4.TransactionDefinition.PROPAGATION_MANDATORY
若是当前存在事务,则加入该事务;若是当前没有事务,则抛出异常。(mandatory:强制性)
这个使用的不多,就不举例子来讲了。
如果错误的配置如下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的不多。
TransactionDefinition.PROPAGATION_SUPPORTS
: 若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事务方式运行,若是当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
: 以非事务方式运行,若是当前存在事务,则抛出异常。
更多关于事务传播行为的内容请看这篇文章:《太难了~面试官让我结合案例讲讲本身对 Spring 事务传播行为的理解。》
TransactionDefinition
接口中定义了五个表示隔离级别的常量:
public interface TransactionDefinition {
...... int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; ...... } 复制代码
和事务传播行为这块同样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); private final int value; Isolation(int value) { this.value = value; } public int value() { return this.value; } } 复制代码
下面我依次对每一种事务隔离级别进行介绍:
TransactionDefinition.ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别,MySQL 默认采用的
REPEATABLE_READ
隔离级别 Oracle 默认采用的
READ_COMMITTED
隔离级别.
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔离级别,使用这个隔离级别不多,由于它容许读取还没有提交的数据变动,
可能会致使脏读、幻读或不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED
: 容许读取并发事务已经提交的数据,
能够阻止脏读,可是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ
: 对同一字段的屡次读取结果都是一致的,除非数据是被自己事务本身所修改,
能够阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE
: 最高的隔离级别,彻底服从 ACID 的隔离级别。全部的事务依次逐个执行,这样事务之间就彻底不可能产生干扰,也就是说,
该级别能够防止脏读、不可重复读以及幻读。可是这将严重影响程序的性能。一般状况下也不会用到该级别。
由于平时使用 MySQL 数据库比较多,这里再多提一嘴!
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ
(可重读)。咱们能够经过SELECT @@tx_isolation;
命令来查看,以下:
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
复制代码
这里须要注意的是:与 SQL 标准不一样的地方在于 InnoDB 存储引擎在 REPEATABLE-READ
(可重读) 事务隔离级别下使用的是 Next-Key Lock 锁算法,所以能够避免幻读的产生,这与其余数据库系统(如 SQL Server)是不一样的。因此说 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ
(可重读) 已经能够彻底保证事务的隔离性要求,即达到了 SQL 标准的 SERIALIZABLE
(可串行化) 隔离级别。
由于隔离级别越低,事务请求的锁越少,因此大部分数据库系统的隔离级别都是 READ-COMMITTED
(读取提交内容) :,可是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ
(可重读) 并不会什么任何性能上的损失。
更多关于事务隔离级别的内容请看:
所谓事务超时,就是指一个事务所容许执行的最长时间,若是超过该时间限制但事务尚未完成,则自动回滚事务。在 TransactionDefinition
中以 int 的值来表示超时时间,其单位是秒,默认值为-1。
package org.springframework.transaction;
import org.springframework.lang.Nullable; public interface TransactionDefinition { ...... // 返回是否为只读事务,默认值为 false boolean isReadOnly(); } 复制代码
对于只有读取数据查询的事务,能够指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操做的方法中。
不少人就会疑问了,为何我一个数据查询操做还要启用事务支持呢?
拿 MySQL 的 innodb 举例子,根据官网 dev.mysql.com/doc/refman/… 描述:
“MySQL 默认对每个新创建的链接都启用了
autocommit
模式。在该模式下,每个发送到 MySQL 服务器的sql
语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
可是,若是你给方法加上了Transactional
注解的话,这个方法执行的全部sql
会被放在一个事务中。若是声明了只读事务的话,数据库就会去优化它的执行,并不会带来其余的什么收益。
若是不加Transactional
,每条sql
会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
分享一下关于事务只读属性,其余人的解答:
这些规则定义了哪些异常会致使事务回滚而哪些不会。默认状况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会致使事务回滚,可是,在遇到检查型(Checked)异常时不会回滚。
若是你想要回滚你定义的特定的异常类型的话,能够这样:
@Transactional(rollbackFor= MyException.class)
复制代码
@Transactional
的做用范围@Transactional
的经常使用配置参数@Transactional
注解源码以下,里面包含了基本事务属性的配置:
@Target({ElementType.TYPE, ElementType.METHOD})
@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
的经常使用配置参数总结(只列巨额 5 个我平时比较经常使用的):
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 |
isolation | 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 |
timeout | 事务的超时时间,默认值为-1(不会超时)。若是超过该时间限制但事务尚未完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false。 |
rollbackFor | 用于指定可以触发事务回滚的异常类型,而且能够指定多个异常类型。 |
@Transactional
事务注解原理面试中在问 AOP 的时候可能会被问到的一个问题。简单说下吧!
咱们知道,@Transactional
的工做机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。若是目标对象实现了接口,默认状况下会采用 JDK 的动态代理,若是目标对象没有实现了接口,会使用 CGLIB 动态代理。
多提一嘴:createAopProxy()
方法 决定了是使用 JDK 仍是 Cglib 来作动态代理,源码以下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } } ....... } 复制代码
若是一个类或者一个类中的 public 方法上被标注@Transactional
注解的话,Spring 容器就会在启动的时候为其建立一个代理类,在调用被@Transactional
注解的 public 方法的时候,实际调用的是,TransactionInterceptor
类中的 invoke()
方法。这个方法的做用就是在目标方法以前开启事务,方法执行过程当中若是遇到异常的时候回滚事务,方法调用完成以后提交事务。
“
TransactionInterceptor
类中的invoke()
方法内部实际调用的是TransactionAspectSupport
类的invokeWithinTransaction()
方法。因为新版本的 Spring 对这部分重写很大,并且用到了不少响应式编程的知识,这里就不列源码了。
若同一类中的其余没有 @Transactional
注解的方法内部调用有 @Transactional
注解的方法,有@Transactional
注解的方法的事务会失效。
这是因为Spring AOP
代理的缘由形成的,由于只有当 @Transactional
注解的方法在类之外被调用的时候,Spring 事务管理才生效。
MyService
类中的method1()
调用method2()
就会致使method2()
的事务失效。
@Service
public class MyService { private void method1() { method2(); //...... } @Transactional public void method2() { //...... } } 复制代码
解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
@Transactional
的使用注意事项总结@Transactional
注解只有做用到 public 方法上事务才生效,不推荐在接口上使用;
@Transactional
注解的方法,这样会致使事务失效;
@Transactional
的 rollbackFor 和 propagation 属性,不然事务可能会回滚失败
本文使用 mdnice 排版