官方文档:docs.spring.io/spring-fram...).
推荐阅读:www.marcobehler.com/guides/spri...
Spring 事务的原理
@Transactional 注解的底层实现使用的是 JDBC
事务实现的,所以如果需要使用 @Transactional 的话那么就需要使用支持的数据库,比如 MySQL 的 innodb 就至此事务,下面是简化代码:
java
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 的阐述
比如
java
@Transactional(propagation=TransactionDefinition.NESTED,
isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
对应的就是 JDBC 的 savePoint 和 isolate
java
import java.sql.Connection;
// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1)
// propagation=TransactionDefinition.NESTED
Savepoint savePoint = connection.setSavepoint(); // (2)
...
connection.rollback(savePoint);
- Spring @Transactional 对应的 isolation 也是设置的 JDBC 实现的
- 嵌入式事务的实现是通过设置 savePoint
Spring 的事务管理如何工作
我们需要明白一点就是 Spring 的 @Transactional 不是洪水猛兽,我们不需要太过于害怕。反正就是 JDBC 事务的 start 、commit 、rollback 就是这些操作。
但是问题出在,使用普通 JDBC,只有一种方法 (setAutocommit(false)) 来管理事务,而 Spring 为您提供了许多不同的、更方便的方法来实现相同的目的。
如何实现的?我们后面会慢慢来说!
如何使用 Spring 编程式事务 ?
java
@Service
public class UserService {
@Autowired
private 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 id
return id;
});
}
}
和普通 JDBC 事务进行对比:
- 不需要手动创建、关闭资源
- 不需要捕获 SQLExceptions ,Spring 会把 SQLExceptions 转换成 RuntimExceptions
- 更符合 Spring 生态,使用 @Autowired
使用编程式事务有一个好处就是,我们可以控制事务的最小粒度增加执行的效率!
使用 xml 配置 Spring 事务
之前这种配置很常见,但是现在注解式编程更加常见'
xml
<!-- 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 切面
xml
<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 代码
java
public class UserService {
public Long registerUser(User user) {
// execute some SQL that e.g.
// inserts the user into the db and retrieves the autogenerated id
return id;
}
}
使用注解配置 Spring 事务
java
@Service
public class UserService {
@Transactional
public 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 会自动帮我们配置)
java@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 生效的代码:
java
@Configuration
@EnableTransactionManagement
public class MySpringConfig {
@Bean
public PlatformTransactionManager txManager() {
return yourTxManager; // more on that later
}
}
我们根据上文提到的 JDBC 方面的知识,我们可以猜到底层的实现:
java
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 配置:
java
@Bean
public DataSource dataSource() {
return new MysqlDataSource(); // (1)
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource()); // (2)
}
- 创建一个特定于数据库或连接池的数据源。本示例中使用MySQL
- 创建你的事务管理器,它需要一个数据源来管理事务
下面就是一个简化版本的 transaction class doBegin 方法作用是开启事务, doCommit 方法就是提交事务
java
public class DataSourceTransactionManager implements PlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
Connection newCon = obtainDataSource().getConnection();
// ...
con.setAutoCommit(false);
// 是的就这些,没其他的了!
}
@Override
protected 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 ...
物理事务和逻辑事务有什么区别?
java
@Service
public class UserService {
@Autowired
private InvoiceService invoiceService;
@Transactional
public void invoice() {
invoiceService.createPdf();
// send invoice as email, etc.
}
}
@Service
public class InvoiceService {
@Transactional
public void createPdf() {
// ...
}
}
UserService 有一个事务性 Invoice() 方法。它调用 InvoiceService 上的另一个事务方法 createPdf()。
现在数据库只有一个 数据库事务. (就是 getConnection(). setAutocommit(false). commit())Spring 称这个叫做物理事务
但是在对于 Spring 来说有发生了两个逻辑事务:第一个在 UserService 中,另一个在 InvoiceService 中。 Spring 非常智能,知道这两个 @Transactional 方法应该使用相同的物理事务之中
但是当我做出如下的修改:
java
@Service
public class InvoiceService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
那么物理事务就是两个了( getConnection() x2. setAutocommit(false) x2. commit() x2),同时逻辑事务也是两个
@Transactional 传播级别
首先介绍一下 @Transactional 的传播级别
java
@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 隔离级别
当我们进行下面配置
java
@Transactional(isolation = Isolation.REPEATABLE_READ)
底层其实是这样的:
java
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
还要注意,在事务过程中切换隔离级别时,必须确保 JDBC 驱动程序/数据库,了解支持的情况和不支持的情况
最常见的 @Transactional 坑
java
@Service
public class UserService {
@Transactional
public 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() 方法