-
MANDATORY
-
NEVER
-
NOT_SUPPORTED
-
SUPPORTS
-
REQUIRED
(默认) -
REQUIRES_NEW
-
NESTED
所以,这个面试题的答案就是:“2个事务性的方法,一个调用另一个,由于事务传播的默认值是REQUIRED
,则表现为:如果当前无事务,则创建,如果当前有事务,则使用”,为了完善我的答案,我还继续补充了:“如果将@Transactional
注解的propagation
属性配置为其它值,则会不同”。当我非常流利的把我脑中的答案说完之后,面试官笑了笑,说了两个字:“不对”,我当时就懵了,最后,面试官也没有告诉我答案,只是让我自己回去找答案……不过运气还算不错,由于只错了这一题,最后还是顺利入职了。
我觉得每个码农对技术都是有一定的执着的,前天面试完后,自己也上网看了一些文章,大多都只说了事务的传播类型,及各种类型的表现,根本没有我想要的答案,于是,昨天我联系了一下当年培训时的苍老师,他听了题目和我的答案后,也是“呵呵”一笑,说这是Spring认证考试中的原题,被考到这一题的概率至少有70%,而且,最近好多公司都直接拿Spring题库里的题当面试题……然后他就让我等着,过一会给我发了个压缩包,是一份Demo代码,果然是人狠话不多,直接拿代码讲道理,我看了看代码,按照苍老师在代码里留的注释改动了几下,基本上就有答案了!
虽然答案本身很简单,但是又领悟了不少东西,为了“纪念”一下这个错题,和大家分享一下Spring中@Transactional
的细节!
首先,项目结构是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sr0TLW2I-1625749860880)(image-20210630170414935.png)]
这个项目中主要用到了Spring、Mybatis和单元测试,比较基础的环境搭建和配置就不说了,如果需要这个代码的,可以从 http:// 下载。
大概就是:项目中使用了t_user
和t_order
这2张表,且都有几条初始数据,在这2张表对应的持久层都编写了根据id修改数据的功能。
重点是业务部分,我们都知道,事务是在业务层进行管理的,业务层的结构是这样的(暂时不用的先不贴出来):
[src]
[main]
[java]
[cn.tedu]
[service]
[impl]
UserServiceImpl
UserService
很显然,以User
前缀开头的都是处理t_user
表的数据的,在最初的实验中只需要观察这1张表就可以了。
关于UserService
接口:
package cn.tedu.service;
public interface UserService {
void update1();
void update2();
}
关于UserServiceImpl
类:
package cn.tedu.service.impl;
import cn.tedu.mapper.UserMapper;
import cn.tedu.service.OrderService;
import cn.tedu.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
private OrderService orderService;
public UserServiceImpl(UserMapper userMapper, OrderService orderService) {
this.userMapper = userMapper;
this.orderService = orderService;
}
// TODO-01:调整是否使用以下@Transactional注解,并运行单元测试,以观察效果
// @Transactional
public void update1() {
int rows;
// 更新id=1的数据,会成功
rows = userMapper.updateUserNameById(1, “USER-1000001”);
if (rows != 1) {
throw new RuntimeException(“更新User:id=1数据失败!”);
}
// 更新id=1000000,会失败
rows = userMapper.u 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 pdateUserNameById(1000000, “USER-1000001”);
if (rows != 1) {
throw new RuntimeException(“更新User:id=1000000数据失败!”);
}
}
// TODO-02:调整是否使用以下@Transactional注解,并运行单元测试,以观察效果
@Transactional
public void update2() {
update1();
}
}
可以看到,以上update1()
方法中有2次更新操作,第1次肯定会成功的,第2次则会因为id值不存在而失败,失败后抛出了RuntimeException
对象,符合Spring管理事务的默认回滚规则,但是,update1()
方法不一定有@Transactional
注解,这是苍老师留着我自己测试效果的,下面的update2()
就比较简单了,它直接调用了update1()
方法。
苍老师写的测试也非常有趣,使用了@Sql
注解处理初始化数据库与数据,使用了断言,和我们平时偷懒写的完全不同,那天我也问过他,他说Spring认证考试也会考这个,以后搞不好也会成为用人单位的面试题(毕竟有不少用人单位都是直接上网百度找面试题,根本不自己出错,大家都懂的)……他是这么写的:
package cn.tedu.service;
import cn.tedu.config.ApplicationConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringJUnitConfig(ApplicationConfig.class)
@Sql(config = @SqlConfig(dataSource = “dataSource”),
scripts = {“classpath:/sql/schema.sql”, “classpath:/sql/data.sql”})
public class UserServiceTests {
@Autowired
UserService userService;
@Test
public void testUpdate1() {
assertThrows(RuntimeException.class, () -> {
userService.update1();
});
}
@Test
public void testUpdate2() {
assertThrows(RuntimeException.class, () -> {
userService.update2();
});
}
}
其实,现在就可以测试出效果了,根据在业务类中的2个方法上是否使用@Transactional
注解,观察数据是否回滚即可判断,我测试的结果如下:
| 是否在update1()
上使用注解 | 是否在update2()
上使用注解 | 是否回滚 |
| — | — | — |
| 是 | 是 | 是 |
| 否 | 是 | 是 |
| 是 | 否 | 否 |
| 否 | 否 | 否 |
可以看到,事务是否回滚完全取决于update2()
方法有没有@Transactional
注解,与update1()
方法是否有注解无关!
苍老师说,Spring官方给出的文档中明确指出:Propagation Rules Are Enforced by a Proxy,即“传播规则是由代理强制执行的”。所以,Spring管理事务是基于接口进行代理的,在调用@Transactional
注解的方法之前就会开启事务,并在过程中决定是否回滚或最终提交!
在以上代码中,由于update1()
是在update2()
内部调用的,不是由代理对象来调用的,所以,执行update2()
方法的过程大致上是:
开启事务
执行update2()方法
调用update1()方法
因update1()方法抛出异常且符合回滚规则,执行回滚事务
若未出现回滚,则提交事务(本例会回滚,不会执行这一步)
所以,回到我面试的那个题目,正确的答案应该是:只会在调用update2()
方法时开启1个事务,内部调用的update1()
根本不是事务性的(不管有没有@Transactional
注解),既然只有1个事务,也就不存在事务的传播了!
其实,到这里,我的问题已经解决了,但是苍老师还帮我写好了后续的Demo代码,让我更深刻的理解,这可能就是老师的职业病吧,要么不讲,要讲就一讲到底。
接下来就要涉及更新t_order
表的数据了,对应的业务接口和业务实现类分别是OrderService
和OrderServiceImpl
,关于OrderService
接口:
package cn.tedu.service;
public interface OrderService {
void updateSuccessfully();
}
关于OrderServiceImpl
类:
package cn.tedu.service.impl;
import cn.tedu.mapper.OrderMapper;
import cn.tedu.service.OrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderServiceImpl implements OrderService {