Skip to content

声明式事务管理

声明式事务管理是Spring框架提供的一种非侵入式的事务管理方式,它允许开发者通过注解或XML配置来定义事务边界和属性,而不需要在代码中显式编写事务管理逻辑。

声明式事务的优势

相比于编程式事务管理,声明式事务具有以下优势:

  1. 代码简洁: 无需编写重复的事务管理代码
  2. 关注点分离: 业务逻辑与事务管理分离
  3. 配置灵活: 可以在不修改代码的情况下调整事务属性
  4. 一致性: 为应用提供统一的事务管理方式

配置声明式事务

Spring提供了两种配置声明式事务的方式:

  1. 基于注解的配置: 使用@Transactional注解
  2. 基于XML的配置: 使用AOP配置事务通知和切点

基于注解的声明式事务

1. 启用事务注解

使用@EnableTransactionManagement注解启用事务注解支持:

java
@Configuration
@EnableTransactionManagement
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        // 配置数据源
        return new HikariDataSource();
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

或者在XML配置中启用:

xml
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd">

    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <!-- 数据源配置 -->
    </bean>
</beans>

2. 使用@Transactional注解

@Transactional注解可以应用在类级别或方法级别,方法级别的配置会覆盖类级别的配置。

java
@Service
@Transactional  // 类级别注解,应用于所有公共方法
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 继承类级别的事务配置
    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    // 覆盖类级别的事务配置
    @Override
    @Transactional(readOnly = true)
    public User findById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
    
    // 自定义事务配置
    @Override
    @Transactional(timeout = 60, rollbackFor = {DataAccessException.class, ServiceException.class})
    public void updateUserStatus(Long userId, UserStatus status) {
        User user = findById(userId);
        user.setStatus(status);
        userRepository.save(user);
    }
    
    // 禁用事务
    @Override
    @Transactional(propagation = Propagation.NEVER)
    public List<User> getAllUserNames() {
        return userRepository.findAllUserNames();
    }
}

3. @Transactional注解的属性

@Transactional注解提供了多种事务属性的配置:

java
@Transactional(
    transactionManager = "transactionManager", // 事务管理器
    propagation = Propagation.REQUIRED,        // 事务传播行为
    isolation = Isolation.DEFAULT,             // 事务隔离级别
    timeout = 30,                             // 事务超时时间(秒)
    readOnly = false,                         // 是否只读事务
    rollbackFor = {Exception.class},          // 触发回滚的异常类
    noRollbackFor = {UserNotFoundException.class} // 不触发回滚的异常类
)

基于XML的声明式事务

对于某些不便使用注解的场景,Spring也支持通过XML配置声明式事务:

xml
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 配置事务属性 -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- AOP配置 -->
    <aop:config>
        <aop:pointcut id="serviceOperation" expression="execution(* com.example.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
    </aop:config>
</beans>

事务属性详解

1. 事务传播行为

事务传播行为定义了当前方法被另一个事务方法调用时,应该如何处理事务:

传播行为描述
REQUIRED (默认)如果存在事务,则加入;否则创建新事务
SUPPORTS如果存在事务,则加入;否则以非事务方式执行
MANDATORY如果存在事务,则加入;否则抛出异常
REQUIRES_NEW创建新事务,如果存在事务,则挂起当前事务
NOT_SUPPORTED以非事务方式执行,如果存在事务,则挂起当前事务
NEVER以非事务方式执行,如果存在事务,则抛出异常
NESTED如果存在事务,则创建嵌套事务;否则创建新事务

2. 事务隔离级别

隔离级别定义了一个事务的结果对其他并发事务的可见性:

隔离级别描述
DEFAULT使用底层数据库的默认隔离级别
READ_UNCOMMITTED允许读取未提交的数据变更(脏读)
READ_COMMITTED防止脏读,允许不可重复读和幻读
REPEATABLE_READ防止脏读和不可重复读,允许幻读
SERIALIZABLE防止脏读、不可重复读和幻读,但可能导致性能下降

3. 事务超时时间

定义了事务在强制回滚前可以运行的最大时间(秒):

java
@Transactional(timeout = 30)  // 30秒超时

4. 只读事务

标记事务为只读可以让数据库优化性能:

java
@Transactional(readOnly = true)

5. 回滚规则

定义哪些异常会导致事务回滚,哪些不会:

java
@Transactional(
    rollbackFor = {CustomException.class, SQLException.class},
    noRollbackFor = {InvalidInputException.class}
)

声明式事务的原理

Spring声明式事务基于AOP实现,主要过程如下:

  1. Spring识别带有@Transactional注解的类和方法
  2. 在运行时,Spring创建这些类的代理对象
  3. 当代理对象的方法被调用时:
    • 开始事务
    • 调用目标方法
    • 如果方法正常完成,则提交事务
    • 如果方法抛出异常,则根据配置决定是否回滚事务

常见问题与解决方案

1. 事务不生效的原因

  • 非public方法: @Transactional只对public方法有效
  • 自调用问题: 在同一个类中调用带有@Transactional的方法不会触发事务
  • 异常被捕获: 如果方法内部捕获了异常但没有重新抛出,事务不会回滚
  • 不正确的异常类型: 默认情况下,只有RuntimeException和Error会触发回滚

2. 自调用问题解决方案

java
@Service
public class UserService {
    
    @Autowired
    private ApplicationContext context;
    
    public void methodA() {
        // 获取代理对象,避免自调用问题
        UserService proxy = context.getBean(UserService.class);
        proxy.methodB();
    }
    
    @Transactional
    public void methodB() {
        // 事务方法实现
    }
}

3. 确保异常正确传播

java
@Transactional
public void updateUser(User user) {
    try {
        userRepository.save(user);
        emailService.sendNotification(user);
    } catch (EmailServiceException e) {
        // 转换为运行时异常,确保事务回滚
        throw new ServiceException("Failed to send notification", e);
    }
}

最佳实践

  1. 在服务层使用事务: 将事务控制在服务层,而不是数据访问层或控制器层
  2. 合理设置传播行为: 根据业务需求选择合适的传播行为
  3. 精确配置回滚规则: 明确定义哪些异常应该导致回滚
  4. 注意事务边界: 避免在一个事务中执行过多操作,特别是耗时的操作
  5. 避免在事务方法中调用远程服务: 这可能导致长时间运行的事务
  6. 不要过度使用REQUIRES_NEW: 它会创建新的事务,可能导致性能问题
  7. 合理使用只读事务: 对于查询操作,使用只读事务可以提高性能
  8. 测试事务行为: 确保在各种异常情况下,事务行为符合预期

总结

声明式事务管理是Spring框架的强大特性,它大大简化了事务管理的复杂性,使开发者可以专注于业务逻辑而不是事务控制细节。通过合理配置@Transactional注解或XML配置,可以灵活控制事务的行为,满足各种业务场景的需求。

在实际应用中,声明式事务是处理大多数事务需求的首选方式,只有在需要非常精细的事务控制时,才考虑使用编程式事务管理。