Spring Boot Data JPA双向关联外键为空问题求助
问题:Spring Boot JPA 关联实体保存后外键为空
我正在开发一个超级英雄与反派的Spring Boot演示项目,使用JPA实现实体关联。抽象类Character被Superhero和Villain继承,Team与Character是一对多关系(一个团队包含多个角色,一个角色仅属于一个团队)。初始化数据后,数据库中superheroes表的team_id字段始终为空,尝试过添加referencedColumnName、调整级联类型、查询后再设置属性等方法都没解决。
实体代码
抽象Character类
@Data @NoArgsConstructor @Entity @Table(name = "characters") @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = Superhero.class, name = "superhero"), @JsonSubTypes.Type(value = Villain.class, name = "villain")}) public abstract class Character { @Id @GeneratedValue(strategy = GenerationType.TABLE) private Integer id; private String firstName; private String lastName; @Transient private String fullName; @Convert(converter = GenderConverter.class) private Gender gender; @Column(nullable = false) private String alias; @Convert(converter = StatusConverter.class) private Status status; @ManyToOne @JoinColumn(name = "team_id") private Team team; private String originStory; @Convert(converter = UniverseConverter.class) private Universe universe; public Character(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Character(String firstName, String lastName, String alias) { this.firstName = firstName; this.lastName = lastName; this.alias = alias; } public String getFullName() { return firstName + " " + lastName; } }
Team类
@Data @Builder @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "teams") public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; @OneToMany(mappedBy = "team", cascade = CascadeType.ALL) private List<Character> characters; @Convert(converter = StatusConverter.class) private Status status; }
初始化数据代码
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } /** Disable before running Tests! */ @Bean public CommandLineRunner commandLineRunner(CharacterRepository characterRepository, TeamRepository teamRepository) { return args -> { // Initilaizing Superhero ironMan = new Superhero("Tony", "Stark", "Iron Man"); Superhero thor = new Superhero("Thor", "Odinson", "God of Thunder"); Team avengers = Team.builder().name("Avengers").status(DISBANDED).build(); // Setting attributes ironMan.setGender(MALE); thor.setGender(MALE); ironMan.setUniverse(MCU); thor.setUniverse(MCU); ironMan.setStatus(DECEASED); thor.setStatus(ALIVE); // Saving to repositories characterRepository.saveAll(List.of(ironMan, thor)); teamRepository.save(avengers); // Trying to assign foreign keys avengers.setCharacters(List.of(ironMan, thor)); ironMan.setTeam(avengers); thor.setTeam(avengers); }; } }
数据库查询结果
demo2=# SELECT * FROM teams; id | name | status ----+----------+----------- 1 | Avengers | Disbanded (1 row) demo2=# SELECT * FROM superheroes; id | team_id | alias | first_name | gender | last_name | origin_story | status | universe ----+---------+----------------+------------+--------+-----------+--------------+----------+---------- 1 | | Iron Man | Tony | Male | Stark | | Deceased | MCU 2 | | God of Thunder | Thor | Male | Odinson | | Alive | MCU (2 rows)
问题原因
- 关联维护端逻辑:
@OneToMany(mappedBy = "team")明确了Character是关联的维护端(持有外键team_id),只有修改维护端的team属性并保存,才会触发数据库外键更新;Team的characters集合是被维护端,仅设置该集合不会同步到数据库。 - 未保存关联变更:当前代码先保存了
Character和Team,之后才设置关联关系,但没有重新保存Character实体,JPA无法感知后续的关联修改。
解决方案
方案一:先建立关联再级联保存
利用Team的CascadeType.ALL配置,先建立双向关联,再保存Team即可自动保存关联的Character,同时填充外键:
@Bean public CommandLineRunner commandLineRunner(CharacterRepository characterRepository, TeamRepository teamRepository) { return args -> { // 初始化实体 Superhero ironMan = new Superhero("Tony", "Stark", "Iron Man"); Superhero thor = new Superhero("Thor", "Odinson", "God of Thunder"); Team avengers = Team.builder().name("Avengers").status(DISBANDED).build(); // 建立双向关联 ironMan.setTeam(avengers); thor.setTeam(avengers); avengers.setCharacters(List.of(ironMan, thor)); // 设置其他属性 ironMan.setGender(MALE); thor.setGender(MALE); ironMan.setUniverse(MCU); thor.setUniverse(MCU); ironMan.setStatus(DECEASED); thor.setStatus(ALIVE); // 级联保存Team及关联的Character teamRepository.save(avengers); }; }
方案二:先保存Team,再关联并保存Character
先保存Team获取主键ID,再给Character设置关联属性并保存:
@Bean public CommandLineRunner commandLineRunner(CharacterRepository characterRepository, TeamRepository teamRepository) { return args -> { // 先保存Team获取ID Team avengers = Team.builder().name("Avengers").status(DISBANDED).build(); teamRepository.save(avengers); // 初始化角色并设置关联 Superhero ironMan = new Superhero("Tony", "Stark", "Iron Man"); Superhero thor = new Superhero("Thor", "Odinson", "God of Thunder"); ironMan.setTeam(avengers); thor.setTeam(avengers); avengers.setCharacters(List.of(ironMan, thor)); // 设置其他属性 ironMan.setGender(MALE); thor.setGender(MALE); ironMan.setUniverse(MCU); thor.setUniverse(MCU); ironMan.setStatus(DECEASED); thor.setStatus(ALIVE); // 保存角色,触发外键更新 characterRepository.saveAll(List.of(ironMan, thor)); }; }
方案三:原有顺序下重新保存关联实体
如果坚持原有操作顺序,需在设置关联后重新保存Character:
// Trying to assign foreign keys avengers.setCharacters(List.of(ironMan, thor)); ironMan.setTeam(avengers); thor.setTeam(avengers); // 重新保存角色,同步外键到数据库 characterRepository.saveAll(List.of(ironMan, thor));
内容的提问来源于stack exchange,提问作者saba morchilashvili
相关产品推荐
相关产品推荐

