@Transactional(rollbackFor = Throwable.class)事务注解讲解,事务传播机制讲解

@Transactional(rollbackFor = Throwable.class) 是一个用于声明事务行为的注解,在 Spring 框架中使用。它的作用是将被注解的方法或类标记为一个事务性操作,并指定在出现哪些异常时触发事务回滚。

具体解释如下:

@Transactional:表示该方法或类是一个事务性操作。
rollbackFor = Throwable.class:指定了在出现任何 Throwable 类型的异常时都触发事务回滚。Throwable 是所有错误和异常的超类,包括受检异常和未受检异常。

使用 @Transactional 注解可以通过以下方式来控制事务的行为:

当方法执行期间出现指定的异常时(如 RuntimeException 或自定义异常),事务将回滚。
如果没有指定异常类型,则默认情况下只有在出现运行时异常(RuntimeException 及其子类)时才会触发事务回滚。

需要注意的是,事务的具体行为还取决于所使用的事务管理器和配置。在 Spring 中,可以通过配置事务管理器的相关属性来定义事务的隔离级别、传播行为等。

使用 @Transactional 注解可以简化事务管理的代码,并提供了一种声明式的方式来处理事务。但需要注意合理使用事务,避免事务范围过大或嵌套过深,以免影响性能和产生意外的结果。
事务传播机制是指在多个事务方法相互调用时,各个方法之间事务如何进行传播和交互的规则。Spring 框架提供了七种不同的事务传播行为,开发人员可以根据实际需求选择合适的传播行为来控制事务的行为。

下面是七种事务传播行为:

REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务中,加入到这个事务中。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,暂停当前事务。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 REQUIRED 类似的操作。

REQUIRED 传播行为示例:

