Springboot事务篇
通过。
一、DataSource 多数据源配置
在Spring Boot中配置多数据源需要手动定义多个数据源、实体管理器和事务管理器。以下是详细步骤:
1. 添加依赖
确保项目中包含JDBC和JPA依赖:
xml
复制
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!-- 根据数据库类型调整 --> </dependency>
运行 HTML
2. 配置多数据源参数
在 application.properties
中配置两个数据源:
properties
复制
# 主数据源(默认) spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1 spring.datasource.primary.username=root spring.datasource.primary.password=123456 spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver # 第二数据源 spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2 spring.datasource.secondary.username=root spring.datasource.secondary.password=123456 spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver # JPA通用配置(可选) spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
3. 主数据源配置
使用 @Primary
注解标记主数据源:
java
复制
@Configuration @EnableTransactionManagement @EnableJpaRepositories( basePackages = "com.example.repository.primary", // 主数据源Repository包路径 entityManagerFactoryRef = "primaryEntityManagerFactory", transactionManagerRef = "primaryTransactionManager" ) public class PrimaryDataSourceConfig { @Primary @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean(name = "primaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.example.model.primary") // 主数据源实体类包路径 .persistenceUnit("primaryPU") .properties(jpaProperties()) .build(); } @Primary @Bean(name = "primaryTransactionManager") public PlatformTransactionManager primaryTransactionManager( @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } private Map<String, Object> jpaProperties() { Map<String, Object> props = new HashMap<>(); props.put("hibernate.hbm2ddl.auto", "update"); props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); return props; } }
4. 第二数据源配置
配置第二个数据源(无需 @Primary
):
java
复制
@Configuration @EnableTransactionManagement @EnableJpaRepositories( basePackages = "com.example.repository.secondary", // 第二数据源Repository包路径 entityManagerFactoryRef = "secondaryEntityManagerFactory", transactionManagerRef = "secondaryTransactionManager" ) public class SecondaryDataSourceConfig { @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryEntityManagerFactory") public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("secondaryDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.example.model.secondary") // 第二数据源实体类包路径 .persistenceUnit("secondaryPU") .properties(jpaProperties()) .build(); } @Bean(name = "secondaryTransactionManager") public PlatformTransactionManager secondaryTransactionManager( @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } private Map<String, Object> jpaProperties() { Map<String, Object> props = new HashMap<>(); props.put("hibernate.hbm2ddl.auto", "update"); props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); return props; } }
5. 使用数据源
-
实体类:将不同数据源的实体类分别放在
com.example.model.primary
和com.example.model.secondary
包下。 -
Repository接口:将对应的Repository放在
com.example.repository.primary
和com.example.repository.secondary
包下。 -
事务管理:在Service层通过
@Transactional
指定事务管理器:java
复制
@Service public class PrimaryService { @Autowired private PrimaryRepository primaryRepository; @Transactional("primaryTransactionManager") // 指定事务管理器 public void savePrimaryEntity(PrimaryEntity entity) { primaryRepository.save(entity); } } @Service public class SecondaryService { @Autowired private SecondaryRepository secondaryRepository; @Transactional("secondaryTransactionManager") // 指定事务管理器 public void saveSecondaryEntity(SecondaryEntity entity) { secondaryRepository.save(entity); } }
注意事项
-
包路径隔离:确保实体类和Repository接口按数据源分开存放。
-
事务管理器:调用数据库操作时需指定对应的事务管理器。
-
排除自动配置:若启动类扫描到默认数据源,可排除
DataSourceAutoConfiguration
:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
通过以上步骤,即可在Spring Boot中实现多数据源的配置与使用。
二、Springboot事务底层如何实现
Spring Boot 的事务管理基于 Spring Framework 的事务抽象层,底层实现主要依赖 AOP(面向切面编程)、事务管理器(TransactionManager) 和 数据库的事务支持。以下是其核心实现细节:
1. 事务抽象层(PlatformTransactionManager
)
Spring 通过 PlatformTransactionManager
接口统一事务操作,屏蔽底层具体实现(如 JDBC、JPA、JTA 等)。
关键方法:
-
getTransaction()
:开启事务(基于传播行为决定是否创建新事务)。 -
commit()
:提交事务。 -
rollback()
:回滚事务。
常见实现类:
-
DataSourceTransactionManager
:用于 JDBC 或 MyBatis 等基于DataSource
的事务。 -
JpaTransactionManager
:用于 JPA(Hibernate)事务。 -
JtaTransactionManager
:用于分布式事务(如 Atomikos)。
Spring Boot 自动配置:
当检测到 DataSource
存在时,Spring Boot 默认自动配置 DataSourceTransactionManager
。
2. 声明式事务(@Transactional
)
通过 AOP 动态代理 实现事务的声明式管理:
-
代理对象创建:
-
若类或方法标记了
@Transactional
,Spring 会为该 Bean 创建代理(JDK 动态代理或 CGLIB 代理)。 -
代理对象会拦截目标方法,在方法执行前后插入事务逻辑。
-
-
事务拦截器(
TransactionInterceptor
):-
代理对象调用
TransactionInterceptor
,根据@Transactional
的配置(如传播行为、隔离级别)执行事务操作。 -
代码逻辑流程:
java
复制
try { // 1. 开启事务(PlatformTransactionManager.getTransaction()) // 2. 执行目标方法 // 3. 提交事务(PlatformTransactionManager.commit()) } catch (Exception e) { // 4. 回滚事务(PlatformTransactionManager.rollback()) }
-
3. 事务传播行为(Propagation)
定义事务边界如何与现有事务交互,例如:
-
REQUIRED
:如果当前存在事务,则加入;否则新建事务。 -
REQUIRES_NEW
:始终新建事务,挂起当前事务(如果存在)。 -
NESTED
:在嵌套事务中执行(依赖数据库的 Savepoint 机制)。
底层实现:
-
通过
TransactionManager
和TransactionStatus
管理事务的生命周期。 -
例如,
REQUIRES_NEW
会挂起当前事务,创建一个新事务,并在结束后恢复原事务。
4. 事务同步与资源绑定
-
事务同步(Transaction Synchronization):
Spring 通过TransactionSynchronizationManager
将事务资源(如数据库连接)绑定到当前线程。-
例如,
DataSourceUtils.getConnection()
会从当前事务中获取连接,而非直接调用DataSource.getConnection()
。
-
-
资源绑定:
-
事务开启时,数据库连接会被绑定到当前线程。
-
同一事务中的所有数据库操作共享同一个连接,确保原子性。
-
5. 事务隔离级别与锁
-
隔离级别:
通过@Transactional(isolation = Isolation.READ_COMMITTED)
设置,底层依赖数据库的隔离级别实现。-
例如,
READ_COMMITTED
对应数据库的读已提交。
-
-
锁机制:
通过 SQL 语句(如SELECT ... FOR UPDATE
)或 JPA 的@Lock
注解实现悲观锁。
6. 回滚规则
-
默认回滚策略:
遇到RuntimeException
或Error
时回滚,Checked Exception
不回滚。 -
自定义回滚:
通过@Transactional(rollbackFor = CustomException.class)
指定回滚的异常类型。
7. 底层技术实现
JDBC 事务示例:
java
复制
Connection conn = DataSourceUtils.getConnection(dataSource); try { conn.setAutoCommit(false); // 开启事务 // 执行 SQL 操作 conn.commit(); } catch (SQLException e) { conn.rollback(); } finally { DataSourceUtils.releaseConnection(conn, dataSource); }
Spring 的 DataSourceTransactionManager
封装了此逻辑。
JPA/Hibernate 事务:
-
通过
EntityManager
管理事务,底层调用EntityTransaction
的begin()
、commit()
和rollback()
。
8. 调试与日志
-
开启事务日志:
在application.properties
中添加:properties
复制
logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
-
观察代理对象:
通过调试查看 Bean 是否是Proxy
类型(如JdkDynamicAopProxy
)。
常见问题
-
自调用失效:
同一类中非事务方法调用@Transactional
方法会失效(代理对象无法拦截内部调用)。
解决方案:通过 AOP 代理对象调用(如注入自身 Bean 或使用AopContext.currentProxy()
)。 -
事务传播不生效:
确保方法是通过代理对象调用的(如@Transactional
方法必须是public
)。
总结
Spring Boot 事务的底层实现本质是 AOP 代理 + 事务管理器 + 数据库事务 的协作:
-
通过动态代理拦截
@Transactional
方法。 -
由
PlatformTransactionManager
管理事务生命周期。 -
最终由数据库驱动实现事务的 ACID 特性。
三、Springboot事务传播机制汇总
Spring Boot 的事务传播机制是 Spring 事务管理的核心功能之一,它定义了多个事务方法相互调用时事务如何传播。以下是 7 种传播机制的详细说明及记忆技巧,帮助你快速掌握。
1. 事务传播机制详解
(1) REQUIRED
(默认)
-
行为:
-
如果当前存在事务,则加入该事务;
-
如果当前没有事务,则新建一个事务。
-
-
场景:适用于大多数业务方法(如订单和库存操作需在同一个事务中)。
-
记忆关键词:“有则加入,无则新建”
(2) SUPPORTS
-
行为:
-
如果当前存在事务,则加入该事务;
-
如果当前没有事务,则以非事务方式运行。
-
-
场景:适用于查询方法(有事务时保证一致性,无事务时直接执行)。
-
记忆关键词:“随波逐流”
(3) MANDATORY
-
行为:
-
如果当前存在事务,则加入该事务;
-
如果当前没有事务,则抛出异常。
-
-
场景:强制要求方法必须在事务中调用(如核心业务方法)。
-
记忆关键词:“必须有人带我玩”
(4) REQUIRES_NEW
-
行为:
-
无论当前是否存在事务,都新建一个事务;
-
如果当前有事务,则挂起原事务。
-
-
场景:需要独立事务的操作(如日志记录,即使主事务回滚,日志仍需提交)。
-
记忆关键词:“另起炉灶,不受干扰”
(5) NOT_SUPPORTED
-
行为:
-
以非事务方式运行;
-
如果当前存在事务,则挂起原事务。
-
-
场景:强制方法以非事务方式执行(如某些耗时操作)。
-
记忆关键词:“拒绝参与,独自行动”
(6) NEVER
-
行为:
-
以非事务方式运行;
-
如果当前存在事务,则抛出异常。
-
-
场景:确保方法不在事务中调用(如某些校验方法)。
-
记忆关键词:“禁止事务,否则翻脸”
(7) NESTED
-
行为:
-
如果当前存在事务,则在嵌套事务(Savepoint)中执行;
-
如果当前没有事务,则新建一个事务(类似
REQUIRED
)。
-
-
场景:部分操作需要独立回滚(如订单主表成功,子表失败时仅回滚子表)。
-
记忆关键词:“嵌套执行,局部回滚”
2. 记忆技巧
(1) 分类记忆法
将传播行为分为 “依赖事务” 和 “独立事务” 两类:
类别 | 传播机制 | 行为特点 |
---|---|---|
依赖事务 | REQUIRED 、SUPPORTS 、MANDATORY |
依赖当前事务的存在与否决定行为 |
独立事务 | REQUIRES_NEW 、NOT_SUPPORTED 、NEVER |
强制独立于当前事务(新建或非事务执行) |
特殊嵌套 | NESTED |
嵌套事务,支持局部回滚 |
(2) 关键词联想
-
REQUIRED
→ “必须的”:没有事务就新建,有就加入。 -
REQUIRES_NEW
→ “新开窗口”:无论当前如何,都开新事务。 -
NESTED
→ “套娃”:事务里再开一个子事务,可以部分回滚。 -
NEVER
→ “绝不”:禁止在事务中调用,否则报错。
(3) 场景类比
想象事务传播机制像 “排队办理业务”:
-
REQUIRED
:如果已经在排队,就一起办;如果没人排队,自己新开一个窗口。 -
REQUIRES_NEW
:无论是否有人排队,自己新开一个窗口,原窗口暂停。 -
NESTED
:在同一个窗口办理,但某些步骤可以单独撤销(比如填表错误只需重填,不用全部重来)。
3. 对比表格
传播机制 | 当前有事务 | 当前无事务 |
---|---|---|
REQUIRED |
加入当前事务 | 新建事务 |
SUPPORTS |
加入当前事务 | 非事务执行 |
MANDATORY |
加入当前事务 | 抛出异常 |
REQUIRES_NEW |
挂起当前事务,新建独立事务 | 新建事务 |
NOT_SUPPORTED |
挂起当前事务,非事务执行 | 非事务执行 |
NEVER |
抛出异常 | 非事务执行 |
NESTED |
嵌套事务(Savepoint) | 新建事务(类似 REQUIRED ) |
4. 常见问题
(1) REQUIRED
vs NESTED
-
REQUIRED
:父子方法共享同一个事务,任一失败会导致全部回滚。 -
NESTED
:子方法在嵌套事务中执行,子事务回滚不影响父事务(父事务可选择是否回滚)。
(2) REQUIRES_NEW
的性能问题
频繁新建事务会增加数据库连接开销,需谨慎使用。
(3) 自调用失效
同一个类中非事务方法调用 @Transactional
方法会失效(需通过代理对象调用)。
5. 总结
-
核心逻辑:事务传播机制本质是定义事务方法之间的调用规则。
-
记忆口诀:
text
复制
REQUIRED 有则加,无则新建不用怕; REQUIRES_NEW 开新路,原事务先挂住; NESTED 嵌套可回滚,局部失败不耽误; NEVER 见事务就翻脸,MANDATORY 必须有依靠; SUPPORTS 随大流,NOT_SUPPORTED 不掺和。
在 Spring 的事务传播机制中,子事务的失败是否影响父事务取决于具体的事务传播行为。以下是每种传播类型下子事务失败时父事务的行为分析:
1. REQUIRED
(默认)
-
父子事务关系:子事务与父事务共享同一个物理事务(同一数据库连接)。
-
子事务失败时父事务的行为:
父事务会整体回滚(因为共享同一个事务)。 -
示例:
java
复制
@Transactional(propagation = Propagation.REQUIRED) public void parentMethod() { // 父事务操作 childMethod(); // 子事务(REQUIRED) } @Transactional(propagation = Propagation.REQUIRED) public void childMethod() { // 子事务操作(失败,抛异常) }
结果:父事务和子事务一起回滚。
2. SUPPORTS
-
父子事务关系:
-
若父事务存在,子事务加入父事务(共享同一事务);
-
若父事务不存在,子事务以非事务方式运行。
-
-
子事务失败时父事务的行为:
-
若父事务存在:父事务整体回滚(同一事务)。
-
若父事务不存在:子事务的非事务操作无法回滚(数据可能不一致)。
-
-
示例:
java
复制
public void parentMethod() { // 无事务 childMethod(); // 子事务(SUPPORTS) } @Transactional(propagation = Propagation.SUPPORTS) public void childMethod() { // 子事务操作(失败,抛异常) }
结果:子事务以非事务执行,失败后无法回滚。
3. MANDATORY
-
父子事务关系:子事务强制依赖父事务,父事务必须存在。
-
子事务失败时父事务的行为:
父事务整体回滚(子事务与父事务共享同一事务)。 -
示例:
java
复制
@Transactional(propagation = Propagation.REQUIRED) public void parentMethod() { // 父事务操作 childMethod(); // 子事务(MANDATORY) } @Transactional(propagation = Propagation.MANDATORY) public void childMethod() { // 子事务操作(失败,抛异常) }
结果:父事务和子事务一起回滚。
4. REQUIRES_NEW
-
父子事务关系:子事务新建独立事务,父事务被挂起。
-
子事务失败时父事务的行为:
父事务不受影响(子事务独立提交/回滚)。
但:若父事务未捕获子事务抛出的异常,父事务也会回滚(外层事务标记为回滚)。 -
示例:
java
复制
@Transactional(propagation = Propagation.REQUIRED) public void parentMethod() { // 父事务操作 try { childMethod(); // 子事务(REQUIRES_NEW) } catch (Exception e) { // 捕获子事务异常 } // 父事务继续执行 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void childMethod() { // 子事务操作(失败,抛异常) }
结果:
-
若父事务捕获异常:子事务独立回滚,父事务继续执行。
-
若未捕获异常:父事务因异常回滚。
-
5. NOT_SUPPORTED
-
父子事务关系:子事务以非事务方式运行,父事务被挂起。
-
子事务失败时父事务的行为:
父事务不受影响(子事务非事务执行,无法回滚)。
但:子事务的非事务操作可能破坏数据一致性。 -
示例:
java
复制
@Transactional(propagation = Propagation.REQUIRED) public void parentMethod() { // 父事务操作 childMethod(); // 子事务(NOT_SUPPORTED) } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void childMethod() { // 子事务操作(失败,但无事务回滚) }
结果:子事务的非事务操作失败后无法回滚,父事务继续提交。
6. NEVER
-
父子事务关系:子事务强制以非事务方式运行,且父事务必须不存在。
-
子事务失败时父事务的行为:
-
若父事务存在:直接抛出
IllegalTransactionStateException
(无法执行子事务)。 -
若父事务不存在:子事务以非事务运行,失败后无法回滚。
-
-
示例:
java
复制
public void parentMethod() { // 无事务 childMethod(); // 子事务(NEVER) } @Transactional(propagation = Propagation.NEVER) public void childMethod() { // 子事务操作(失败,无事务回滚) }
结果:子事务的非事务操作失败后无法回滚。
7. NESTED
-
父子事务关系:子事务是父事务的嵌套事务(基于数据库 Savepoint 机制)。
-
子事务失败时父事务的行为:
父事务可选择回滚到 Savepoint 前(子事务独立回滚),父事务可继续执行其他操作。
若父事务未捕获异常:整个事务(包括父事务)回滚。 -
示例:
java
复制
@Transactional(propagation = Propagation.REQUIRED) public void parentMethod() { // 父事务操作 try { childMethod(); // 子事务(NESTED) } catch (Exception e) { // 捕获子事务异常,继续执行其他操作 } } @Transactional(propagation = Propagation.NESTED) public void childMethod() { // 子事务操作(失败,抛异常) }
结果:
-
若父事务捕获异常:子事务回滚到 Savepoint,父事务继续执行。
-
若未捕获异常:父事务整体回滚。
-
总结
传播类型 | 子事务失败时父事务行为 | 关键特点 |
---|---|---|
REQUIRED |
父事务整体回滚 | 共享同一事务 |
SUPPORTS |
父事务存在时回滚;父事务不存在时无回滚 | 依赖当前事务状态 |
MANDATORY |
父事务整体回滚 | 强制要求父事务存在 |
REQUIRES_NEW |
父事务默认回滚(若未捕获异常);否则继续执行 | 独立事务,父事务挂起 |
NOT_SUPPORTED |
父事务继续执行 | 子事务非事务运行 |
NEVER |
父事务存在时报错;父事务不存在时无回滚 | 强制无事务环境 |
NESTED |
父事务可选择回滚到 Savepoint 前或整体回滚 | 嵌套事务(依赖数据库 Savepoint) |
最佳实践
-
REQUIRED
:默认选择,适用于大多数业务场景。 -
REQUIRES_NEW
:用于需要独立提交的操作(如日志记录)。 -
NESTED
:需要部分回滚时使用(需数据库支持 Savepoint)。 -
谨慎使用
NOT_SUPPORTED
/NEVER
:非事务操作可能导致数据不一致。
四、事务失效场景及排查方法
在 Spring 中,事务失效的常见场景及解决方案如下:
---
### **1. 方法修饰符非 `public`**
- **原因**:
Spring 的 AOP 代理默认只能拦截 `public` 方法,非 `public` 方法上的 `@Transactional` 注解会被忽略。
- **示例**:
```java
@Transactional
private void saveData() { // 非 public 方法,事务失效
// ...
}
```
- **解决**:
将方法改为 `public`,或使用 AspectJ 模式增强代理(需额外配置)。
---
### **2. 自调用问题(同类方法调用)**
- **原因**:
同一类中,方法 A 调用方法 B(B 有 `@Transactional`),此时调用的是目标对象的方法,而非代理对象,导致事务未生效。
- **示例**:
```java
@Service
public class UserService {
public void methodA() {
methodB(); // 自调用,事务失效
}
@Transactional
public void methodB() {
// ...
}
}
```
- **解决**:
通过依赖注入代理对象(如注入 `UserService` 自身)或拆分到不同类中调用。
---
### **3. 异常被捕获未抛出**
- **原因**:
Spring 默认在抛出 `RuntimeException` 或 `Error` 时回滚事务。若捕获异常未抛出,或抛出检查型异常(如 `Exception`),事务不会回滚。
- **示例**:
```java
@Transactional
public void update() {
try {
// 可能抛出 SQLException(检查型异常)
} catch (Exception e) {
// 未抛出异常,事务提交
}
}
```
- **解决**:
- 抛出 `RuntimeException` 或在注解中指定 `rollbackFor`:
```java
@Transactional(rollbackFor = Exception.class)
```
- 避免在事务方法内捕获异常后“吞掉”。
---
### **4. 数据库引擎不支持事务**
- **原因**:
如 MySQL 使用 MyISAM 引擎(不支持事务),需切换为 InnoDB。
- **检查**:
```sql
SHOW TABLE STATUS LIKE 'table_name';
```
- **解决**:
修改表引擎为 InnoDB:
```sql
ALTER TABLE table_name ENGINE=InnoDB;
```
---
### **5. 未启用事务管理**
- **原因**:
Spring Boot 未自动配置事务管理器(通常因依赖缺失或配置错误)。
- **解决**:
- 确保引入 `spring-boot-starter-data-jpa` 或 `spring-boot-starter-jdbc`。
- 检查是否添加 `@EnableTransactionManagement`(Spring Boot 默认已启用)。
---
### **6. 传播机制配置错误**
- **原因**:
使用 `NOT_SUPPORTED`、`NEVER` 等传播行为会强制方法以非事务运行。
- **示例**:
```java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void doSomething() { // 强制非事务执行
// ...
}
```
- **解决**:
根据业务需求选择正确的传播行为(如 `REQUIRED`)。
---
### **7. 多数据源未指定事务管理器**
- **原因**:
多数据源场景下,未通过 `@Transactional(value = "txManagerName")` 指定事务管理器。
- **解决**:
显式指定事务管理器名称:
```java
@Transactional(value = "secondaryTxManager")
public void save() {
// 使用 secondary 数据源的事务
}
```
---
### **8. 静态方法或 `final` 方法**
- **原因**:
Spring AOP 无法代理静态方法或 `final` 方法。
- **示例**:
```java
@Transactional
public static void staticMethod() { // 事务失效
// ...
}
```
- **解决**:
避免在静态方法或 `final` 方法上使用 `@Transactional`。
---
### **9. 事务方法内使用 `try-catch` 未回滚**
- **原因**:
即使抛出异常,若未在 `catch` 中标记回滚,事务可能提交。
- **示例**:
```java
@Transactional
public void update() {
try {
// 抛出异常
} catch (Exception e) {
// 未标记回滚,事务提交
}
}
```
- **解决**:
在 `catch` 中手动回滚:
```java
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
```
---
### **10. 事务超时或只读配置冲突**
- **原因**:
若配置 `@Transactional(timeout = 10)` 但数据库操作超时,事务可能提前终止。
- **解决**:
确保超时时间合理,或移除不必要的事务属性配置。
---
### **如何验证事务是否生效?**
1. **查看日志**:
开启 Spring 事务日志:
```properties
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.transaction=TRACE
```
2. **插入后回滚**:
在方法中插入数据后手动抛出异常,观察数据是否回滚。
3. **调试断点**:
在 `TransactionInterceptor` 中打断点,确认事务是否被拦截。
---
### **总结**
事务失效的核心原因通常涉及 **代理机制失效**、**异常处理不当** 或 **配置错误**。排查时建议:
1. 检查方法修饰符和调用方式。
2. 确保异常正确抛出。
3. 验证数据库引擎和事务配置。
4. 通过日志跟踪事务生命周期。
在 Spring Boot 中,事务失效可能由多种原因引起。以下是常见的事务失效场景及对应的排查方法:
1. 事务方法未被 Spring 代理
场景:
-
未将类标记为
@Service
、@Component
等,导致未被 Spring 管理。 -
事务方法所在的类未被 Spring 代理(如未启用 AOP)。
-
事务方法为非
public
方法(Spring 默认只代理public
方法)。
排查方法:
-
检查类是否被
@Service
、@Component
等注解标记。 -
在方法内打印
this.getClass().getName()
,确认是否为代理类(如$$EnhancerBySpringCGLIB$$
)。 -
确保事务方法为
public
类型。
2. 自调用(Self-Invocation)问题
场景:
-
在同一个类中,一个非事务方法调用本类的
@Transactional
方法。 -
原因:事务基于 AOP 代理实现,自调用不走代理对象。
示例:
java
复制
public class UserService { public void methodA() { methodB(); // 自调用,事务失效 } @Transactional public void methodB() { // 操作数据库 } }
解决方案:
-
将
methodB
移到另一个类中。 -
通过
AopContext.currentProxy()
获取代理对象调用:java
复制
((UserService) AopContext.currentProxy()).methodB();
3. 异常未被正确抛出
场景:
-
默认情况下,事务只在抛出
RuntimeException
和Error
时回滚。 -
若捕获了异常未重新抛出,或抛出了检查型异常(如
Exception
),事务不会回滚。
示例:
java
复制
@Transactional public void saveData() { try { // 数据库操作 } catch (Exception e) { // 未抛出异常,事务不会回滚 } }
解决方案:
-
确保异常被抛出,或在
@Transactional
中指定rollbackFor
:java
复制
@Transactional(rollbackFor = Exception.class)
-
避免在事务方法内“吞没”异常。
4. 数据库引擎不支持事务
场景:
-
使用 MySQL 的 MyISAM 引擎(不支持事务),而非 InnoDB。
排查方法:
-
检查数据库表的引擎类型:
sql
复制
SHOW TABLE STATUS LIKE 'table_name';
-
配置 Spring Boot 数据源时指定默认引擎(可选)。
5. 事务传播行为配置不当
场景:
-
错误使用
Propagation.NOT_SUPPORTED
等传播属性。 -
嵌套事务中未正确配置传播行为(如
Propagation.REQUIRES_NEW
)。
示例:
java
复制
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodA() { // 此方法不会开启事务 }
解决方案:
-
根据业务需求选择合适的传播行为(如
Propagation.REQUIRED
)。
6. 多数据源未指定事务管理器
场景:
-
项目中配置了多个数据源,但未显式指定事务管理器。
解决方案:
-
在
@Transactional
中指定transactionManager
:java
复制
@Transactional(value = "txManager1") public void methodA() { ... }
7. 手动提交或回滚干扰
场景:
-
在代码中手动调用
commit()
或rollback()
,干扰了事务管理。
解决方案:
-
避免在事务方法中手动操作
Connection
或TransactionTemplate
。
8. 未启用事务管理
场景:
-
未在启动类或配置类添加
@EnableTransactionManagement
(Spring Boot 默认自动启用,但需检查是否被覆盖)。
排查方法:
-
确认配置类未禁用事务管理。
事务失效排查流程
-
检查日志:
-
开启 Spring 事务日志:
properties
复制
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
-
观察事务是否正常开启、提交或回滚。
-
-
验证代理机制:
-
打印
this.getClass().getName()
,确认是否为代理对象。
-
-
检查异常处理:
-
确保异常未被捕获或正确处理。
-
-
数据库引擎检查:
-
确认表引擎为 InnoDB。
-
-
传播行为验证:
-
检查
@Transactional(propagation=...)
配置。
-
-
多数据源配置:
-
确认事务管理器名称与数据源匹配。
-
通过上述步骤,可以系统性地定位并解决 Spring Boot 中事务失效的问题。
五、好用的数据库连接池
以下是几种常用且高效的数据库连接池及其特点、配置方法和适用场景,帮助您根据需求选择最适合的方案:
1. HikariCP
特点:
-
性能最优:轻量级(仅130KB),无锁设计,响应速度极快。
-
Spring Boot 默认连接池(2.x及以上版本)。
-
支持连接泄漏检测、超时控制、异步关闭等。
配置示例(Spring Boot):
properties
复制
# application.properties spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 显式指定 HikariCP(默认无需配置) spring.datasource.type=com.zaxxer.hikari.HikariDataSource # 连接池参数 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=30000 spring.datasource.hikari.connection-timeout=2000
适用场景:
-
高并发、低延迟的互联网应用。
-
需要轻量级且高性能的场景。
2. Alibaba Druid
特点:
-
强大的监控功能:内置 SQL 监控、防火墙、慢查询日志。
-
支持扩展(如分库分表、加密)。
-
性能接近 HikariCP,适合需要监控的复杂场景。
配置示例(Spring Boot):
properties
复制
# application.properties spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # 连接池参数 spring.datasource.druid.max-active=20 spring.datasource.druid.min-idle=5 spring.datasource.druid.initial-size=5 # 启用监控 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.web-stat-filter.enabled=true spring.datasource.druid.stat-view-servlet.enabled=true
启用监控页面:
访问 http://localhost:8080/druid
,需配置 StatViewServlet
和 WebStatFilter
。
适用场景:
-
需要 SQL 监控、防火墙的企业级应用。
-
对安全性和可观测性要求较高的场景。
3. Apache DBCP2
特点:
-
Apache 老牌连接池,稳定性高。
-
功能全面,但性能略逊于 HikariCP 和 Druid。
配置示例:
properties
复制
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource spring.datasource.dbcp2.max-total=20 spring.datasource.dbcp2.min-idle=5
适用场景:
-
传统企业应用,依赖 Apache 生态的项目。
-
对性能要求不极端,追求稳定性的场景。
4. Tomcat JDBC Pool
特点:
-
Tomcat 内置连接池,适合内嵌 Tomcat 的 Spring Boot 应用。
-
性能较好,支持异步获取连接。
配置示例:
properties
复制
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource spring.datasource.tomcat.max-active=20 spring.datasource.tomcat.min-idle=5
适用场景:
-
使用内嵌 Tomcat 的 Spring Boot 应用。
-
需要与 Tomcat 生命周期深度集成的场景。
5. C3P0
特点:
-
老牌连接池,稳定性高,但性能较低。
-
逐渐被 HikariCP 和 Druid 取代。
配置示例:
properties
复制
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.type=com.mchange.v2.c3p0.ComboPooledDataSource spring.datasource.c3p0.max-pool-size=20 spring.datasource.c3p0.min-pool-size=5
适用场景:
-
遗留系统维护或需要兼容老版本的项目。
选型建议
场景 | 推荐连接池 |
---|---|
高性能、轻量级 | HikariCP |
需要监控和扩展功能 | Alibaba Druid |
内嵌 Tomcat 集成 | Tomcat JDBC Pool |
企业级传统应用 | Apache DBCP2 |
遗留系统维护 | C3P0 |
通用优化建议
-
合理配置连接数:
-
max-active
(最大连接数)根据并发量和数据库负载调整(通常建议CPU核心数 * 2 + 有效磁盘数
)。 -
min-idle
(最小空闲连接)避免频繁创建连接的开销。
-
-
启用连接泄漏检测:
properties
复制
# HikariCP spring.datasource.hikari.leak-detection-threshold=5000 # Druid spring.datasource.druid.remove-abandoned=true spring.datasource.druid.remove-abandoned-timeout=60
-
**监控与
更多推荐
所有评论(0)