searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Spring事务管理@Transactional注解详解

2024-10-21 09:43:17
68
0

1. 使用场景

@Transactional 注解主要用于需要确保数据一致性和完整性的场景,特别是在涉及多个数据库操作的场景中,如:

  • 读写数据库​:通常用于增、删、改等涉及数据修改的操作,确保这些操作要么全部成功,要么全部失败。
  • 批量操作​:如批量插入或更新记录,一旦其中一条记录操作失败,全部操作都会回滚。
  • 复杂业务逻辑​:当多个数据库表之间存在关联时,比如涉及订单、支付等业务场景时,需要保持数据一致性。

2. 使用限制

@Transactional 注解在使用时,有一些限制或注意事项:

  • 适用于公有方法​:Spring 的事务管理默认是基于 AOP 代理机制,因此 @Transactional 只能应用在 public 方法上。如果在 privateprotected 方法上使用,则不会生效。
  • 同类方法调用​:如果在同一个类中调用带有 @Transactional 注解的方法,事务将不会生效,因为代理无法在类内部调用时生效。
  • 只支持单个数据库​:在使用 DataSourceTransactionManager 时,事务管理默认只支持单个数据源。如果涉及到多个数据库操作,建议使用分布式事务管理框架(如 Spring Cloud 的 Seata 或 JTA)。

3. 属性详解

@Transactional 注解提供了一些常用属性,用于更细粒度地控制事务行为:

  • ​​​propagation​:
    REQUIRED(默认值):如果当前有事务存在,则加入当前事务;如果没有事务,则创建新事务。
    REQUIRES_NEW:无论当前是否有事务,都会创建新事务,并且暂停当前事务。
    NESTED:嵌套事务,内部事务可以独立回滚,但依赖于外部事务的提交。
    SUPPORTS:如果当前有事务,则加入事务;如果没有事务,则以非事务方式执行。
    NOT_SUPPORTED:以非事务方式执行,并暂停当前事务。
    NEVER:以非事务方式执行,如果当前有事务,则抛出异常。
    MANDATORY:必须在一个现有事务中执行,如果没有事务,则抛出异常。
  • isolation​:定义了数据库操作的隔离性,避免并发问题。常见的隔离级别:
    DEFAULT:使用底层数据库的默认隔离级别。
    READ_UNCOMMITTED:允许读取未提交的数据,可能会导致脏读。
    READ_COMMITTED:只能读取已提交的数据,避免脏读。
    REPEATABLE_READ:在事务期间多次读取相同数据时,结果是一致的,避免不可重复读。
    SERIALIZABLE:最高级别的隔离,完全避免并发问题,但可能导致性能下降。
  • timeout​:设置事务超时时间,超过此时间后,事务会被强制回滚。单位为秒。
  • readOnly​:如果设置为 true,则表示该事务为只读事务,用于优化性能。常用于只进行查询的操作。
  • rollbackFor​:指定抛出哪些异常时,事务应该回滚。默认情况下,Spring 只在抛出 RuntimeExceptionError 时回滚。
  • noRollbackFor​:指定抛出哪些异常时,事务不应该回滚。

4. 使用方式

通常有三种方式使用 @Transactional注解:

4.1. 在类或方法上

在类上声明 @Transactional 表示该类中的所有方法都将参与事务管理。如果只希望特定方法参与事务管理,可以只在方法上添加。

@Service
@Transactional
public class UserService {
    public void saveUser(User user) {
        // 数据库操作
    }

    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        // 只读操作
    }
}

4.2. 通过 XML 配置事务

可以通过 XML 来定义事务配置(较少使用)。

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" />
        <tx:method name="find*" read-only="true" />
    </tx:attributes>
</tx:advice>

4.3. 通过编程式事务管理

除了使用注解和 XML 配置,还可以通过 Spring 提供的编程式 API 手动控制事务。在某些复杂场景中,例如需要动态地控制事务的行为时,编程式事务管理可能更加灵活。

开发者可以使用 TransactionTemplate 来手动控制事务的开始、提交和回滚。

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void saveUser(User user) {
        transactionTemplate.execute(status -> {
            try {
                // 数据库保存操作
                return true;
            } catch (Exception e) {
                status.setRollbackOnly();  // 手动触发回滚
                throw e;
            }
        });
    }
}