@Service
public class UserService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        // 执行更新操作
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void createUserAndConsumePoints(User user, int points) {
        // 创建用户
        createUser(user);
        // 消耗积分
        consumePoints(user, points);
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createUser(User user) {
        // 执行创建操作
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void consumePoints(User user, int points) {
        // 消耗积分操作
    }
}

在上面的示例中,我们有一个服务类 UserService,其中包含了四个方法,它们都使用了 @Transactional(propagation = Propagation.REQUIRED) 注解。当调用 createUserAndConsumePoints 方法时,如果当前已经存在一个事务,则 createUserAndConsumePoints 方法会加入到这个事务中。如果当前不存在事务,会创建一个新的事务。

REQUIRES_NEW 传播行为示例:

@Service
public class UserService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        // 执行更新操作
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createUserAndConsumePoints(User user, int points) {
        // 创建用户
        createUser(user);
        // 消耗积分
        consumePoints(user, points);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createUser(User user) {
        // 执行创建操作
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void consumePoints(User user, int points) {
        // 消耗积分操作
    }
}

在这个示例中,我们仍然有一个服务类 UserService,但是这次 createUserAndConsumePoints、createUser 和 consumePoints 方法都使用了 @Transactional(propagation = Propagation.REQUIRES_NEW) 注解。这样,无论当前是否存在事务,这些方法都会创建一个新的事务。

NESTED 传播行为示例:

@Service
public class UserService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        // 执行更新操作
    }

    @Transactional(propagation = Propagation.NESTED)
    public void createUserAndConsumePoints(User user, int points) {
        // 创建用户
        createUser(user);
        // 消耗积分
        consumePoints(user, points);
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void createUser(User user) {
        // 执行创建操作
    }

    @Transactional(propagation = Propagation.NESTED)
    public void consumePoints(User user, int points) {
        // 消耗积分操作
    }
}

在这个示例中,我们同样有一个服务类 UserService,但是这次 createUserAndConsumePoints、createUser 和 consumePoints 方法都使用了 @Transactional(propagation = Propagation.NESTED) 注解。这样,如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与 REQUIRED 类似的操作。

下面来看一个事务传播机制不生效的场景。

事务传播机制不生效的场景示例:

@Service
public class UserService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        // 执行更新操作
        try {
            // 调用内部方法
            internalMethod();
        } catch (Exception e) {
            // 捕获异常
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void internalMethod() {
        // 内部方法逻辑
        throw new RuntimeException("Something went wrong");
    }
}

在上面的示例中,我们有一个服务类 UserService,其中有一个方法 updateUser 使用了 REQUIRED 传播行为,另一个方法 internalMethod 使用了 REQUIRES_NEW 传播行为。但是,即使 internalMethod 中抛出了异常,由于它是在同一个类中被调用的,事务传播机制并不会生效,updateUser 方法中的事务也不会回滚。这是因为 Spring 默认使用代理对象来实现事务,而代理对象的方法调用本类内部的另一个方法,并不会触发事务的传播行为。

总的来说,通过合理设置事务传播行为,可以达到精确控制事务边界和隔离级别的目的。但需要注意的是,在同一个类的方法之间调用时,事务传播机制可能不会生效,需要特别注意这一点。

代码拆分讲解:

class UserService {
    @Transactional
    public void methodA() {
        // 执行一些数据库操作
        // 调用 createUserAndConsumePoints 方法
        createUserAndConsumePoints();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createUserAndConsumePoints() {
        // 创建新的事务
        // 执行一些数据库操作
        // 调用 createUser 方法
        createUser(user);
        // 调用 consumePoints 方法
        consumePoints(user, points);
    }

    @Transactional
    public void createUser(User user) {
        // 执行创建用户的数据库操作
    }

    @Transactional
    public void consumePoints(User user, int points) {
        // 执行扣除积分的数据库操作
    }
}

当执行 methodA 方法时,如果在其中的任何一个数据库操作或调用方法过程中发生了异常,将触发事务的回滚。具体来说,在以下情况下会触发回滚:

如果在 createUserAndConsumePoints 方法内部发生了异常,会导致 createUserAndConsumePoints 方法内部的数据库操作以及调用的 createUser 和 consumePoints 方法的数据库操作都被回滚。

如果在 methodA 方法内部发生了异常,在调用 createUserAndConsumePoints 方法之前的数据库操作也会被回滚。

如果在 createUser 方法内部或 consumePoints 方法内部发生了异常,只会回滚对应方法内部的数据库操作。

关于事务传播机制的隔离性,这里使用的是 REQUIRES_NEW 传播机制。它的特点是创建一个新的事务,与调用它的方法的事务相互独立。具体来说:

当调用 createUserAndConsumePoints 方法时,会创建一个新的事务,并执行其中的数据库操作和方法调用。

如果在 createUserAndConsumePoints 方法内部发生异常并触发回滚,只会回滚 createUserAndConsumePoints 方法内部的数据库操作,而不会影响调用 createUserAndConsumePoints 方法的 methodA 方法的事务。

同样地,如果在 createUser 方法或 consumePoints 方法中发生异常并触发回滚,只会回滚对应方法内部的数据库操作,不会影响调用它们的 createUserAndConsumePoints 方法或 methodA 方法的事务。

通过使用 REQUIRES_NEW 传播机制,可以实现事务之间的隔离性,确保每个方法的操作在独立的事务中执行,并且异常不会影响到其他方法的事务。这样可以更细粒度地控制事务的回滚范围,提高代码的可靠性和数据的一致性。
createUserAndConsumePoints 方法内部发生异常并导致事务回滚,那么会影响到该方法内部以及调用的 createUser 和 consumePoints 方法的数据库操作的原因是,这些方法都处于同一个事务中。

在 Spring 中,默认情况下,@Transactional 注解应用在类级别时,会对这个类中的所有公共方法应用事务。当调用一个被 @Transactional 注解修饰的方法时,Spring 会检查当前是否已经存在一个事务。如果存在,则将方法加入到该事务中;如果不存在,则创建一个新的事务。

在你提供的示例中,createUserAndConsumePoints 方法使用了 @Transactional(propagation = Propagation.REQUIRES_NEW) 注解,表示该方法应该在一个新的事务中执行。当这个方法发生异常导致事务回滚时,不仅仅会回滚 createUserAndConsumePoints 方法内部的数据库操作,还会回滚调用的 createUser 和 consumePoints 方法的数据库操作,因为它们都在同一个事务中。

这种行为是由于事务的特性所决定的。在一个事务中,如果任何一个方法出现异常,整个事务都会被回滚到最初的状态,以确保数据的一致性和完整性。因此,即使是嵌套的事务,也会受到外部事务的影响。

如果你不希望 createUser 和 consumePoints 方法受到 createUserAndConsumePoints 方法事务回滚的影响,可以将它们设置为独立的方法,并使用不同的事务传播行为,如 Propagation.REQUIRED 或 Propagation.NESTED。这样,每个方法将在自己独立的事务中执行,互相之间的事务操作不会相互干扰。

你可能感兴趣的:(java)