在Spring Boot应用程序中,事务注解是最常用的注解之一。它可以帮助程序员更好地管理数据库事务,并提高代码的可维护性和可测试性。本文将从多个方面对Spring Boot事务注解进行详细的阐述,包括事务注解回滚、事务注解不生效、事务注解原理、事务注解失效问题、事务注解参数、核心注解等内容。
一、Spring Boot事务注解回滚
Spring Boot事务注解中最常用到的一个功能就是回滚。在默认情况下,当代码中的一个方法出现异常时,Spring会自动回滚所有对数据库的修改。例如:
@Service public class UserService { @Autowired private UserRepository userRepository; @Transactional public void saveAll(ListuserList) { for (User user : userList) { userRepository.save(user); } throw new RuntimeException("出现异常,事务回滚"); } }
上面的代码中,当saveAll方法被调用时,如果保存到数据库时出现异常,Spring将自动回滚所有对数据库的修改。这是因为saveAll方法上被@Transactional注解所标注。
同时,也可以使用@Transactional注解的rollbackFor属性来指定哪些异常可以触发回滚:
@Transactional(rollbackFor = Exception.class) public void saveAll(ListuserList) { 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 ListgetAllUser() { //... }
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核心注解
最后,本文列举了一