5. 注意事项

  • 异常类型影响回滚​:默认情况下,Spring 只会对 RuntimeExceptionError 类型的异常回滚。对于 Checked Exception(受检异常),事务不会自动回滚,除非你显式配置 rollbackFor 属性。
  • 手动管理事务时避免混用​:如果你手动管理事务(如使用 TransactionTemplate),那么在这些方法中不要混用 @Transactional,否则会导致不一致行为。
  • 只读事务的性能优化​:如果明确某些方法仅用于查询,可以设置 @Transactional(readOnly = true),这会帮助底层数据库优化性能(如避免加锁)。

6. 常见异常

  • TransactionRequiredException:当一个需要事务支持的方法在没有事务的环境下执行时,抛出此异常。通常是由于事务传播行为不正确或事务管理器未正确配置。
  • UnexpectedRollbackException:当一个事务在提交时检测到已经回滚时抛出此异常。可能是因为嵌套事务或其他操作导致事务回滚。
  • OptimisticLockingFailureException​:在乐观锁的场景下,如果两个事务并发更新同一条记录,并且其中一个事务提交了数据,另一个事务则会抛出该异常。

7. 最佳实践

  • 区分读写操作​:使用 readOnly = true 来优化只读查询操作,避免不必要的事务开销。
@Service
public class UserService {

    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        // 查询操作,标记为只读事务
        return userRepository.findById(id);
    }

    @Transactional
    public void saveUser(User user) {
        // 写操作,进行插入或更新
        userRepository.save(user);
    }
}

findUserById 方法被标记为 readOnly = true,Spring 将优化事务管理,避免不必要的加锁和其他资源开销。

  • 合适的传播行为​:在编写多个事务嵌套调用的方法时,仔细选择传播行为,尤其是在需要多个事务独立回滚或提交的场景下。
    当你在一个方法中调用另一个有事务控制的方法时,如果子方法中的事务失败不应该影响到父方法,可以使用 REQUIRES_NEW
@Service
public class OrderService {

