Hibernate保存主从实体时非空字段约束报错求助
问题
拥有Rule和RuleUnitTest两个实体(一对多关联),创建Rule时会自动生成ruleId,需将该ruleId关联到RuleUnitTest中。相关代码及报错信息如下:
Rule实体
package io.avalor.webserver.rule; import com.vladmihalcea.hibernate.type.array.EnumArrayType; import com.vladmihalcea.hibernate.type.array.ListArrayType; import com.vladmihalcea.hibernate.type.json.JsonBinaryType; import io.avalor.webserver.account.Account; import io.avalor.webserver.customquery.CustomQuery; import io.avalor.webserver.persistency.AbstractEntity; import io.avalor.webserver.rule.ruletest.RuleTest; import io.avalor.webserver.rule.unittest.RuleUnitTest; import lombok.Getter; import lombok.Setter; import org.hibernate.annotations.*; import javax.persistence.*; import javax.persistence.Entity; import javax.persistence.Table; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; import java.util.Set; @Entity @Table @SQLDelete(sql = "UPDATE rule SET deleted = true WHERE rule_id=?") @Where(clause = "deleted=false") @TypeDef(name = "list-array", typeClass = EnumArrayType.class, parameters = {@org.hibernate.annotations.Parameter(name = EnumArrayType.SQL_ARRAY_TYPE, value = "varchar")}) @TypeDef(name = "json", typeClass = JsonBinaryType.class) @TypeDef(name = "list", typeClass = ListArrayType.class) @Getter @Setter public class Rule extends AbstractEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "rule_id") private String id; @Column @NotBlank private String name; @OneToMany(fetch = FetchType.EAGER) @Fetch(value = FetchMode.SUBSELECT) @JoinTable( name = "rule_unit_test", joinColumns = {@JoinColumn(name = "rule_id", updatable = false)}, inverseJoinColumns = {@JoinColumn(name = "test_id", updatable = false)} ) private List<RuleUnitTest> unitTests; }
RuleUnitTest实体
package io.avalor.webserver.rule.unittest; import com.fasterxml.jackson.databind.JsonNode; import io.avalor.webserver.persistency.AbstractEntity; import io.avalor.webserver.rule.Rule; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.*; import javax.persistence.*; import javax.persistence.Entity; import javax.persistence.Table; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.util.List; @Entity @Table @Where(clause = "deleted=false") @SQLDelete(sql = "UPDATE rule_unit_test SET deleted = true WHERE test_id=?") @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class RuleUnitTest { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "test_id") private String id; @NotNull @JoinColumn(name = "rule_id", updatable = false) @ManyToOne(fetch = FetchType.LAZY) private Rule rule; @Column @NotBlank private String name; @NotEmpty @Type(type = "json") @Column(name = "test_data") private List<JsonNode> testData; }
Rule服务层方法
@Transactional public RuleDto create(RuleDto ruleDto) { Rule rule = ruleMapper.dtoToEntity(ruleDto); Rule savedRule = ruleRepository.save(rule); savedRule = saveUnitTestsIfExists(savedRule,ruleDto.getUnitTests()); // ruleRepository.save(savedRule); return ruleMapper.entityToDto(savedRule); } private Rule saveUnitTestsIfExists(Rule rule, List<RuleUnitTestDto> unitTestsDto) { if (CollectionUtils.isNotEmpty(unitTestsDto)) { return unitTestService.create(rule,unitTestsDto); } return rule; }
RuleUnitTest服务层代码
public Rule create(Rule rule, List<RuleUnitTestDto> unitTestsDto) { List<RuleUnitTestDto> limitedRuleUnitTestDtos = unitTestsDto; if (unitTestsDto.size() > MAX_TESTS_PER_RULE) { limitedRuleUnitTestDtos = unitTestsDto.subList(0, MAX_TESTS_PER_RULE); } List<RuleUnitTest> unitTests = ruleUnitTestMapper.buildBuildUnitTestFromUnitTest(rule, limitedRuleUnitTestDtos); rule.setUnitTests(unitTestRepository.saveAll(unitTests)); return rule; }
Mapper代码
package io.avalor.webserver.rule; import io.avalor.webserver.rule.unittest.RuleUnitTest; import io.avalor.webserver.rule.unittest.RuleUnitTestDto; import org.mapstruct.*; import java.util.*; @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class RuleMapper { @Mapping(source = "account.id", target = "accountId") @Mapping(target = "dataSourceTypes", ignore = true) public abstract RuleDto entityToDto(Rule rule); @Mapping(target = "account.id", source = "accountId") @Mapping(target = "dataSourceTypes", ignore = true) public abstract Rule dtoToEntity(RuleDto ruleDto); @AfterMapping public void setRuleProperties(@MappingTarget Rule rule, RuleDto ruleDto) { if (rule.getId() != null) { rule.setUnitTests(buildBuildUnitTestFromUnitTest(rule, ruleDto.getUnitTests())); } else { rule.setUnitTests(null); } } @AfterMapping public RuleDto enrichDto(@MappingTarget RuleDto ruleDto, Rule rule) { if (rule.getUnitTests() != null) { ruleDto.setUnitTests(rule.getUnitTests().stream().map(unitTest -> { RuleUnitTestDto unitTestDto = new RuleUnitTestDto(); unitTestDto.setId(unitTest.getId()); unitTestDto.setName(unitTest.getName()); unitTestDto.setTestData(unitTest.getTestData()); unitTestDto.setRuleId(rule.getId()); return unitTestDto; }).toList()); } return ruleDto; } public List<RuleUnitTest> buildBuildUnitTestFromUnitTest(Rule rule, List<RuleUnitTestDto> unitTestDtos) { if (unitTestDtos == null) return Collections.emptyList(); List<RuleUnitTest> unitTestsList = new ArrayList<>(); for (RuleUnitTestDto unitTestDto : unitTestDtos) { unitTestsList.add(new RuleUnitTest(null, rule, unitTestDto.getName(), unitTestDto.getTestData())); } return unitTestsList; } }
请求体
{ "name": "rule1", "unitTests":[{ "name":"blah blah", "testData":[{"a":"b"}] }] }
报错信息
ERROR: null value in column "name" of relation "rule_unit_test" violates not-null constraint
Detail: Failing row contains (p0n9vr, 07rplw, null, null).
调试时确认name和testData字段并非空值,但仍触发非空约束报错,请问可能的原因是什么?
分析与解决方案
核心原因:双向关联配置冲突导致重复插入
Rule实体中用@JoinTable定义一对多关系,但RuleUnitTest实体本身已经通过@ManyToOne+@JoinColumn(name = "rule_id")维护了外键关联。这种重复配置会让Hibernate执行两次插入:
- 第一次调用
unitTestRepository.saveAll(unitTests)时,正常插入带name、testData和rule_id的记录; - 事务提交时,Hibernate根据Rule的
@JoinTable配置,尝试向rule_unit_test表插入仅含rule_id和test_id的记录,此时name和testData为空,触发非空约束。
解决步骤
- 修正Rule实体的关联配置:移除
@JoinTable,改用mappedBy指定由RuleUnitTest的rule属性维护关联,避免重复操作:
@OneToMany(fetch = FetchType.EAGER, mappedBy = "rule", cascade = CascadeType.ALL) @Fetch(value = FetchMode.SUBSELECT) private List<RuleUnitTest> unitTests;
- 加上
cascade = CascadeType.ALL后,保存Rule时会自动级联保存关联的RuleUnitTest,还能简化代码逻辑。
- 验证构造参数顺序:当前RuleUnitTest的全参构造函数参数顺序(id, rule, name, testData)和创建实例时的参数一致,这部分没问题,但后续修改构造函数要注意对应关系。
额外建议
- 双向关联里别同时用
@JoinTable和@JoinColumn,选一方维护关联即可,通常由多的一方(RuleUnitTest)维护外键更符合数据库设计规范; - 检查
rule_unit_test表的字段定义,确保name和test_data的非空约束和实体类注解一致,避免配置不一致引发问题。
内容的提问来源于stack exchange,提问作者liron29
相关产品推荐
相关产品推荐

