Spring Boot 中使用 @Transactional 注解配置事务管理

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional注解的方式。本文将着重介绍基于@Transactional注解的事务管理。

需要明确几点:

以下的示例使用的是 mybatis,所以 spring boot 会自动配置一个DataSourceTransactionManager,我们只需在方法(或者类)加上@Transactional注解,就自动纳入 Spring 的事务管理了。

1. 简单的使用方法

只需在方法加上@Transactional注解就可以了。

如下有一个保存用户的方法,加入@Transactional注解,使用默认配置,抛出异常之后,事务会自动回滚,数据不会插入到数据库。

@Transactional
@Override
public void save() {
User user = new User(“花花”);
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

2. @Transactional 注解的属性介绍

下面分别介绍一下@Transactional的几个属性。

2.1 value 和 transactionManager 属性

它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

2.2 propagation 属性

事务的传播行为,String默认的事务传播类型为 Propagation.REQUIRED。

可选的值有:

Propagation.REQUIRED

如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

Propagation.SUPPORTS

如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

Propagation.MANDATORY

如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

Propagation.REQUIRES_NEW

重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

Propagation.NOT_SUPPORTED

以非事务的方式运行,如果当前存在事务,暂停当前的事务。

Propagation.NEVER

以非事务的方式运行,如果当前存在事务,则抛出异常。

Propagation.NESTED

和 Propagation.REQUIRED 效果一样。

这些概念理解起来实在是有点儿抽象,后文会用代码示例解释说明。

2.3 isolation 属性

事务的隔离级别,默认值为 Isolation.DEFAULT。

可选的值有:

Isolation.DEFAULT

使用底层数据库默认的隔离级别。

Isolation.READ_UNCOMMITTED (未提交读)

Isolation.READ_COMMITTED (提交读,不可重复读)oracle默认的隔离级别
Isolation.REPEATABLE_READ (可重复读)mysql默认的隔离级别
Isolation.SERIALIZABLE (可串行化)

越往下效率越低

2.4 timeout 属性

事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

2.5 readOnly 属性

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

2.6 rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

2.7 noRollbackFor 属性

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

3. @Transactional 的 propagation 属性代码示例

比如如下代码,save 方法首先调用了 method1 方法,然后抛出了异常,就会导致事务回滚,如下两条数据都不会插入数据库。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("许嵩");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

public void method1() {
User user = new User(“汪苏泷”);
userMapper.insertSelective(user);
}

现在有需求如下,就算 save 方法的后面抛异常了,也不能影响 method1 方法的数据插入。或许很多人的想法如下,给 method1 页加入一个新的事务,这样 method1 就会在这个新的事务中执行,原来的事务不会影响到新的事务。比如 method1 方法上面再加入注解 @Transactional,设置 propagation 属性为 Propagation.REQUIRES_NEW,代码如下。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("许嵩");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User(“汪苏泷”);
userMapper.insertSelective(user);
}

运行之后,发现然并卵,数据也是没有插入数据库。怎么肥四,看起来很不科学。其实两个方法都是处于同一个事务中,method1 方法并没有创建一个新的事务。

这就得看看Spring 官方文档了。

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-
invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction
at runtime even if the invoked method is marked with @Transactional.

大概意思:在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截,就像上面的 save 方法直接调用了同一个类中的 method1方法,method1 方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题,但是这里暂不讨论。

为了解决这个问题,我们可以新建一个类。

@Service
public class OtherServiceImpl implements OtherService {

@Autowired
private UserMapper userMapper;

@Transactional(propagation = Propagation.REQUIRES\_NEW)
@Override
public void method1() {
    User user = new User("周杰伦");
    userMapper.insertSelective(user);
}

}

然后在 save 方法中调用 otherService.method1 方法

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

otherService.method1();

User user = new User("林俊杰");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

这下,otherService.method1 方法的数据插入成功,save 方法的数据未插入,事务回滚。

从日志可以看出,首先创建了 save 方法的事务,由于 otherService.method1 方法的 @Transactional 的 propagation 属性为 Propagation.REQUIRES_NEW ,所以接着暂停了 save 方法的事务,重新创建了 otherService.method1 方法的事务,接着 otherService.method1 方法的事务提交,接着 save 方法的事务回滚。这就印证了只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。
还有几个示例如下:

接着把 save 方法的 @Transactional 注解去掉,otherService.method1 的 @Transactional 注解保持不变,从日志就可以看出,只会创建一个 otherService.method1 方法的事务,两条数据都会插入。

@Autowired
private OtherService otherService;

// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

otherService.method1();

User user = new User("汪苏泷");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

接着把 save 方法的 @Transactional 注解去掉,save 方法改为调用内部的 method1 方法,从日志就可以看出,完全没有创建任何事务,两条数据都会插入。

// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("华晨宇");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User(“许嵩”);
userMapper.insertSelective(user);
}

这样,其他的几个 propagation 属性值也就比较好理解了。

4. @Transactional 事务实现机制

在应用系统调用声明了@Transactional的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional的属性配置信息,这个代理对象决定该声明@Transactional的目标方法是否由拦截器TransactionInterceptor来使用拦截,在TransactionInterceptor拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager操作数据源DataSource提交或回滚事务。

Spring AOP 代理有CglibAopProxy和JdkDynamicAopProxy两种,以CglibAopProxy为例,对于CglibAopProxy,需要调用其内部类的DynamicAdvisedInterceptor的 intercept 方法。对于JdkDynamicAopProxy,需要调用其 invoke 方法。

