You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 01:40:32