官方文档:https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html#:~:text=The%20%40Transactional%20annotation%20is%20metadata,suspending%20any%20existing%20transaction%22).
推荐阅读:https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth
Spring 事务的原理
@Transactional 注解的底层实现使用的是 JDBC
事务实现的,所以如果需要使用 @Transactional 的话那么就需要使用支持的数据库,比如 MySQL 的 innodb 就至此事务,下面是简化代码:
import java.sql.Connection;Connection connection = dataSource.getConnection(); // (1)try (connection) {connection.setAutoCommit(false); // (2)// execute some SQL statements...connection.commit(); // (3)} catch (SQLException e) {connection.rollback(); // (4)
}
(1)你需要连接到数据库才能启动事务。DriverManager.getConnection(url, user, password) 也可以,但在大多数企业应用程序中,你会配置数据源并从中获取连接。这是在 Java 中“启动”数据库事务的唯一方式,尽管名称听起来有点奇怪。
(2)setAutoCommit(true) 确保每个单独的 SQL 语句都自动包装在自己的事务中,而 setAutoCommit(false) 则相反:你是事务的管理者,需要开始调用 commit 和其他相关方法。请注意,autoCommit 标志在连接打开的整个时间段内有效,这意味着你只需调用该方法一次,而不是重复调用。
(3)让我们提交我们的事务…
(4)或者,如果出现异常,回滚我们的更改。
这段代码虽然简单,但是确实 Spring 的 @Transactional 帮我们实现事务的核心代码
同时对应的 @Transactional 对应的注解是相关的参数都有 JDBC 的阐述
比如
@Transactional(propagation=TransactionDefinition.NESTED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
对应的就是 JDBC 的 savePoint 和 isolate
import java.sql.Connection;// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTEDconnection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1)// propagation=TransactionDefinition.NESTEDSavepoint savePoint = connection.setSavepoint(); // (2)
...
connection.rollback(savePoint);
- Spring @Transactional 对应的 isolation 也是设置的 JDBC 实现的
- 嵌入式事务的实现是通过设置 savePoint
Spring 的事务管理如何工作
我们需要明白一点就是 Spring 的 @Transactional 不是洪水猛兽,我们不需要太过于害怕。反正就是 JDBC 事务的 start 、commit 、rollback 就是这些操作。
但是问题出在,使用普通 JDBC,只有一种方法 (setAutocommit(false)) 来管理事务,而 Spring 为您提供了许多不同的、更方便的方法来实现相同的目的。
如何实现的?我们后面会慢慢来说!
如何使用 Spring 编程式事务 ?
@Service
public class UserService {@Autowiredprivate TransactionTemplate template;public Long registerUser(User user) {Long id = template.execute(status -> {// execute some SQL that e.g.// inserts the user into the db and returns the autogenerated idreturn id;});}
}
和普通 JDBC 事务进行对比:
- 不需要手动创建、关闭资源
- 不需要捕获 SQLExceptions ,Spring 会把 SQLExceptions 转换成 RuntimExceptions
- 更符合 Spring 生态,使用 @Autowired
使用编程式事务有一个好处就是,我们可以控制事务的最小粒度增加执行的效率!
使用 xml 配置 Spring 事务
之前这种配置很常见,但是现在注解式编程更加常见‘
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --><tx:advice id="txAdvice" transaction-manager="txManager"><!-- the transactional semantics... --><tx:attributes><!-- all methods starting with 'get' are read-only --><tx:method name="get*" read-only="true"/><!-- other methods use the default transaction settings (see below) --><tx:method name="*"/></tx:attributes></tx:advice>
还需要指定对应的 AOP 切面
<aop:config><aop:pointcut id="userServiceOperation" expression="execution(* x.y.service.UserService.*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation"/>
</aop:config><bean id="userService" class="x.y.service.UserService"/>
service 代码
public class UserService {public Long registerUser(User user) {// execute some SQL that e.g.// inserts the user into the db and retrieves the autogenerated idreturn id;}
}
使用注解配置 Spring 事务
@Service
public class UserService {@Transactionalpublic Long registerUser(User user) {// execute some SQL that e.g.// inserts the user into the db and retrieves the autogenerated id// userDao.save(user);return id;}
}
没有一点 xml 的配置,但是我们需要注意两点:
-
需要确保 Spring 配置类需要 @EnableTransactionManagement 注解(SpringBoot 会自动帮我们配置)
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {..... }
-
确保您在 Spring 配置中指定了事务管理器(无论如何都需要这样做)
-
然后 Spring 非常方便,用 @Transactional 注释标识的任何 bean 的公共方法都将在数据库事务内执行
所以如果需要 @Transactional 生效的代码:
@Configuration
@EnableTransactionManagement
public class MySpringConfig {@Beanpublic PlatformTransactionManager txManager() {return yourTxManager; // more on that later}}
我们根据上文提到的 JDBC 方面的知识,我们可以猜到底层的实现:
public class UserService {public Long registerUser(User user) {Connection connection = dataSource.getConnection(); // (1)try (connection) {connection.setAutoCommit(false); // (1)// execute some SQL that e.g.// inserts the user into the db and retrieves the autogenerated id// userDao.save(user); <(2)connection.commit(); // (1)} catch (SQLException e) {connection.rollback(); // (1)}}
}
CGlib & JDK Proxies
还是动态代理实现的 @Transactional 的底层,Spring 帮我们在底层实现了
当一个 bean 上使用 @Transactional 时,Spring 都会使用到动态代理。它不仅实例化 UserService,而且还实例化该 UserService 的事务代理。它通过使用 Cglib 库中的 proxy-through-subclassing 方法来实现
从上面的图片可以了解到 动态代理 帮我们做的工作
- 打开和关闭数据库连接/事务。
- 然后调用真正的 UserService
- 其他的 bean 比如 UserRestController “永远” 不会知道 TA 调用的 UseService 不是真的而是动态代理的对象
为什么需要事务管理器(如 PlatformTransactionManager)?
UserService 会动态代理,而代理会为您管理事务。但实际上并非代理本身处理所有的事务状态(打开、提交、关闭),而是代理将这些工作委托给了事务管理器
Spring 提供 PlatformTransactionManager / TransactionManager 两个接口并且提供了实现
它完全做的就是你迄今为止管理事务所做的一切,但首先,让我们看一下所需的 Spring 配置:
@Bean
public DataSource dataSource() {return new MysqlDataSource(); // (1)
}@Bean
public PlatformTransactionManager txManager() {return new DataSourceTransactionManager(dataSource()); // (2)
}
- 创建一个特定于数据库或连接池的数据源。本示例中使用MySQL
- 创建你的事务管理器,它需要一个数据源来管理事务
下面就是一个简化版本的 transaction class doBegin 方法作用是开启事务, doCommit 方法就是提交事务
public class DataSourceTransactionManager implements PlatformTransactionManager {@Overrideprotected void doBegin(Object transaction, TransactionDefinition definition) {Connection newCon = obtainDataSource().getConnection();// ...con.setAutoCommit(false);// 是的就这些,没其他的了!}@Overrideprotected void doCommit(DefaultTransactionStatus status) {// ...Connection connection = status.getTransaction().getConnectionHolder().getConnection();try {con.commit();} catch (SQLException ex) {throw new TransactionSystemException("Could not commit JDBC transaction", ex);}}
}
- 如果 Spring 检测到 bean 上的 @Transactional 注释,它会创建该 bean 的动态代理。
- 代理可以访问事务管理器,并要求它打开和关闭事务/连接。
- 事务管理器本身将做在普通 Java 部分中所做的操作,creat、rollback、commit …
物理事务和逻辑事务有什么区别?
@Service
public class UserService {@Autowiredprivate InvoiceService invoiceService;@Transactionalpublic void invoice() {invoiceService.createPdf();// send invoice as email, etc.}
}@Service
public class InvoiceService {@Transactionalpublic void createPdf() {// ...}
}
UserService 有一个事务性 Invoice() 方法。它调用 InvoiceService 上的另一个事务方法 createPdf()。
现在数据库只有一个数据库事务. (就是 getConnection(). setAutocommit(false). commit())Spring 称这个叫做物理事务
但是在对于 Spring 来说有发生了两个逻辑事务:第一个在 UserService 中,另一个在 InvoiceService 中。 Spring 非常智能,知道这两个 @Transactional 方法应该使用相同的物理事务之中
但是当我做出如下的修改:
@Service
public class InvoiceService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void createPdf() {// ...}
}
那么物理事务就是两个了( getConnection() x2. setAutocommit(false) x2. commit() x2),同时逻辑事务也是两个
@Transactional 传播级别
首先介绍一下 @Transactional 的传播级别
@Transactional(propagation = Propagation.REQUIRED)// or@Transactional(propagation = Propagation.REQUIRES_NEW)// etc
一共有一下几个传播级别:
- REQUIRED
- SUPPORTS
- MANDATORY
- REQUIRES_NEW
- NOT_SUPPORTED
- NEVER
- NESTED
我们知道 JDBC 的事务能够做到的事情,但是 Spring 到底是如何通过 JDBC 的事务实现的这么多的级别?
- REQUIRED(默认)方法需要事务,但是如果存在一个事务就使用存在的事务 → getConnection(). setAutoCommit(false). commit
- SUPPORTS 无论有没有事务都会执行 → JDBC 什么也不会做
- MANDATORY 如果没有事务就会抛出异常,有事务正常执行 → JDBC 什么也不会做
- REQUIRES_NEW 我想要一个完全的属于我自己的事务 → getConnection(). setAutocommit(false). commit().
- NOT_SUPPORTED 以非事务方式执行,如果存在当前事务,则暂停当前事务 → JDBC 什么也不做
- NEVER 以非事务执行,如果有事务就报错 → JDBC 什么也不做
- NESTED 如果有事务那么就用嵌入式的事务执行,好像有些复杂,但是其实这个是关于 savePoint → connection.setSavepoint()
@Transactional 隔离级别
当我们进行下面配置
@Transactional(isolation = Isolation.REPEATABLE_READ)
底层其实是这样的:
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
还要注意,在事务过程中切换隔离级别时,必须确保 JDBC 驱动程序/数据库,了解支持的情况和不支持的情况
最常见的 @Transactional 坑
@Service
public class UserService {@Transactionalpublic void invoice() {createPdf();// send invoice as email, etc.}@Transactional(propagation = Propagation.REQUIRES_NEW)public void createPdf() {// ...}
}
这个 UserService 里面有两个 @Transactional 标识的方法,而且其中的 creatPdf 方法的传播级别是 REQUIRES_NEW
那么现在有一个问题,这个类之中有几个物理事务 ?
答案是 1 个事务而不是 2 个物理事务!!!
为什么?
Spring 为创建事务性 UserService 代理,但是一旦您进入 UserService 类并调用其他内部方法,就不再涉及代理了!这意味着不会再有新的事务
但是这个问题也是有对应的解法
1、可以自己注入自己然后调用 @Lazy @Resource private UserService userService;
2、可以使用 ((UserService) AopContext.currentProxy()).creatPdf()
3、使用 SpringUtil 获取 bean 调用 creatPdf() 方法