Spring 事务管理是 Spring 框架的核心功能之一,它为开发者提供了一种方便、灵活且强大的方式来管理数据库事务。
1、事务的基本概念
事务是一组不可分割的操作序列,这些操作要么全部成功执行,要么全部失败回滚,以确保数据的一致性和完整性。事务具有四个特性,即 ACID 特性:
-
原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚。
-
一致性(Consistency):事务执行前后,数据库的状态必须保持一致。
-
隔离性(Isolation):多个事务并发执行时,一个事务的执行不能被其他事务干扰。
-
持久性(Durability):事务一旦提交,其对数据库的更改将永久保存。
2、事务的传播行为
事务的传播行为定义了在嵌套事务场景下,事务如何传播。Spring 定义了 7 种事务传播行为:
-
PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
-
PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
-
PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则将当前事务挂起。
-
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
-
PROPAGATION_NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
-
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
使用 @Transactional
注解指定传播行为的示例:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transferMoney() {
// 执行数据库操作
}
}
3、事务的隔离级别
事务的隔离级别定义了一个事务对其他事务的可见性。Spring 支持 4 种事务隔离级别:
-
ISOLATION_DEFAULT:使用数据库默认的隔离级别。
-
ISOLATION_READ_UNCOMMITTED:允许读取未提交的数据,可能会导致脏读、不可重复读和幻读。
-
ISOLATION_READ_COMMITTED:只允许读取已提交的数据,可以避免脏读,但可能会导致不可重复读和幻读。
-
ISOLATION_REPEATABLE_READ:确保在同一个事务中多次读取同一数据的结果是一致的,可以避免脏读和不可重复读,但可能会导致幻读。
-
ISOLATION_SERIALIZABLE:最高的隔离级别,确保事务串行执行,可以避免脏读、不可重复读和幻读,但会影响并发性能。
使用 @Transactional
注解指定隔离级别的示例:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney() {
// 执行数据库操作
}
}
4、Spring 事务管理的实现方式
Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。
4.1 编程式事务管理
编程式事务管理需要在代码中显式地管理事务的开始、提交和回滚。Spring 提供了 TransactionTemplate
或 PlatformTransactionManager
来实现编程式事务管理。
使用 TransactionTemplate
的示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transferMoney() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 执行数据库操作
// 例如:从账户 A 转账到账户 B
} catch (Exception e) {
status.setRollbackOnly();
}
}
});
}
}
使用 PlatformTransactionManager
的示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void addUser() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
}
解释
-
PlatformTransactionManager
:Spring 提供的事务管理器接口,用于管理事务的提交、回滚等操作。 -
TransactionDefinition
:定义事务的属性,如隔离级别、传播行为等。 -
TransactionStatus
:表示当前事务的状态,通过transactionManager.getTransaction(def)
方法获取。 -
手动提交和回滚:在代码中手动调用
transactionManager.commit(status)
提交事务,调用transactionManager.rollback(status)
回滚事务。
优缺点
-
优点:
-
对事务的控制非常精细,可以根据业务逻辑在代码的任何位置灵活地开启、提交或回滚事务。
-
适用于复杂的事务处理场景,例如需要根据不同的业务条件动态决定是否提交或回滚事务。
-
-
缺点:
-
代码中会包含大量的事务管理代码,导致业务逻辑和事务管理逻辑耦合度高,增加了代码的复杂度和维护难度。
-
当业务逻辑发生变化时,可能需要同时修改事务管理代码,不符合开闭原则。
-
适用场景
-
事务逻辑复杂,需要根据业务条件动态控制事务的提交和回滚。
-
对事务管理有特殊需求,例如需要在事务中执行一些非数据库操作,并且要保证这些操作与数据库操作的原子性。
4.2 声明式事务管理
声明式事务管理是通过 AOP(面向切面编程)实现的,它允许开发者通过配置的方式来定义事务的边界,而不需要在代码中显式地管理事务。声明式事务管理有两种配置方式:基于 XML 配置和基于注解配置。
@Transactional
注解默认的事务隔离级别是 ISOLATION_DEFAULT(
使用数据库默认的隔离级别)
@Transactional
注解默认的事务传播行为是 PROPAGATION_REQUIRED(
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务)
基于注解配置的示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void transferMoney() {
// 执行数据库操作
// 例如:从账户 A 转账到账户 B
}
}
解释
-
@Transactional
注解:在方法上添加@Transactional
注解,Spring 会自动管理该方法的事务。如果方法执行过程中抛出异常,Spring 会自动回滚事务。
在配置类中开启事务注解支持:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
优缺点
-
优点:
-
事务管理代码与业务逻辑分离,降低了代码的耦合度,使业务代码更加简洁、清晰,易于维护。
-
通过配置的方式定义事务,提高了开发效率,减少了重复代码。
-
-
缺点:
-
事务的控制粒度相对较粗,不够灵活。例如,无法在方法内部的某个具体位置精确控制事务的提交和回滚。
-
由于是基于 AOP 实现的,可能会带来一定的性能开销。
-
适用场景
-
事务逻辑相对简单,且事务边界比较固定的场景。
-
大多数业务方法都需要相同的事务管理策略,通过统一的注解配置可以提高开发效率。
4.2.1 注意事项
1. 注意 @Transactional
注解位置
该注解可以用在方法上,此时仅对该方法生效。要确保该方法是 public
的,因为 Spring AOP 基于代理模式,默认情况下,只有 public
方法才会被代理增强。
若将注解放在类上,意味着该类中的所有 public
方法都会启用事务管理。不过,要注意如果类中有部分方法不需要事务,这种方式可能会带来不必要的事务开销。
2.异常处理与回滚规则
默认回滚规则:@Transactional
默认只对 RuntimeException
及其子类和 Error
进行回滚。如果业务代码中抛出的是受检异常(如 IOException
),默认情况下事务不会回滚。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
@Transactional
public void saveUser() throws IOException {
// 业务逻辑
throw new IOException("文件操作异常"); // 默认不回滚
}
}
自定义回滚规则:可通过 rollbackFor
和 noRollbackFor
属性来指定需要回滚和不需要回滚的异常类型。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
@Transactional(rollbackFor = {IOException.class})
public void saveUser() throws IOException {
// 业务逻辑
throw new IOException("文件操作异常"); // 会回滚
}
}
3. 事务传播行为和隔离级别
合理选择传播行为:传播行为定义了事务方法如何与当前事务进行交互,有 REQUIRED
、SUPPORTS
、MANDATORY
等多种传播行为。例如,REQUIRED
是最常用的传播行为,若当前存在事务则加入,不存在则创建新事务;而 SUPPORTS
表示若当前存在事务则加入,不存在则以非事务方式执行。要根据业务需求合理选择,避免不必要的事务嵌套。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void saveUser() {
// 业务逻辑
}
}
隔离级别定义了一个事务对其他事务的可见性,有 READ_UNCOMMITTED
、READ_COMMITTED
、REPEATABLE_READ
和 SERIALIZABLE
等。过高的隔离级别会增加事务的并发控制开销,要根据业务需求选择合适的隔离级别,如大多数场景使用 READ_COMMITTED
即可。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void saveUser() {
// 业务逻辑
}
}
4. 代理机制
内部方法调用问题
同一类内方法调用:Spring AOP 基于代理模式,当在同一个类的一个无事务方法中调用有 @Transactional
注解的方法时,事务不会生效。因为这种调用没有经过代理对象,而是直接调用目标对象的方法。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
public void nonTransactionalMethod() {
this.transactionalMethod(); // 事务不生效
}
@Transactional
public void transactionalMethod() {
// 业务逻辑
}
}
可以通过注入自身代理对象来解决,或者将有事务的方法提取到另一个服务类中。
5. 数据源和事务管理器配置
确保数据源与事务管理器匹配:要保证数据源和事务管理器的配置正确且相互匹配。如果使用多个数据源,需要为每个数据源配置相应的事务管理器,并在 @Transactional
注解中指定使用的事务管理器。
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional("transactionManager2")
public void saveUser() {
// 业务逻辑
}
}
6. 缩小事务范围,减少事务持有时间:尽量将不需要事务的操作放在事务之外执行,缩小事务的范围,减少事务持有锁的时间,从而提高并发性能。例如,先进行查询操作,再将需要事务的更新操作放在事务方法中。
4.2.2 @Transactional
注解失效的情况
1. 注解应用在非 public
方法上
Spring AOP 基于代理模式实现事务管理,@Transactional
注解只能应用在 public
方法上。如果应用在非 public
方法上,事务将失效。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
// 非 public 方法,事务失效
@Transactional
protected void saveUser() {
// 数据库操作
}
}
解决办法:将方法的访问修饰符改为 public
。
2. 同一类中方法调用
在同一个类中,一个方法调用另一个带有 @Transactional
注解的方法,事务会失效。这是因为 Spring AOP 是基于代理模式,同一类中的方法调用不会经过代理对象。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
public void outerMethod() {
innerMethod();
}
@Transactional
public void innerMethod() {
// 数据库操作
}
}
解决办法:可以通过注入自身代理对象来调用带有事务的方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserService self;
public void outerMethod() {
self.innerMethod();
}
@Transactional
public void innerMethod() {
// 数据库操作
}
}
3. 异常类型不匹配
@Transactional
注解默认只对 RuntimeException
及其子类和 Error
进行回滚。如果抛出的异常不是这些类型,事务不会回滚。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
@Transactional
public void saveUser() throws IOException {
// 数据库操作
throw new IOException("IO 异常");
}
}
解决办法:可以通过 rollbackFor
属性指定需要回滚的异常类型。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class)
public void saveUser() throws IOException {
// 数据库操作
throw new IOException("IO 异常");
}
}
4. 数据库不支持事务
不同的数据库对事务的支持不同,例如 MySQL 的 MyISAM 存储引擎不支持事务,即使使用 @Transactional
注解,事务也不会生效。
解决办法:将数据库表的存储引擎改为支持事务的引擎,如 MySQL 的 InnoDB。
5. 事务传播行为配置错误
@Transactional
注解的 propagation
属性用于配置事务的传播行为,如果配置不当,可能导致事务失效。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveUser() {
// 数据库操作
}
}
Propagation.NOT_SUPPORTED
表示以非事务方式执行操作,如果当前存在事务,将把当前事务挂起。
根据业务需求选择合适的事务传播行为,如 Propagation.REQUIRED
表示如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void saveUser() {
// 数据库操作
}
}
6. 没有开启 Spring 事务管理
如果没有在 Spring 配置中开启事务管理,@Transactional
注解将不会生效。
解决办法:
在 Spring Boot 项目中,在主类上添加 @EnableTransactionManagement
注解。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.3 两种方式的对比
编程式事务管理 | 声明式事务管理 | |
---|---|---|
代码耦合度 | 高,事务管理代码与业务逻辑代码紧密耦合 | 低,事务管理代码与业务逻辑代码分离 |
灵活性 | 高,可以在代码中根据具体情况灵活控制事务的开始、提交和回滚 | 相对较低,主要通过注解配置事务属性 |
可维护性 | 低,事务管理代码分散在业务逻辑代码中,维护困难 | 高,事务管理代码集中在注解中,易于维护 |
适用场景 | 适用于事务管理逻辑复杂,需要根据具体情况灵活控制事务的场景 | 适用于大多数业务场景,特别是事务管理逻辑相对简单的场景 |
综上所属,编程式事务管理适合复杂的事务处理场景,而声明式事务管理则更适合事务逻辑相对简单、需要提高开发效率和降低代码耦合度的场景。在实际开发中,可以根据具体需求选择合适的事务管理方式。