一、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);
        }
    }

注意事项

  1. 包路径隔离:确保实体类和Repository接口按数据源分开存放。

  2. 事务管理器:调用数据库操作时需指定对应的事务管理器。

  3. 排除自动配置:若启动类扫描到默认数据源,可排除 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 动态代理 实现事务的声明式管理:

  1. 代理对象创建

    • 若类或方法标记了 @Transactional,Spring 会为该 Bean 创建代理(JDK 动态代理或 CGLIB 代理)。

    • 代理对象会拦截目标方法,在方法执行前后插入事务逻辑。

  2. 事务拦截器(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)。


常见问题

  1. 自调用失效
    同一类中非事务方法调用 @Transactional 方法会失效(代理对象无法拦截内部调用)。
    解决方案:通过 AOP 代理对象调用(如注入自身 Bean 或使用 AopContext.currentProxy())。

  2. 事务传播不生效
    确保方法是通过代理对象调用的(如 @Transactional 方法必须是 public)。


总结

Spring Boot 事务的底层实现本质是 AOP 代理 + 事务管理器 + 数据库事务 的协作:

  1. 通过动态代理拦截 @Transactional 方法。

  2. 由 PlatformTransactionManager 管理事务生命周期。

  3. 最终由数据库驱动实现事务的 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) 分类记忆法

将传播行为分为 “依赖事务” 和 “独立事务” 两类:

类别 传播机制 行为特点
依赖事务 REQUIREDSUPPORTSMANDATORY 依赖当前事务的存在与否决定行为
独立事务 REQUIRES_NEWNOT_SUPPORTEDNEVER 强制独立于当前事务(新建或非事务执行)
特殊嵌套 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)

最佳实践

  1. REQUIRED:默认选择,适用于大多数业务场景。

  2. REQUIRES_NEW:用于需要独立提交的操作(如日志记录)。

  3. NESTED:需要部分回滚时使用(需数据库支持 Savepoint)。

  4. 谨慎使用 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 默认自动启用,但需检查是否被覆盖)。

排查方法

  • 确认配置类未禁用事务管理。


事务失效排查流程

  1. 检查日志

    • 开启 Spring 事务日志:

      properties

      复制

      logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
    • 观察事务是否正常开启、提交或回滚。

  2. 验证代理机制

    • 打印 this.getClass().getName(),确认是否为代理对象。

  3. 检查异常处理

    • 确保异常未被捕获或正确处理。

  4. 数据库引擎检查

    • 确认表引擎为 InnoDB。

  5. 传播行为验证

    • 检查 @Transactional(propagation=...) 配置。

  6. 多数据源配置

    • 确认事务管理器名称与数据源匹配。


通过上述步骤,可以系统性地定位并解决 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

通用优化建议

  1. 合理配置连接数

    • max-active(最大连接数)根据并发量和数据库负载调整(通常建议 CPU核心数 * 2 + 有效磁盘数)。

    • min-idle(最小空闲连接)避免频繁创建连接的开销。

  2. 启用连接泄漏检测

    properties

    复制

    # HikariCP
    spring.datasource.hikari.leak-detection-threshold=5000
    # Druid
    spring.datasource.druid.remove-abandoned=true
    spring.datasource.druid.remove-abandoned-timeout=60
  3. **监控与

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