Spring JPA OneToMany关联无法正确保存至PostgreSQL
问题:Spring JPA OneToMany关联保存时外键字段为空
我在数据库中实现OneToMany关联关系,使用Spring、JPA、Mapper及Repository操作数据,大部分功能正常,但保存包含PompierJpa和CamionJpa的CaserneJpa时,pompiers和camions表的caserne_id字段未被正确填充。
JPA实体类
@Getter @Setter @Entity @Table(name = "casernes") public class CaserneJpa { @Id @Nonnull private Integer id; @Embedded private CoordonneeJpa coordonnee; @OneToMany(mappedBy="caserne", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<CamionJpa> camions; @OneToMany(mappedBy="caserne", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<PompierJpa> pompiers; } @Getter @Setter @Entity @Table(name = "interventions") public class InterventionJpa { @Id @Nonnull private UUID id; private Integer idCapteur; private Date date; private CoordonneeJpa coordonnee; @OneToMany(mappedBy="intervention", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<CamionJpa> camions; @OneToMany(mappedBy="intervention", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<PompierJpa> pompiers; } @Getter @Setter @Entity @Table(name = "camions") public class CamionJpa { @Id @Nonnull private Integer id; private Integer places; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "caserne_id") private CaserneJpa caserne; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "intervention_id") private InterventionJpa intervention; } @Getter @Setter @Entity @Table(name = "pompiers") public class PompierJpa { @Id @Nonnull private Integer id; private String prenom; private String nom; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "caserne_id") private CaserneJpa caserne; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "intervention_id") private InterventionJpa intervention; }
测试代码
public void create() { Pompier pompier = new Pompier(); Pompier pompier2 = new Pompier(); Camion camion = new Camion(); Camion camion2 = new Camion(); pompier.setId(1); pompier.setNom("Gauchoux"); pompier.setPrenom("Xav"); pompier2.setId(2); pompier2.setNom("Biosca"); pompier2.setPrenom("Coco"); camion.setId(1); camion.setPlaces(2); camion2.setId(2); camion2.setPlaces(1); ArrayList<Pompier> listPompier = new ArrayList<>(); listPompier.add(pompier); listPompier.add(pompier2); ArrayList<Camion> listCamion = new ArrayList<>(); listCamion.add(camion); listCamion.add(camion2); Coordonnee c = new Coordonnee(); c.setLatitude("1"); c.setLongitude("1"); Caserne caserne = new Caserne(); caserne.setId(1); caserne.setCamions(listCamion); caserne.setPompiers(listPompier); caserne.setCoordonnee(c); caserneRepository.save(caserneMapper.toCaserneJpa(caserne)); }
执行日志
Hibernate: create table camions ( id integer not null, places integer, caserne_id integer, intervention_id uuid, primary key (id) ) Hibernate: create table casernes ( id integer not null, latitude varchar(255), longitude varchar(255), primary key (id) ) Hibernate: create table interventions ( id uuid not null, latitude varchar(255), longitude varchar(255), date timestamp(6), id_capteur integer, primary key (id) ) Hibernate: create table pompiers ( id integer not null, nom varchar(255), prenom varchar(255), caserne_id integer, intervention_id uuid, primary key (id) ) Hibernate: alter table if exists camions add constraint FKom9w5spol5vd1ix3oxfde4on0 foreign key (caserne_id) references casernes Hibernate: alter table if exists camions add constraint FKantgpwa0an7ktlsewuvjlbpnu foreign key (intervention_id) references interventions Hibernate: alter table if exists pompiers add constraint FKjeg8ji8qlukn6gnrfs5p26tlu foreign key (caserne_id) references casernes Hibernate: alter table if exists pompiers add constraint FK60xhoettofkdthm00wdt9rvmp foreign key (intervention_id) references interventions ... Started EmergencymanagerApplication in 3.87 seconds (process running for 4.473) Hibernate: select c1_0.id, c2_0.caserne_id, c2_0.id, i1_0.id, i1_0.latitude, i1_0.longitude, i1_0.date, i1_0.id_capteur, c2_0.places, c1_0.latitude, c1_0.longitude from casernes c1_0 left join camions c2_0 on c1_0.id=c2_0.caserne_id left join interventions i1_0 on i1_0.id=c2_0.intervention_id where c1_0.id=? Hibernate: select c1_0.id, c2_0.id, c2_0.latitude, c2_0.longitude, p1_0.caserne_id, p1_0.id, i1_0.id, i1_0.latitude, i1_0.longitude, i1_0.date, i1_0.id_capteur, p1_0.nom, p1_0.prenom, i2_0.id, i2_0.latitude, i2_0.longitude, i2_0.date, i2_0.id_capteur, c1_0.places from camions c1_0 left join casernes c2_0 on c2_0.id=c1_0.caserne_id left join pompiers p1_0 on c2_0.id=p1_0.caserne_id left join interventions i1_0 on i1_0.id=p1_0.intervention_id left join interventions i2_0 on i2_0.id=c1_0.intervention_id where c1_0.id=? Hibernate: select c1_0.id, c2_0.id, c2_0.latitude, c2_0.longitude, p1_0.caserne_id, p1_0.id, i1_0.id, i1_0.latitude, i1_0.longitude, i1_0.date, i1_0.id_capteur, p1_0.nom, p1_0.prenom, i2_0.id, i2_0.latitude, i2_0.longitude, i2_0.date, i2_0.id_capteur, c1_0.places from camions c1_0 left join casernes c2_0 on c2_0.id=c1_0.caserne_id left join pompiers p1_0 on c2_0.id=p1_0.caserne_id left join interventions i1_0 on i1_0.id=p1_0.intervention_id left join interventions i2_0 on i2_0.id=c1_0.intervention_id where c1_0.id=? Hibernate: select p1_0.id, c1_0.id, c2_0.caserne_id, c2_0.id, i1_0.id, i1_0.latitude, i1_0.longitude, i1_0.date, i1_0.id_capteur, c2_0.places, c1_0.latitude, c1_0.longitude, i2_0.id, i2_0.latitude, i2_0.longitude, i2_0.date, i2_0.id_capteur, p1_0.nom, p1_0.prenom from pompiers p1_0 left join casernes c1_0 on c1_0.id=p1_0.caserne_id left join camions c2_0 on c1_0.id=c2_0.caserne_id left join interventions i1_0 on i1_0.id=c2_0.intervention_id left join interventions i2_0 on i2_0.id=p1_0.intervention_id where p1_0.id=? Hibernate: select p1_0.id, c1_0.id, c2_0.caserne_id, c2_0.id, i1_0.id, i1_0.latitude, i1_0.longitude, i1_0.date, i1_0.id_capteur, c2_0.places, c1_0.latitude, c1_0.longitude, i2_0.id, i2_0.latitude, i2_0.longitude, i2_0.date, i2_0.id_capteur, p1_0.nom, p1_0.prenom from pompiers p1_0 left join casernes c1_0 on c1_0.id=p1_0.caserne_id left join camions c2_0 on c1_0.id=c2_0.caserne_id left join interventions i1_0 on i1_0.id=c2_0.intervention_id left join interventions i2_0 on i2_0.id=p1_0.intervention_id where p1_0.id=? Hibernate: insert into casernes (latitude, longitude, id) values (?, ?, ?) Hibernate: insert into camions (caserne_id, intervention_id, places, id) values (?, ?, ?, ?) Hibernate: insert into camions (caserne_id, intervention_id, places, id) values (?, ?, ?, ?) Hibernate: insert into pompiers (caserne_id, intervention_id, nom, prenom, id) values (?, ?, ?, ?, ?) Hibernate: insert into pompiers (caserne_id, intervention_id, nom, prenom, id) values (?, ?, ?, ?, ?)
结果
表结构创建正确,数据插入成功,但camions和pompiers表的caserne_id字段为空。
已尝试方法
调整OneToMany和ManyToOne注解的cascade参数,问题未解决。
解决方案
问题出在双向关联未维护双方关系,JPA中mappedBy标记的是关系由多的一方(CamionJpa、PompierJpa)维护,因此必须在子实体中设置关联的父实体。
1. 修改测试代码,维护双向关系
在创建Pompier和Camion后,设置它们所属的Caserne:
public void create() { Pompier pompier = new Pompier(); Pompier pompier2 = new Pompier(); Camion camion = new Camion(); Camion camion2 = new Camion(); pompier.setId(1); pompier.setNom("Gauchoux"); pompier.setPrenom("Xav"); pompier2.setId(2); pompier2.setNom("Biosca"); pompier2.setPrenom("Coco"); camion.setId(1); camion.setPlaces(2); camion2.setId(2); camion2.setPlaces(1); Coordonnee c = new Coordonnee(); c.setLatitude("1"); c.setLongitude("1"); Caserne caserne = new Caserne(); caserne.setId(1); caserne.setCoordonnee(c); // 关键:维护子实体的关联关系 pompier.setCaserne(caserne); pompier2.setCaserne(caserne); camion.setCaserne(caserne); camion2.setCaserne(caserne); ArrayList<Pompier> listPompier = new ArrayList<>(); listPompier.add(pompier); listPompier.add(pompier2); ArrayList<Camion> listCamion = new ArrayList<>(); listCamion.add(camion); listCamion.add(camion2); caserne.setCamions(listCamion); caserne.setPompiers(listPompier); caserneRepository.save(caserneMapper.toCaserneJpa(caserne)); }
2. 优化实体类,添加关联维护工具方法
为了避免手动维护关系时出错,建议在CaserneJpa中添加添加子实体的方法,自动维护双方关系:
@Getter @Setter @Entity @Table(name = "casernes") public class CaserneJpa { // ... 现有字段 public void addCamion(CamionJpa camion) { this.camions.add(camion); camion.setCaserne(this); } public void addPompier(PompierJpa pompier) { this.pompiers.add(pompier); pompier.setCaserne(this); } }
这样在添加子实体时,直接调用addCamion和addPompier即可,无需手动设置子实体的caserne属性。
3. 注意事项
- 双向关联中,
mappedBy所在的一方是被动方,不负责维护外键;多的一方是主动方,负责维护外键字段。 - 避免设置不必要的
cascade:CamionJpa和PompierJpa的ManyToOne上的CascadeType.ALL可能导致意外的级联操作,建议根据实际需求调整(比如仅保留CascadeType.PERSIST)。
内容的提问来源于stack exchange,提问作者Gochou
相关产品推荐
相关产品推荐

