您的位置:

Spring Boot事务注解全面解析

在Spring Boot应用程序中,事务注解是最常用的注解之一。它可以帮助程序员更好地管理数据库事务,并提高代码的可维护性和可测试性。本文将从多个方面对Spring Boot事务注解进行详细的阐述,包括事务注解回滚、事务注解不生效、事务注解原理、事务注解失效问题、事务注解参数、核心注解等内容。

一、Spring Boot事务注解回滚

Spring Boot事务注解中最常用到的一个功能就是回滚。在默认情况下,当代码中的一个方法出现异常时,Spring会自动回滚所有对数据库的修改。例如:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void saveAll(List userList) {
        for (User user : userList) {
            userRepository.save(user);
        }
        throw new RuntimeException("出现异常,事务回滚");
    }
}

  

上面的代码中,当saveAll方法被调用时,如果保存到数据库时出现异常,Spring将自动回滚所有对数据库的修改。这是因为saveAll方法上被@Transactional注解所标注。

同时,也可以使用@Transactional注解的rollbackFor属性来指定哪些异常可以触发回滚:

@Transactional(rollbackFor = Exception.class)
public void saveAll(List userList) {
    for (User user : userList) {
        userRepository.save(user);
    }
}

  

上面的代码中,指定了rollbackFor属性为Exception.class,表示只要代码中出现Exception及其子类的异常,都会触发事务回滚。

二、Spring Boot事物注解不生效

事务注解不生效可能是让程序员最为头疼的问题之一。以下列出一些事务注解不生效的原因:

1. 不是public方法

Spring事务默认只对public方法有效,因为只有public方法才能被外部调用。如果使用事务注解来注解一个非public方法,那么这个事务注解将不会生效。

@Component
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    protected void save(User user) {
        userDao.save(user);
    }
}

上面的代码中,save方法是被protected修饰的。因此,save方法上加的@Transactional注解将无效。

2. 方法内部调用

如果在方法内部调用了另一个带有@Transactional注解的方法,事务注解将不会生效。这是因为Spring事务是基于AOP来实现的。在方法内部调用另一个方法,会绕过AOP代理。因此如果要使事务注解生效,必须在外部调用方法。

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    public void saveAndDelete(User userToDelete, User userToSave) {
        userDao.delete(userToDelete);
        save(userToSave);//save方法上有@Transactional注解
    }

    @Transactional
    public void save(User user) {
        userDao.save(user);
    }
}

上面的代码中,如果调用saveAndDelete方法,只有saveAndDelete方法上的@Transactional注解会生效,而save方法上的@Transactional注解将无效。

3. 异常被捕获

如果在事务方法内部捕获了异常,并处理了异常,那么事务将正常提交,不会回滚。

@Autowired
private UserDao userDao;

@Transactional
public void save(User user) {
    try {
        userDao.save(user);
        throw new RuntimeException("出现异常,但已被处理");
    } catch (Exception e) {
        //异常处理
    }
}

上面的代码中,出现异常会被捕获并进行了处理。因此,@Transactional注解将不会回滚事务。

三、Spring Boot事务注解原理

在Spring Boot中,事务注解的原理是基于AOP(面向切面编程)的。也就是说,对于带有@Transactional注解的方法,在运行时会被动态地生成一个代理类,并将该方法被代理。当执行代理方法时,代理方法会判断是否有事务正在进行,如果没有,则开启一个新事务;如果有,则将该方法的操作放入当前事务中。最后,执行完代理方法时,会判断是否有异常发生,如果有,则回滚事务,否则提交事务。

四、Spring Boot事务注解失效问题

除了上面提到的事务注解不生效的原因之外,下面列举一些其他的事务注解失效问题:

1. 异常不声明抛出类型

声明抛出的异常类型不能够冲突,否则事务注解将失效。

@Transactional
public void save(User user) throws Exception {
    userDao.save(user);
    try {
        throw new Exception("业务异常");
    } catch (Exception e) {
        throw new Exception("异常");
    }
}

上面的代码中,save方法声明抛出Exception异常。但是,在try-catch中,又抛出了另一个Exception异常。这将导致布满Spring默认的事务注解失效。

2. Redis注解与事务注解同时使用

