Search code examples
javapostgresqlspring-bootforeign-keysone-to-many

Spring boot @OneToMany and @ManyToOne don't save foreign key


I am using @OneToMany and @ManyToOne in spring boot and when I try to save to the database (PostgresSQL) the foreign key is not saved. I tried to follow all the instructions and settings described but when I check on the DB the table “emitters”, all the saved elements always have the foreign key null

enter image description here

package org.cnr.plantvocdb.entity;

import java.time.OffsetDateTime;
import java.util.Set;
import java.util.UUID;
import jakarta.persistence.*;

import lombok.*;
import org.apache.commons.lang3.StringUtils;
import org.cnr.plantvocdb.enums.LeafHabitus;
import org.cnr.plantvocdb.enums.PlantsRanks;

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
@Table(name = "plants_voc")
public class PlantVocEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name="id", length = 50, nullable = false, updatable = false)
    private UUID id;

    @Column(name="ipni", length = 50)
    private String ipni;

    @Column(name="full_name_plain", length = 50)
    private String fullNamePlain;

    @Column(name="full_name_no_authors_plain", length = 50)
    private String fullNameNoAuthorsPlain;

    @Setter(AccessLevel.NONE)
    @Column(name="name", length = 30, nullable = false)
    private String name;

    @Setter(AccessLevel.NONE)
    @Column(name="family", length = 30, nullable = false)
    private String family;

    @Setter(AccessLevel.NONE)
    @Column(name="genus", length = 20, nullable = false)
    private String genus;

    @Setter(AccessLevel.NONE)
    @Column(name="species", length = 20)
    private String species;

    @Column(name="valid_nomenclature")
    private boolean validNomenclature;

    @Column(name="rank")
    @Enumerated(EnumType.STRING)
    private PlantsRanks rank;

    @Column(name="leaf_habitus")
    @Enumerated(EnumType.STRING)
    private LeafHabitus leafHabitus;

    @OneToMany(
            fetch = FetchType.EAGER,
            cascade = CascadeType.ALL,
            mappedBy = "plant"
    )
    private Set<PlantEmitterEntity> emitter;

    @ElementCollection
    @Column(name="synonyms")
    private Set<String> synonyms;

    @Column(name="created_datetime_utc", updatable = false) // creation_datetime_utc
    private OffsetDateTime createdDatetimeUTC;

    @Column(name="updated_datetime_utc")  // last_modified_datetime_utc
    private OffsetDateTime updatedDatetimeUTC;

    public void setName(String name) {

        this.name = name.toLowerCase();
    }

    public void setFamily(String family) {

        this.family = StringUtils.capitalize(family.toLowerCase());
    }

    public void setGenus(String genus) {

        this.genus = StringUtils.capitalize(genus.toLowerCase());
    }

    public void setSpecies(String species) {

        this.species = species.toLowerCase();
    }

}



package org.cnr.plantvocdb.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name="emitters")
@Getter
@Setter
public class PlantEmitterEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="emits")
    private boolean emits;

    @Column(name="doi")
    private String doi;

    @ManyToOne
    @JoinColumn(name = "fk_plant_id", nullable = false, updatable = true, insertable = true)
    private PlantVocEntity plant;

}


package org.cnr.plantvocdb.service;

import org.cnr.plantvocdb.dto.RequestPlantVocDTO;
import org.cnr.plantvocdb.dto.ResponsePlantVocDTO;
import org.cnr.plantvocdb.entity.PlantVocEntity;
import org.cnr.plantvocdb.repository.PlantsVocRepository;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;

@Service
public class PlantsVocService {

    private final PlantsVocRepository repository;
    private final ModelMapper mapper;


    @Autowired
    public PlantsVocService(PlantsVocRepository repository, ModelMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;

    }

    public ResponsePlantVocDTO CreateNewPlantVoc(RequestPlantVocDTO plantDTO){

        // map DTO to Entity
        PlantVocEntity plantEntity = mapper.map(plantDTO, PlantVocEntity.class);

        // set datetime in UTC
        OffsetDateTime odt = OffsetDateTime.now(ZoneOffset.UTC);
        plantEntity.setCreatedDatetimeUTC(odt);
        plantEntity.setUpdatedDatetimeUTC(odt);

        // save new plant in DB
        PlantVocEntity savedPlantEntity = repository.save(plantEntity);

        // map Entity to DTO
        return mapper.map(savedPlantEntity, ResponsePlantVocDTO.class);
    }


}

UPDATE 1 by suggestion of @Roar S.

enter image description here

UPDATE 2 by suggestion of @Roar S. (works)

enter image description here


Solution

  • Update #2

    As I suspected, PlantEmitterEntity#plant is null before save. OP is using a mapping library and writes in a comment that problem cannot be solved with that. I asked OP to just loop through all PlantEmitterEntity instances and set plant before save.

    Something like this

    plantEntity.getEmitter().forEach(it -> it.setPlant(plantEntity));
    

    You may need a null-check on plantEntity.getEmitter() depending on what your mapper does.


    Update #1

    OP added code for the service class, but we don't now what's happening in this line:

    PlantVocEntity plantEntity = mapper.map(plantDTO, PlantVocEntity.class);
    

    You don't show code for the service that performs the save-operation, but I suspect that you don't initialize PlantEmitterEntity#plant.

    In class PlantVocEntity, add this method for adding PlantEmitterEntity instances

        // initialize emitter
        private Set<PlantEmitterEntity> emitter = new HashSet<>();
        
        void addPlantEmitterEntity(PlantEmitterEntity emitterEntity) {
            emitterEntity.setPlant(this);
            emitter.add(emitterEntity);
        }
    

    and use this method in your service class.