@Transactional
是 Spring 框架提供的一个注解,用于声明事务性方法。它可以用于类、方法和接口上,以便对数据库操作进行事务管理。通过使用这个注解,我们可以控制事务的传播行为、隔离级别、超时时间以及回滚条件等参数,从而实现更加灵活和高效的事务管理。
下面详细介绍一下 @Transactional
注解的关键属性和用法:
关键属性
(1)propagation(传播行为): 定义了事务方法的传播行为,即当一个事务方法被另一个事务方法调用时,如何共享事务。常见的传播行为包括:
Propagation.REQUIRED
(默认):如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。Propagation.SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行。Propagation.MANDATORY
:使用当前的事务,如果当前没有事务,就抛出异常。Propagation.REQUIRES_NEW
:新建事务,如果当前存在事务,把当前事务挂起。Propagation.NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。Propagation.NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。Propagation.NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就执行 REQUIRED 行为。
(2)isolation(隔离级别): 定义了事务的隔离级别,即多个并发事务之间的隔离程度。常见的隔离级别包括:
Isolation.READ_UNCOMMITTED
:允许读取尚未提交的数据变更,可能导致脏读、不可重复读和幻读。Isolation.READ_COMMITTED
:允许读取并发事务已经提交的数据,可以防止脏读,但是可能出现不可重复读和幻读。Isolation.REPEATABLE_READ
:对同一字段的多次读取结果都是一致的,可以防止脏读和不可重复读,但可能出现幻读。Isolation.SERIALIZABLE
:完全服从ACID的隔离级别,最高级别,事务序列化执行,资源消耗大,性能低下。-
timeout(超时时间): 定义了事务的超时时间,以秒为单位。如果在该时间内事务没有完成,则抛出异常。
-
rollbackFor(回滚条件): 定义了哪些异常类型会导致事务回滚。默认情况下,只有运行时异常(
RuntimeException
)和错误(Error
)会导致事务回滚。如果需要其他异常也触发回滚,可以指定这个属性。 -
readOnly(只读): 标记事务为只读,用于优化。只读事务不会进行更新操作,因此数据库引擎可能会为只读事务提供优化。
使用方法
@Transactional
注解可以应用于类、方法和接口上。当应用于类上时,类中的所有 public
方法都将具有该类型的事务属性。如果同时应用于方法和类上,方法级别的事务属性将覆盖类级别的定义。
需要注意的是,Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时才会生效。另外,@Transactional
注解应该只被应用到 public
方法上,这是由 Spring AOP(面向切面编程)的本质决定的。
@Transactional
注解不生效的八大场景
1、数据库引擎不支持事务
这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
2、没有被 Spring 管理
如下面例子所示:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
3、方法不是 public 的
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
以下来自 Spring 官方文档:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
也是说的上面那个意思。
4、自身调用问题
来看两个示例:
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
// update order
}
}
update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?
再来看下面这个例子:
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
// update order
}
}
这次在 update 方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么?
这两个例子的答案是:不管用!
因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。
5、数据源没有配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
如上面所示,当前数据源若没有配置事务管理器,那也是白搭!
6、不支持事务
来看下面这个例子:
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起,都主动不支持以事务方式运行了,那事务生效也是白搭。
7、异常被吃了
这个也是出现比较多的场景:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
把异常吃了,然后又不抛出来,事务怎么回滚吧!
8、异常类型错误
上面的例子再抛出一个异常:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类。
參考鏈接:总结Spring事务注解@transactional无法生效的八个场景 | w3cschool笔记
@Transactional
注解不生效的特殊场景
(1)同一个方法,但是不同线程之间不会生效
@Transactional public void insertDevelopment(){ CompletableFuture.supplyAsync(() -> { log.info("one 线程名称:{}",Thread.currentThread().getName()); Development development = new Development(); development.setDevName("大数据"); development.setDevDesc("bigData"); development.setCreateTime(LocalDateTime.now()); int insert = mapper.insert(development); //成功 return development; }, executorService).thenAcceptAsync(res -> { log.info("two 线程名称:{},参数 {}",Thread.currentThread().getName(),res); int insert = mapper.insert(res); //成功 int i = 1 / 0; //此处异常不会回滚 redisTemplate.opsForValue().set(res.getDevDesc(),res); },executorService).exceptionally(exe -> { exe.printStackTrace(); return null; }); }
(2)看下面的例子
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void handle(){
throw new RuntimeException("");
}
注意:该方法原本的是目的是不参与到事务中去,但如果该方法执行过程中抛出了任何Exception
类型的异常,则调用该方法之前已存在的事务都会被回滚,但是,如果该注解标注的方法中存在与@Transactional注解中操作同一张表,则该注解中的执行的方法也会回滚。
举例:
@Service
public class User2ServiceImpl implements User2Service{
@Autowired
private User2Mapper user2Mapper;
@Autowired
private IncentiveServiceImpl incentiveService;
@Override
@Transactional(rollbackFor = Exception.class)
public void testTransactional() {
deleteById();
incentiveService.updateById();
insertNewUser();
}
public void deleteById() {
Example example = new Example(User2.class);
example.createCriteria()
.andLessThan("createTime",LocalDateTime.now());
user2Mapper.deleteByExample(example);
}
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void insertNewUser(){
User2 user2 = new User2();
user2.setUserId(String.valueOf((int) (Math.random() * 100) +1))
.setUserName("那一年的春天,我遇见了你。阳光明媚,花儿盛开,仿佛是上天安排的缘分。
你的微笑如同阳光,温暖了我的心灵,让我感受到生命的美好"
.substring((int) (Math.random()*10 + 1),(int) (Math.random()*12 + 1)))
.setCreateTime(LocalDateTime.now());
user2Mapper.insert(user2);
int i = 1/0;
}
}
insertNewUser()方法虽然不参与事务,但操作的表User 和 deleteById() 方法操作的是同一张表,所以
insertNewUser()中报异常后,insertNewUser()中执行的写入User表中的操作也会回滚。
解决方法通常涉及以下几个方面:
-
确保类被 Spring 管理。
-
使用公开的方法。
-
确保正确使用接口。
-
确保事务管理方式(如果使用 CGLIB)一致。
-
使用代理对象调用事务方法。
-
合理处理异常,或使用
rollbackFor
属性。 -
根据需要正确设置
propagation
属性。 -
检查数据库和事务管理器配置。
-
检查和修复 Spring 配置问题。
-
确保注解处理器工作正常。
针对具体问题,需要检查并调整配置或代码以确保@Transactional
注解能够正常工作。
注意事项
-
并非所有异常都会导致事务回滚。默认情况下,只有运行时异常(
RuntimeException
)和错误(Error
)会触发回滚。如果需要其他类型的异常也触发回滚,可以通过rollbackFor
属性来指定。 -
使用
@Transactional
注解时,需要注意事务的传播行为和隔离级别对性能的影响。不合理的配置可能导致性能下降或数据不一致。 -
在并发环境中,需要特别注意事务的隔离级别和并发控制,以避免脏读、不可重复读和幻读等问题。
总之,@Transactional
注解是 Spring 框架中强大的事务管理工具,通过合理配置和使用这个注解,可以实现对数据库操作的高效和可靠管理。