正如上文提到的,事务管理的框架是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如事务管理器DataSourceTransactionManager。不同的事务管理器管理不同的数据资源DataSource,比如DataSourceTransactionManager管理JDBC 的 Connection。

5. 源码地址

6. 参考资料

<think>好的,我现在要回答用户的问题:在Spring Boot中不加@Transactional注解的影响及默认行为。首先,我需要回忆Spring Boot事务管理机制。根据用户提供的引用,特别是引用[2]和[4],@Transactional注解用于将方法或类纳入Spring事务管理,确保数据库操作要么全部成功要么回滚。 用户想知道如果不加这个注解会发生什么。首先,默认情况下,Spring Boot使用自动配置事务管理器,比如DataSourceTransactionManager,当使用MyBatis或JPA时。但如果没有@Transactional注解,每个数据库操作可能会在单独的事务中执行,或者可能没有事务。例如,如果在一个方法中有多个数据库操作,不加注解的话,每个操作可能独立提交,导致中间出错时前面的操作无法回滚,数据不一致。 然后,要考虑事务的传播行为。默认情况下,方法如果没有事务上下文,会新建一个事务,但如果没有注解,可能不会开启事务。这点需要确认Spring的默认行为。根据引用[5],@Transactional的propagation属性默认是REQUIRED,即如果存在事务就加入,否则新建。但如果没有注解,方法可能不在事务中执行,所以每个数据库操作可能自动提交。 接下来,需要验证这些推测是否正确。比如,引用[4]提到,加上@Transactional后方法内的操作被事务管理,那么不加的话,可能每个save()或update()会立即提交。例如,在JPA中,默认的flush模式可能导致自动提交,但具体情况可能因配置而异。 另外,需要考虑异常处理。使用@Transactional时,遇到RuntimeException会回滚,但如果不加注解,即使抛出异常,之前的操作可能已经提交。比如,用户服务中保存用户后,如果后续操作失败,没有事务的话,用户记录可能已经存在数据库中,导致不一致。 还需要提到,某些框架或配置可能隐式开启事务,比如JPA的某些配置,但通常显式使用@Transactional更可靠。此外,在只读操作中不加事务可能导致连接未释放等问题,但具体情况需要看数据源配置。 最后,总结不加注解的影响:无事务管理,操作自动提交,可能数据不一致,无法保证原子性。需要引用用户提供的资料中的相关部分,如引用[2]和[4],说明注解的作用以及不加时的默认行为。</think>在Spring Boot中不使用`@Transactional`注解时,事务管理的默认行为及影响如下: --- ### 1. **默认事务行为** - **无事务自动管理**:若未显式添加`@Transactional`注解Spring Boot不会为方法自动创建事务。此时,每个数据库操作(如`save()`、`update()`)会立即提交到数据库,无法保证多个操作的原子性[^2][^4]。 - **隐式事务与框架行为**:某些持久层框架(如JPA)可能在单个`Repository`方法调用时隐式开启事务,但多个操作之间仍独立提交。例如,连续调用两个`save()`方法会导致两次独立的提交[^3]。 --- ### 2. **主要影响** #### (1) **数据一致性风险** - **操作无法回滚**:若方法中包含多个数据库操作,当某个操作失败时,之前的操作可能已提交到数据库,导致数据不一致。 ```java public void createUser(User user) { userRepository.save(user); // 自动提交 someService.updateLog(); // 若此处抛出异常,user记录仍存在 } ``` #### (2) **性能与连接管理** - **频繁提交开销**:每个数据库操作单独提交会增加网络和I/O开销。 - **连接释放问题**:长时间未提交的操作可能导致数据库连接未及时释放,影响连接池资源[^5]。 #### (3) **隔离与传播行为失效** - 无法通过`isolation`和`propagation`属性控制事务边界(如嵌套事务、事务挂起等)[^5]。 --- ### 3. **典型场景示例** #### 场景1:无事务的方法调用 ```java public void transferMoney(Account from, Account to, double amount) { from.setBalance(from.getBalance() - amount); accountRepository.save(from); // 立即提交 to.setBalance(to.getBalance() + amount); accountRepository.save(to); // 若此处失败,from的余额已扣减且无法恢复 } ``` **结果**:若第二个`save()`失败,第一个操作已提交,导致数据不一致。 #### 场景2:隐式事务的局限性(JPA) ```java public void batchInsert(List<User> users) { users.forEach(user -> userRepository.save(user)); // 每条记录单独提交 } ``` **结果**:批量插入性能低下,且部分失败时已插入的数据无法回滚。 --- ### 4. **如何避免问题** 1. **显式添加`@Transactional`注解**: ```java @Transactional public void transferMoney(Account from, Account to, double amount) { // 所有操作在同一个事务中 } ``` 2. **配置只读事务**(适用于查询): ```java @Transactional(readOnly = true) public User getUserById(Long id) { return userRepository.findById(id); } ``` --- ### 5. **例外情况** - **自动提交模式**:若数据源配置了`auto-commit=true`(默认通常为`false`),单个SQL语句可能自动提交,但仍无法解决多操作原子性问题[^3]。 - **框架特定行为**:某些ORM工具(如Hibernate)可能在事务外延迟写入,但最终仍依赖事务提交。 --- ### 总结 不加`@Transactional`注解时,数据库操作默认无事务管理,可能导致数据不一致、性能低下等问题。建议在涉及多步骤数据库操作时显式启用事务[^2][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值