    @Transactional
    public void processOrder(Order order) {
        // 处理订单逻辑
        saveOrder(order);
        
        try {
            // 尝试发送通知,即使通知失败,订单也应该提交
            sendNotification(order);
        } catch (Exception e) {
            // 处理通知发送失败的情况
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(Order order) {
        // 发送通知的逻辑,可能会抛出异常
        notificationService.send(order);
    }
}

processOrder 方法会处理订单逻辑并提交订单,同时调用 sendNotification。即使 sendNotification 方法抛出异常,因为它使用 REQUIRES_NEW 传播级别,不会影响 processOrder 的事务提交。

  • 合理使用隔离级别​:根据业务场景选择适合的隔离级别,避免性能瓶颈或不必要的锁。
@Service
public class BankService {

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public Account getAccountBalance(Long accountId) {
        // 获取账户余额,防止不可重复读
        return accountRepository.findById(accountId);
    }
}

getAccountBalance 方法使用 REPEATABLE_READ 隔离级别,确保在事务中多次读取同一账户余额时,返回的数据一致,防止不可重复读的问题。

  • 捕获异常处理​:尽量不要在事务方法内部捕获并吞掉 RuntimeException,否则可能会导致事务未回滚。
@Service
public class PaymentService {

    @Transactional
    public void processPayment(Payment payment) {
        try {
            // 处理支付
            paymentRepository.save(payment);
        } catch (RuntimeException e) {
            // 不要吞掉异常,否则事务不会回滚
            throw e;
        }
    }
}

processPayment 方法中,如果捕获 RuntimeException,要继续抛出异常,否则事务不会回滚,从而可能导致数据库数据不一致。

  • 注解事务粒度​:尽量将事务注解放置在最顶层的方法上,避免内嵌事务带来的复杂性。
@Service
public class PurchaseService {

    @Transactional
    public void completePurchase(Purchase purchase) {
        // 调用多个子方法,保持事务的一致性
        validatePurchase(purchase);
        processPayment(purchase.getPayment());
        saveOrder(purchase.getOrder());
    }
}

completePurchase 是整个购买流程的顶层方法,所有的子操作都在这个事务中执行,这样可以确保整个操作要么全部成功,要么全部回滚。

0条评论
0 / 1000
李****根
3文章数
1粉丝数
李****根
3 文章 | 1 粉丝
原创

Spring事务管理@Transactional注解详解

2024-10-21 09:43:17
68
0

1. 使用场景

@Transactional 注解主要用于需要确保数据一致性和完整性的场景,特别是在涉及多个数据库操作的场景中,如:

  • 读写数据库​:通常用于增、删、改等涉及数据修改的操作,确保这些操作要么全部成功,要么全部失败。
  • 批量操作​:如批量插入或更新记录,一旦其中一条记录操作失败,全部操作都会回滚。
  • 复杂业务逻辑​:当多个数据库表之间存在关联时,比如涉及订单、支付等业务场景时,需要保持数据一致性。

2. 使用限制

@Transactional 注解在使用时,有一些限制或注意事项:

  • 适用于公有方法​:Spring 的事务管理默认是基于 AOP 代理机制,因此 @Transactional 只能应用在 public 方法上。如果在 privateprotected 方法上使用,则不会生效。
  • 同类方法调用​:如果在同一个类中调用带有 @Transactional 注解的方法,事务将不会生效,因为代理无法在类内部调用时生效。
  • 只支持单个数据库​:在使用 DataSourceTransactionManager 时,事务管理默认只支持单个数据源。如果涉及到多个数据库操作,建议使用分布式事务管理框架(如 Spring Cloud 的 Seata 或 JTA)。

3. 属性详解

@Transactional 注解提供了一些常用属性,用于更细粒度地控制事务行为:

  • ​​​propagation​:
    REQUIRED(默认值):如果当前有事务存在,则加入当前事务;如果没有事务,则创建新事务。
    REQUIRES_NEW:无论当前是否有事务,都会创建新事务,并且暂停当前事务。
    NESTED:嵌套事务,内部事务可以独立回滚,但依赖于外部事务的提交。
    SUPPORTS:如果当前有事务,则加入事务;如果没有事务,则以非事务方式执行。
    NOT_SUPPORTED:以非事务方式执行,并暂停当前事务。
    NEVER:以非事务方式执行,如果当前有事务,则抛出异常。
    MANDATORY:必须在一个现有事务中执行,如果没有事务,则抛出异常。
  • isolation​:定义了数据库操作的隔离性,避免并发问题。常见的隔离级别:
    DEFAULT:使用底层数据库的默认隔离级别。
    READ_UNCOMMITTED:允许读取未提交的数据,可能会导致脏读。
    READ_COMMITTED:只能读取已提交的数据,避免脏读。
    REPEATABLE_READ:在事务期间多次读取相同数据时,结果是一致的,避免不可重复读。
    SERIALIZABLE:最高级别的隔离,完全避免并发问题,但可能导致性能下降。
  • timeout​:设置事务超时时间,超过此时间后,事务会被强制回滚。单位为秒。
  • readOnly​:如果设置为 true,则表示该事务为只读事务,用于优化性能。常用于只进行查询的操作。
  • rollbackFor​:指定抛出哪些异常时,事务应该回滚。默认情况下,Spring 只在抛出 RuntimeExceptionError 时回滚。
  • noRollbackFor​:指定抛出哪些异常时,事务不应该回滚。

4. 使用方式

通常有三种方式使用 @Transactional注解:

4.1. 在类或方法上

在类上声明 @Transactional 表示该类中的所有方法都将参与事务管理。如果只希望特定方法参与事务管理,可以只在方法上添加。

@Service
@Transactional
public class UserService {
    public void saveUser(User user) {
        // 数据库操作
    }

    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        // 只读操作
    }
}

4.2. 通过 XML 配置事务

可以通过 XML 来定义事务配置(较少使用)。

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" />
        <tx:method name="find*" read-only="true" />
    </tx:attributes>
</tx:advice>

4.3. 通过编程式事务管理

除了使用注解和 XML 配置,还可以通过 Spring 提供的编程式 API 手动控制事务。在某些复杂场景中,例如需要动态地控制事务的行为时,编程式事务管理可能更加灵活。

开发者可以使用 TransactionTemplate 来手动控制事务的开始、提交和回滚。

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void saveUser(User user) {
        transactionTemplate.execute(status -> {
            try {
                // 数据库保存操作
                return true;
            } catch (Exception e) {
                status.setRollbackOnly();  // 手动触发回滚
                throw e;
            }
        });
    }
}

