Search code examples
javahibernatejpahibernate-mapping

Composite key doesn't get populated by corresponding ID


Here is CampaignEntity.class Entity:

@Entity
@Table(name = "campaign")
public class CampaignEntity implements Serializable {

    @Id
    @SequenceGenerator(name = "campaign_id_gen", sequenceName = "campaign_seq", initialValue = 1, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "campaign_id_gen")
    private Long id;

    @OneToMany(cascade = CascadeType.REMOVE, mappedBy = "campaignId", fetch = FetchType.EAGER, targetEntity = CampaignCountry.class)
    private List<CampaignCountry> countries;

    @Column(name = "include_countries")
    private Boolean includeCountries;

    // getters & setters, toString, hashEquals
}

And here is the CampaignCountry.class I'm trying to map:

@Entity
@Table(name = "campaign_country")
@IdClass(CampaignCountry.CampaignCountryPK.class)
public class CampaignCountry {
    @Id
    @Column(name="campaign_id")
    private Long campaignId;
    @Id
    @Column(name="country")
    private String country;

    public CampaignCountry(String country) {
        this.country = country;
    }

    //getters and setters

    static class CampaignCountryPK implements Serializable {

        private Long campaignId;

        private String country;

        public CampaignCountryPK() {}

        public CampaignCountryPK(Long campaignId, String country) {
            this.campaignId = campaignId;
            this.country = country;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            CampaignCountryPK that = (CampaignCountryPK) o;

            if (campaignId != null ? !campaignId.equals(that.campaignId) : that.campaignId != null) return false;
            return country != null ? country.equals(that.country) : that.country == null;
        }

        @Override
        public int hashCode() {
            int result = campaignId != null ? campaignId.hashCode() : 0;
            result = 31 * result + (country != null ? country.hashCode() : 0);
            return result;
        }
    }
}

The issue comes when I'm calling repository.saveAndFlush(campaignEntity). It saves countries as I need but not CampaignId. It's always null.

org.springframework.orm.jpa.JpaObjectRetrievalFailureException:Unable to find domain.CampaignCountry with id domain.CampaignCountry$CampaignCountryPK@e43;

nested exception is javax.persistence.EntityNotFoundException: Unable to find domain.CampaignCountry with id domain.CampaignCountry$CampaignCountryPK@e43

Here is the migration SQL script (If it has to do with anything) :

CREATE TABLE campaign_country (
  campaign_id BIGINT               NOT NULL,
  country     CHARACTER VARYING(2) NOT NULL,
  PRIMARY KEY (campaign_id, country)
);

ALTER TABLE campaign_country
  ADD CONSTRAINT campaign_to_campaign_country FOREIGN KEY (campaign_id) REFERENCES campaign (id);

So what's wrong here, how do I populate campaignId with corresponding campaignEntity's id? I do believe it has something to @OneToMany annotation, it might be not properly implemented.


Solution

  • How about using @EmbeddedId instead?

    @Entity
    public class CampaignCountry implements Serializable {
    
        @EmbeddedId
        private CampaignCountryPK id;
    
        @MapsId("campaignId")
        @ManyToOne
        @JoinColumn(name = "campaign_id")
        private CampaignEntity campaignEntity;
    }
    
    @Embeddable
    public class CampaignCountryPK implements Serializable {
    
        @Column(name = "campaign_id")
        private long campaignId;
        @Column(name = "country")
        private String country;
    }
    
    @Entity
    @Table(name = "campaign_country")
    public class CampaignCountry implements Serializable {
    
        @EmbeddedId
        private CampaignCountryPK id;
    
        @MapsId("campaignId")
        @ManyToOne
        @JoinColumn(name = "campaign_id")
        private CampaignEntity campaignEntity;
    }
    

    Then in CampaignEntity change the mapping to:

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "campaignEntity", fetch = FetchType.EAGER, targetEntity = CampaignCountry.class)
    private List<CampaignCountry> countries;
    

    You just have to create the objects as:

    List<CampaignCountry> countryList = new ArrayList<>();
    CampaignEntity campaign = new CampaignEntity();
    
    CampaignCountryPK pk1 = new CampaignCountryPK();
    pk1.setCountry("Country 1");
    CampaignCountry country1 = new CampaignCountry();
    country1.setId(pk1);
    country1.setCampaignEntity(campaign);
    
    countryList.add(country1);
    campaign.setCountries(countryList);
    

    Or you can create a method for creating the PK.