@DataJpaTest单元测试忽略@Column的unique、nullable约束问题
问题成因
- JPA的
save()方法不会立刻执行数据库写入操作。@DataJpaTest标注的测试类默认每个测试方法都是事务性的,执行结束后自动回滚,调用save()时仅会把实体放到Hibernate的一级缓存(持久化上下文)里,不会立刻把SQL同步到数据库,配置在表上的非空、唯一键等数据库约束自然没有触发的机会。此时方法返回的就是你传入的实体对象,既不会返回null,也不会抛出异常。 - 很多人对
@Column注解的作用有误解:nullable = false、unique = true这类属性,仅在Hibernate自动生成DDL建表时用来定义表结构,JPA本身不会在Java代码层基于这些属性做字段校验。平时业务代码能正常触发约束报错,是因为业务请求处理完事务提交时,Hibernate会自动把缓存里的变更flush到数据库,执行SQL时触发了数据库层面的约束校验,不是save()方法本身做了校验。 - 你写的断言逻辑本身就不符合实际执行逻辑:就算真的触发了约束违反,代码会直接抛出异常中断执行,根本不会走到返回值赋值的步骤,
assertNull(savedUser)永远不可能捕获到约束违反的场景。
解决方法
- 测试数据库约束时,必须手动触发flush操作,强制Hibernate把持久化上下文里待执行的SQL同步到数据库,才能触发约束校验。可以在
save()之后手动调用userRepository.flush(),也可以直接调用JPA提供的saveAndFlush()方法代替save()。 - 修正断言逻辑:数据库约束违反时会抛出
DataIntegrityViolationException(Spring封装的异常)或者底层的ConstraintViolationException,不要判断返回值是否为null,直接用JUnit5的assertThrows验证对应异常是否抛出即可。 - 如果希望在不访问数据库的情况下提前做字段合法性校验,可以在实体字段上额外添加Bean Validation标准注解(比如
@NotBlank校验非空、@Email校验邮箱格式),配合持久化层的校验机制在Java侧提前拦截非法参数,但要注意这层校验不能替代数据库层面的约束,两者是互补关系。
修正后的测试代码参考:
@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class UserRepositoryTests { @Autowired private UserRepository userRepository; @Test void ifEmailIsNullMustThrowException() { // Arrange User user = User.builder() .name("John") .lastName("Doe") .password("password") .phoneNumber("123456789") .build(); // Act & Assert assertThrows(DataIntegrityViolationException.class, () -> { userRepository.saveAndFlush(user); }); } @Test void ifAUserExistWithGivenEmailMustThrowException() { // Arrange User user1 = User.builder() .name("John") .lastName("Doe") .email("johndoe@email.com") .password("password") .phoneNumber("123456789") .build(); User user2 = User.builder() .name("John") .lastName("Doe") .email("johndoe@email.com") .password("password") .phoneNumber("123456789") .build(); userRepository.saveAndFlush(user1); // Act & Assert assertThrows(DataIntegrityViolationException.class, () -> { userRepository.saveAndFlush(user2); }); } }
提示:如果测试时用的是自动配置的内存数据库(比如H2),要确认Hibernate已经正确创建了带对应约束的表结构;如果像你现在这样配置了
AutoConfigureTestDatabase.Replace.NONE直接使用和业务一致的数据库连接,只要表结构和业务环境一致就不会有问题。
内容的提问来源于stack exchange,提问作者Soroush Shemshadi
相关产品推荐
相关产品推荐