5. 注意事项

  • 异常类型影响回滚​:默认情况下,Spring 只会对 RuntimeExceptionError 类型的异常回滚。对于 Checked Exception(受检异常),事务不会自动回滚,除非你显式配置 rollbackFor 属性。
  • 手动管理事务时避免混用​:如果你手动管理事务(如使用 TransactionTemplate),那么在这些方法中不要混用 @Transactional,否则会导致不一致行为。
  • 只读事务的性能优化​:如果明确某些方法仅用于查询,可以设置 @Transactional(readOnly = true),这会帮助底层数据库优化性能(如避免加锁)。

6. 常见异常

  • TransactionRequiredException:当一个需要事务支持的方法在没有事务的环境下执行时,抛出此异常。通常是由于事务传播行为不正确或事务管理器未正确配置。
  • UnexpectedRollbackException:当一个事务在提交时检测到已经回滚时抛出此异常。可能是因为嵌套事务或其他操作导致事务回滚。
  • OptimisticLockingFailureException​:在乐观锁的场景下,如果两个事务并发更新同一条记录,并且其中一个事务提交了数据,另一个事务则会抛出该异常。

7. 最佳实践

  • 区分读写操作​:使用 readOnly = true 来优化只读查询操作,避免不必要的事务开销。
@Service
public class UserService {

    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        // 查询操作,标记为只读事务
        return userRepository.findById(id);
    }

    @Transactional
    public void saveUser(User user) {
        // 写操作,进行插入或更新
        userRepository.save(user);
    }
}

findUserById 方法被标记为 readOnly = true,Spring 将优化事务管理,避免不必要的加锁和其他资源开销。

  • 合适的传播行为​:在编写多个事务嵌套调用的方法时,仔细选择传播行为,尤其是在需要多个事务独立回滚或提交的场景下。
    当你在一个方法中调用另一个有事务控制的方法时,如果子方法中的事务失败不应该影响到父方法,可以使用 REQUIRES_NEW
@Service
public class OrderService {

    @Transactional
    public void processOrder(Order order) {
        // 处理订单逻辑
        saveOrder(order);
        
        try {
            // 尝试发送通知,即使通知失败,订单也应该提交
            sendNotification(order);
        } catch (Exception e) {
            // 处理通知发送失败的情况
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(Order order) {
        // 发送通知的逻辑,可能会抛出异常
        notificationService.send(order);
    }
}

processOrder 方法会处理订单逻辑并提交订单,同时调用 sendNotification。即使 sendNotification 方法抛出异常,因为它使用 REQUIRES_NEW 传播级别,不会影响 processOrder 的事务提交。

  • 合理使用隔离级别​:根据业务场景选择适合的隔离级别,避免性能瓶颈或不必要的锁。
@Service
public class BankService {

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public Account getAccountBalance(Long accountId) {
        // 获取账户余额,防止不可重复读
        return accountRepository.findById(accountId);
    }
}

getAccountBalance 方法使用 REPEATABLE_READ 隔离级别,确保在事务中多次读取同一账户余额时,返回的数据一致,防止不可重复读的问题。

  • 捕获异常处理​:尽量不要在事务方法内部捕获并吞掉 RuntimeException,否则可能会导致事务未回滚。
@Service
public class PaymentService {

    @Transactional
    public void processPayment(Payment payment) {
        try {
            // 处理支付
            paymentRepository.save(payment);
        } catch (RuntimeException e) {
            // 不要吞掉异常,否则事务不会回滚
            throw e;
        }
    }
}

processPayment 方法中,如果捕获 RuntimeException,要继续抛出异常,否则事务不会回滚,从而可能导致数据库数据不一致。

  • 注解事务粒度​:尽量将事务注解放置在最顶层的方法上,避免内嵌事务带来的复杂性。
@Service
public class PurchaseService {

    @Transactional
    public void completePurchase(Purchase purchase) {
        // 调用多个子方法,保持事务的一致性
        validatePurchase(purchase);
        processPayment(purchase.getPayment());
        saveOrder(purchase.getOrder());
    }
}

completePurchase 是整个购买流程的顶层方法,所有的子操作都在这个事务中执行,这样可以确保整个操作要么全部成功,要么全部回滚。

文章来自个人专栏
Golang本地环境搭建
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0