Search code examples
spring-boothibernatejpaone-to-onecomposite-primary-key

JPA Hibernate Problem for One to One Relationship with Embedded ID


I am struggling with the following problem that I've been trying to solve. After checking solutions on StackOverflow and articles on Baeldung I still get different JPA errors when trying to map the following ONE-TO-ONE relationship between 2 Oracle SQL tables with composite PK in a SpringBoot application:

MASTER

ID VERSION
1 2022.1

Constraint:

PK_MASTER PRIMARY KEY(ID, VERSION)

MASTER_DETAILS

MASTER_ID VERSION DETAILS
1 2022.1 details

Constraint:

PK_MASTER_DETAILS PRIMARY KEY(MASTER_ID, VERSION)

FK_MASTER_DETAILS FOREIGN KEY(MASTER_ID, VERSION) REFERENCES MASTER(ID, VERSION)

After some failures in trying to map it using the @OneToOne JPA annotation with both classes having @EmbeddedId set on the composite PK, I also installed JPA Buddy to check how it will be generated and that resulted in the following 4 classes:

Master.java

@Getter
@Setter
@Entity
@Table(name = "master")
public class Master {
    @EmbeddedId
    private MasterId id;

    @OneToOne(mappedBy = "master")
    private MasterDetails masterDetails;
}

MasterId.java

@Getter
@Setter
@Embeddable
public class MasterId implements Serializable {
    private static final long serialVersionUID = 8254837075462858051L;
    @Column(name = "id", nullable = false)
    private BigDecimal id;

    @Lob
    @Column(name = "version", nullable = false)
    private String version;
}

MasterDetails.java

@Getter
@Setter
@Entity
@Table(name = "master_details")
public class MasterDetails {
    @EmbeddedId
    private MasterDetailsId id;

    @MapsId
    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumns({
            @JoinColumn(name = "master_id", referencedColumnName = "id", nullable = false),
            @JoinColumn(name = "version", referencedColumnName = "version", nullable = false)
    })
    private Master master;

    @Lob
    @Column(name = "details", nullable = false)
    private String details;
}

MasterDetailsId.java

@Getter
@Setter
@Embeddable
public class MasterDetailsId implements Serializable {
    private static final long serialVersionUID = -8375336118866998644L;
    @Column(name = "master_id", nullable = false)
    private BigDecimal masterId;

    @Lob
    @Column(name = "version", nullable = false)
    private String version;
}

When running the SpringBoot application with this JPA structure the run time error received is: org.hibernate.PropertyNotFoundException: Could not locate field [id] on class [org.project.packages.MasterDetails]

After removing the @MapsId that cause this error the application starts but when trying to insert data in the tables I get the following error: org.hibernate.id.IdentifierGenerationException: null id generated for:class org.project.packages.MasterDetails

Checking in the H2 test database I noticed that the FK on the Master_Details table was not present, but only the PK was set.

I would appreciate any help in pointing out how this problem can be solved: other annotations required (Cascade/FetchType) or in case there are any changes to be made to the database level (I also tried adding a separate identifier column in the Master_Details table defined as PK and only keep the FK to the Master table). Thanks in advance!


Solution

  • After many tries, I figured out to solve the issue.

    I had to use a common key between the two entities and also FetchType.LAZY.

    MasterDetails.class

    public class MasterDetails {
    
        @EmbeddedId
        @AttributeOverrides({
           @AttributeOverride(name="ID", column=@Column(name="MASTER_ID"))
        })
        private MasterId id;
    
        @OneToOne(fetch = FetchType.LAZY, optional = false)
        @JoinColumns({
            @JoinColumn(name = "master_id", referencedColumnName = "id", nullable = false),
            @JoinColumn(name = "version", referencedColumnName = "version", nullable = false)
        })
        
        private Master master;
    
        @Lob
        @Column(name = "guidance", nullable = false)
        private String guidance;
    }
    

    Master.class

    public class MasterSheet {
       @EmbeddedId
       private MasterId id;
    
       @OneToOne(mappedBy = "master", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
       private MasterDetails masterDetails;
    }