You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用@DataJpaTest测试@Transactional事务回滚机制?

问题:@DataJpaTest中无法测试@Transactional的事务回滚机制

场景复现

在本地PostgreSQL环境中运行时,带有@Transactional注解的Controller方法会在抛出RuntimeException时正确回滚事务(数据库表为空),但使用@DataJpaTest搭配H2数据库测试时,事务回滚未生效,测试失败——执行完方法后能查询到状态为FAIL的实体。

核心代码示例

Controller类

@RestController
public class MyController {

    private final MyRepository myRepository;
    private final MyService    myService;

    public MyController(MyRepository myRepository, MyService myService) {
        this.myRepository = myRepository;
        this.myService = myService;
    }

    @GetMapping
    @Transactional
    public String justForTest() {
        var myEntity = new MyEntity().setStatus("NEW");
        myRepository.save(myEntity);
        try {
            myService.throwsAnRuntimeException();
            // 服务调用成功时更新状态
            myEntity.setStatus("SUCCESS");
        } catch (Exception e) {
            // 服务调用失败时更新状态
            myEntity.setStatus("FAIL");
            throw e;
        } finally {
            myRepository.save(myEntity);
        }
        return "this is the end";
    }
}

实体类

@Data
@Entity
@Accessors(chain = true)
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String status;
}

Repository接口

@Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
}

Service类

@Service
public class MyService {
    public void throwsAnRuntimeException() {
        throw new RuntimeException("Rollbacks not working!");
    }
}

测试类(原失败版本)

@DataJpaTest
class MyControllerTest {

    @Autowired
    private MyRepository repository;

    @Test
    void justForTest() {
        var controller = new MyController(repository, new MyService());
        assertThrows(RuntimeException.class, controller::justForTest);
        var afterTestEntities = repository.findAll();
        System.out.println(afterTestEntities.getFirst());
        assertEquals(0, afterTestEntities.size());
    }
}

失败原因

@DataJpaTest是Spring的切片测试注解,仅加载JPA相关组件(Repository、EntityManager等),不会自动加载@RestController和@Service类。你手动new出来的MyController对象并非Spring容器管理的代理对象,因此方法上的@Transactional注解完全不生效——所有数据库操作都在测试方法自带的事务中执行,而测试方法的事务默认会在测试结束后回滚,但在测试执行过程中,同一个事务内可以查询到未提交的实体数据,导致断言失败。

解决方案

要让@Transactional注解生效,必须让MyController和MyService被Spring容器管理,从而生成带有事务代理的Bean。修改测试类如下:

@DataJpaTest
@AutoConfigureWebMvc // 加载WebMvc相关配置,支持Controller注入
@Import({MyController.class, MyService.class}) // 手动导入需要的Bean
class MyControllerTest {

    @Autowired
    private MyController controller; // 注入Spring管理的代理对象

    @Autowired
    private MyRepository repository;

    @Test
    void justForTest() {
        assertThrows(RuntimeException.class, controller::justForTest);
        // Controller的事务已回滚,数据库无数据
        assertEquals(0, repository.findAll().size());
    }
}

原理说明

  1. @Import注解将MyController和MyService纳入Spring容器管理,Spring会为带有@Transactional注解的方法生成代理对象;
  2. 调用代理对象的justForTest方法时,Spring会开启独立事务,方法抛出RuntimeException后自动回滚所有数据库操作;
  3. 测试方法查询数据库时,由于Controller的事务已回滚,因此查询结果为空,断言通过。

内容的提问来源于stack exchange,提问作者snej0keee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 21:03:12