Spring Boot多ID嵌入式主键接收为Null的问题排查与解决
解决Spring Boot中复合主键(EmbeddedId)JSON绑定为Null的问题
看起来你遇到了Spring Boot REST接口接收复合主键时无法正确绑定的问题,导致两个ID都为Null。这通常是因为Jackson序列化/反序列化规则和JPA嵌入式主键的要求没匹配上,我来帮你一步步解决:
问题根源分析
你的OneTwoKey作为嵌入式主键,需要满足几个关键条件才能被Jackson和JPA正确处理:
- 必须有无参构造函数:Jackson需要无参构造来实例化对象,JPA也要求实体类(包括嵌入式主键类)提供无参构造。
- 字段需要正确的JSON映射:你的JSON里用的是
oneID和twoID,但OneTwoKey里的字段是_oneID,如果没有显式指定JsonProperty,Jackson可能无法正确匹配。 - 嵌入式主键类需要实现equals和hashCode:JPA要求嵌入式主键类重写这两个方法,确保主键的唯一性判断正确。
OneTwo类中@EmbeddedId字段的getter/setter映射可能存在问题。
具体修改方案
1. 修正OneTwoKey类
给它添加无参构造,显式指定JsonProperty,并重写equals和hashCode:
import java.io.Serializable; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.persistence.Column; public class OneTwoKey implements Serializable { @JsonProperty("oneID") @Column(name = "OneID") private int _oneID; @JsonProperty("twoID") @Column(name = "TwoID") private int _twoID; // 必须添加无参构造函数,Jackson和JPA都需要 public OneTwoKey() {} public OneTwoKey(int oneID, int twoID) { this._oneID = oneID; this._twoID = twoID; } // 添加getter和setter,让Jackson能正确赋值 public int getOneID() { return _oneID; } public void setOneID(int oneID) { this._oneID = oneID; } public int getTwoID() { return _twoID; } public void setTwoID(int twoID) { this._twoID = twoID; } // 重写equals和hashCode,JPA判断主键唯一性的核心依据 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OneTwoKey oneTwoKey = (OneTwoKey) o; return _oneID == oneTwoKey._oneID && _twoID == oneTwoKey._twoID; } @Override public int hashCode() { return Objects.hash(_oneID, _twoID); } }
2. 修正OneTwo实体类
确保@EmbeddedId字段的JSON映射正确,同时添加deleted字段的映射(因为你的请求JSON包含这个字段):
import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.persistence.Column; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import org.hibernate.annotations.Where; @Entity(name = "OneTwo") @Where(clause = "deleted='false'") public class OneTwo{ @EmbeddedId @JsonProperty("oneTwo") // 直接给嵌入式主键字段指定JSON键名,和请求中的key对应 private OneTwoKey _id; @Column(name = "deleted") private boolean deleted; // 添加无参构造 public OneTwo() {} public OneTwo(OneTwoKey id) { this._id = id; } // 添加getter和setter public OneTwoKey getId() { return _id; } public void setId(OneTwoKey id) { this._id = id; } public boolean isDeleted() { return deleted; } public void setDeleted(boolean deleted) { this.deleted = deleted; } }
3. 优化控制器代码(可选但推荐)
把Service改成构造注入,让代码更符合Spring的最佳实践:
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class OneTwoController { private final OneTwoService _service; // 构造注入(替代字段注入,更安全、易测试) public OneTwoController(OneTwoService service) { this._service = service; } @PostMapping("/api/one-two") @CrossOrigin public ResponseEntity<ServiceResponse> save(@RequestBody OneTwo model) { return ResponseEntity.ok(_service.save(model)); // 简化写法 } }
为什么这样修改?
- 无参构造:Jackson在反序列化JSON时,会先创建对象实例,再通过setter赋值,所以必须有无参构造才能完成实例化。
@JsonProperty:显式指定JSON字段和Java字段的映射关系,避免因为命名风格(下划线/驼峰)差异导致的匹配失败。- equals和hashCode:JPA在处理嵌入式主键时,依赖这两个方法判断主键是否相同,确保持久化操作的正确性。
- 添加deleted字段:你的请求JSON包含这个字段,实体类必须有对应的字段才能接收,否则会被Jackson忽略。
这样修改后,你的JSON请求就能正确映射到OneTwo对象,复合主键的oneID和twoID也不会再是Null了。
内容的提问来源于stack exchange,提问作者Pablo De Luca
相关产品推荐
相关产品推荐

