在Spring Boot应用程序中,事务注解是最常用的注解之一。它可以帮助程序员更好地管理数据库事务,并提高代码的可维护性和可测试性。本文将从多个方面对Spring Boot事务注解进行详细的阐述,包括事务注解回滚、事务注解不生效、事务注解原理、事务注解失效问题、事务注解参数、核心注解等内容。
一、Spring Boot事务注解回滚
Spring Boot事务注解中最常用到的一个功能就是回滚。在默认情况下,当代码中的一个方法出现异常时,Spring会自动回滚所有对数据库的修改。例如:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void saveAll(List<User> 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<User> 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<User> 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) {
// ...
}
八、Spring Boot核心注解
最后,本文列举了一