@Size注解测试未抛出DataIntegrityViolationException问题排查
核心问题根源
你混淆了Bean Validation注解(如@Size、@NotBlank)和数据库约束的作用边界:
@Size、@NotBlank属于JSR-380规范的应用层校验注解,仅在触发Bean Validation逻辑时生效,不会直接转换为数据库约束。DataIntegrityViolationException是数据库层面抛出的异常,仅当违反数据库约束(如@Column(unique=true)、nullable=false)时才会触发。- Spring Data JPA的
save()方法默认不会自动触发Bean Validation,因此直接调用repository.save()时,@Size注解的校验逻辑根本没执行。
另外你的依赖版本存在兼容性问题:Spring Boot 3.x基于Jakarta EE 9,需要搭配Hibernate Validator 8.x(而非你手动指定的6.0.12.Final),旧版本无法正常适配。
具体修复步骤
1. 修正Maven依赖
移除手动添加的hibernate-validator依赖,Spring Boot的spring-boot-starter-web或spring-boot-starter-data-jpa已经默认引入了适配版本的校验依赖:
<!-- 移除这一段 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.12.Final</version> </dependency>
2. 开启JPA自动校验
在src/test/resources/application-test.properties中添加配置,让JPA在保存/更新实体时自动触发Bean Validation:
spring.jpa.properties.javax.persistence.validation.mode=auto
3. 修改测试用例
针对@Size的校验,需要捕获应用层异常ConstraintViolationException,而非数据库层面的DataIntegrityViolationException,同时调整测试数据(空字符串会先触发@NotBlank校验,无法测试@Size):
@Test void givenShortTopicName_whenSaveTopic_thenThrowConstraintViolationException() { // 用长度为2的字符串触发@Size校验 TopicEntity topic = TopicEntity.builder() .name("ab") .build(); assertThrows(ConstraintViolationException.class, () -> repository.save(topic)); }
校验最佳实践
1. 分层校验策略
Controller层:用
@Valid或@Validated注解触发请求参数校验,提前拦截无效请求:@PostMapping("/topics") public ResponseEntity<TopicEntity> createTopic(@Valid @RequestBody TopicEntity topic) { return ResponseEntity.ok(topicService.save(topic)); }此时会抛出
MethodArgumentNotValidException,可通过全局异常处理器统一返回错误信息。Service层:手动调用Validator做业务逻辑校验,确保进入数据库前数据合法:
@Service public class TopicService { private final Validator validator; private final TopicRepository repository; public TopicService(Validator validator, TopicRepository repository) { this.validator = validator; this.repository = repository; } public TopicEntity saveTopic(TopicEntity topic) { Set<ConstraintViolation<TopicEntity>> violations = validator.validate(topic); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } return repository.save(topic); } }
2. 高效测试Bean Validation
不需要启动Spring上下文,直接测试Validator逻辑,提升测试速度:
class TopicEntityValidationTest { private Validator validator; @BeforeEach void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test void whenTopicNameTooShort_thenValidationFails() { TopicEntity topic = TopicEntity.builder().name("ab").build(); Set<ConstraintViolation<TopicEntity>> violations = validator.validate(topic); assertThat(violations).hasSize(1); assertThat(violations.iterator().next().getMessage()) .isEqualTo("Topic name must be between 3 and 200 characters"); } }
3. 数据库约束作为最后防线
数据库层面的约束(如@Column(unique=true)、nullable=false)仅作为兜底机制,不要依赖它做业务校验,应用层提前拦截能减少数据库交互开销。
内容的提问来源于stack exchange,提问作者Ernesto Orozco

