前言
我的数据库中有一列数据需要根据不同条件update为不同的状态值,我直接从ServiceImpl中复制了一个方法到Test中,修改完方法体中的几行代码后,执行Test,控制台打印了对应的update语句,但是数据库中该列的状态没有发生变化。原因就是复制过去的方法上带了 @Transactional 注解,我直接加了一个@Test就开始执行,导致事务回滚了。
现在来补一下相关知识。
在 Spring Boot 的测试类中使用 @Transactional 注解是一个常见且推荐的做法,但它的行为与你在生产代码中使用 @Transactional 有所不同,尤其是在执行修改 SQL(如 INSERT、UPDATE、DELETE)时。以下是详细解释:
1. 默认行为:事务回滚
当你在 Spring Boot 测试类(或测试方法)上使用 @Transactional 注解时,默认情况下,整个测试方法在一个事务中执行,并且在测试方法结束后,事务会自动回滚(rollback) ,而不是提交(commit)。
这意味着:
- 你在测试中执行的修改 SQL(如
UPDATE user SET name = 'test' WHERE id = 1)会生效于当前事务内的数据库操作。 - 其他数据库操作(如查询)能看到这些修改。
- 但一旦测试方法执行完毕,Spring 会自动回滚该事务,所有对数据库的修改都不会永久保存到数据库中。
2. 为什么这样设计?
这是为了保证测试的独立性和可重复性:
- 每个测试运行后,数据库状态恢复到初始状态,不会影响其他测试。
- 避免测试之间产生副作用。
- 无需手动清理数据库数据。
3. 示例代码
@SpringBootTest
@Transactional
class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void testUpdateUser() {
// 查询原始数据
User user = userRepository.findById(1L).orElseThrow();
String originalName = user.getName();
// 执行修改 SQL(通过 Service 或直接 JPA)
userService.updateUserName(1L, "NewName");
// 在当前事务中可以查到修改
User updatedUser = userRepository.findById(1L).orElseThrow();
assertEquals("NewName", updatedUser.getName());
// 测试结束,@Transactional 会导致事务回滚
// 数据库中 id=1 的用户姓名仍为 originalName
}
}
在这个例子中,updateUserName 方法执行的 SQL 修改在测试方法内部是可见的,但测试结束后,数据库不会真正更新。
4. 如何让修改真正提交?
如果你希望某个测试的修改真正提交到数据库(比如集成测试或性能测试),有以下几种方式:
方法一:移除 @Transactional
@SpringBootTest
// 不加 @Transactional,每次修改都会提交
class IntegrationTest {
@Test
void testPersistData() {
// 修改会真正写入数据库
}
}
方法二:使用 @Commit 注解
@SpringBootTest
@Transactional
class UserServiceTest {
@Test
@Commit // 明确告诉 Spring 提交事务,而不是回滚
void testUpdateAndCommit() {
userService.updateUserName(1L, "CommittedName");
// 测试结束后,修改会真正写入数据库
}
}