在Spring Boot应用程序中,有时会使用Redis来缓存数据。如果在同一个方法中同时使用Redis注解和事务注解,可能会导致事务注解失效。

@Transactional
public void saveAndCache(User user) {
    userDao.save(user);
    redisTemplate.opsForValue().set(user.getId(), user);
}

上面的代码中,在同一个方法中使用了@Transactional注解和Redis操作,这将导致事务注解失效。

为了解决这个问题,可以使用Spring提供的专门的Redis事务注解。将Redis操作放到一个单独的方法中,并使用@RedisTransaction注解标注该方法,然后在需要访问缓存的其他方法中调用该方法:

@Transactional
public void saveAndCache(User user) {
    userDao.save(user);
    cache(user);
}

@RedisTransaction
public void cache(User user) {
    redisTemplate.opsForValue().set(user.getId(), user);
}

五、Spring Boot事务注解加了没用

如果我们在代码中添加了@Transactional注解,但是并没有看到任何事务被开启或者提交,我们需要检查以下几个事项:

1. 开启事务的方法名是否正确

在使用注解的时候是通过方法进行控制的,因此,事务注解加在的方法是一定要正确的。如果注解加在了一个不执行的方法上,那么注解就是毫无作用的。

2. 检查所在的类是否被代理成功

如果事务代理并没有对一个类进行代理,那么在这个类中加上@Transactional也是没有卵用的。

3. 检查事务是否已经被提交或回滚

如果事务已经被提交或回滚,那么为了保证代码的正确性,事务代理就不会执行任何操作了。在这种情况下,我们可以在日志中查看是否有相关的记录,以确定事务是否被提交或回滚。

六、Spring Boot事物注解失效场景

下面列举一些常见的场景,可能导致Spring Boot事务注解失效:

1. 使用JdbcTemplate访问数据库

如果在使用Spring Boot JdbcTemplate访问数据库时,没有使用Spring Boot提供的JdbcTemplate事务注解,可能会导致事务注解失效。

@Autowired
private JdbcTemplate jdbcTemplate;

@Transactional
public void save(User user) {
    jdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword());
}

上面的代码中,虽然save方法上标注了@Transactional注解,但是jdbcTemplate是通过注入的方式获得的,没有使用JdbcTemplate事务注解,因此,事务注解将失效。

2. 多数据源情况下

在多数据源情况下,铁线事务注解可能会失效。因为事务注解是基于AOP实现的,必须确保所有的操作都在同一个事务中进行。

@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Transactional
public void save(User user) {
    primaryJdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword());
    secondaryJdbcTemplate.update("insert into user(username, password) values(?, ?)", user.getUsername(), user.getPassword());
}

上面的代码中,save方法中同时使用了两个数据源的JdbcTemplate进行访问。由于每个JdbcTemplate都有一个事务,因此@Transactional注解将失效。

七、Spring Boot事务注解参数

Spring Boot事务注解提供了一些参数,可以用来进一步控制事务的行为:

1. isolation

表示事务的隔离级别。常用的有READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。其中,SERIALIZABLE级别可以最大程度地保证数据的一致性,但是对数据库性能的影响最大。

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateCUser(User user1, User user2) {
    //...
}

2. timeout

表示事务的超时时间,单位是秒。如果事务在指定的时间内没有执行完,就会被强制回滚。

@Transactional(timeout = 10)
public void save(User user) {
    //...
}

3. readOnly

表示事务是否为只读。如果为只读,那么事务中只能进行查询操作,不能进行修改操作。这可以提高查询效率,减少锁定时间。

@Transactional(readOnly = true)
public List getAllUser() {
    //...
}

  

4. propagation

表示事务的传播行为。当一个事务方法调用另一个事务方法时,就会使用到传播行为。常用的有REQUIRED、REQUIRES_NEW、NESTED等。其中,REQUIRED是默认值。REQUIRES_NEW表示如果当前方法已有事务,则会挂起当前事务并重新开启一个新事务执行;而NESTED则表示如果当前方法已有事务,则嵌套在当前事务中执行。

@Transactional(propagation = Propagation.REQUIRED)
public void saveUserAndRole(User user, Role role) {
    saveUser(user);
    saveRole(role);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUser(User user) {
    //...
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveRole(Role role) {
    //...
}

八、SpringBoot核心注解

最后,本文列举了一