How to give multiple columns as primary key in entity - JPA

Viewed 3698

I have 3 entities - Course, Module, Timeline In the timeline entity - I want to give 2 keys as primary key i.e Composite Key. How am I supposed to give that. Please tell me about the changes that are to be done in the code below:

Course:

@Id
@Column(name = "id")
Integer courseId;
@Column(name = "course_name")
String course_name;

Module:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "module_id")
Integer module_id;
@Column(name = "module_type")
String module_type;
@Column(name = "module_name")
String module_name;
@Column(name = "duration")
Integer duration;
@OneToOne( cascade = CascadeType.ALL)
private Course course;

Timeline:

    @Id
    @Column(name = "timeline_id")
    Integer timeline_id;
    @ManyToOne( cascade=CascadeType.ALL )
    private Module module;
    @ManyToOne( cascade = CascadeType.ALL)
    private Course course;

Now here in timeline, I want to have course_id and timeline_id as primary keys. Please help. Thank you in advance.

Update: I tried using Embeddable and EmbeddedId:

@Embeddable
public class TimelineId implements Serializable{
    private Integer course_id;
    private Integer timelineId;
    getters and setters
    hashcode and equals
}

Module:

@Entity
@Table (name = "timeline")
public class Timeline {
    @EmbeddedId
    private TimelineId timelinepk;
    @ManyToOne( cascade=CascadeType.ALL )
    private Module module;
    @ManyToOne( cascade = CascadeType.ALL)
    private Course course;
    
}

But this gives an error :

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: com.scb.axess.playbook.model.Timeline
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
1 Answers

There are multiple possibilities to solve your problem:

Possibility 1: Using IdClass

Defining the IdClass type

This class has to implement the Serializable interface and the equals(..) and hashCode() methods. The class holds the parts of the composite primary key.

public class TimelineId implements Serializable  {

    private Integer timelineId;
    private Integer courseId;

    // getters & setters

    @Override
    public int hashCode() {
        // your impl of hashCode
    }

    @Override
    public boolean equals(Object obj) {
        // your impl of equals
    }

}

Modify your Timeline class

Here the @IdClass annotation is added to the entity class. Further, the class holds the same fields like the IdClass type (name and type should be identical), but annotated with @Id.

@Entity
@IdClass(TimelineId.class)
public class Timeline {

    @Id
    @Column(name = "timeline_id")
    private Integer timelineId;
    @Id
    @Column(name = "course_id")
    private Integer courseId;
    @ManyToOne
    @JoinColumn(name = "module_id")
    private Module module;

    // getters & setters

}

Possibility 2: Using EmbeddedId

Defining the EmbeddedId type

This class also holds the parts of the composite primary key.

@Embeddable
public class TimelineId {

    @Column(name = "timeline_id")
    private Integer timelineId;
    @Column(name = "course_id")
    private Integer courseId;

    // getters & setters

}

Modify your Timeline class

In this case the single parts of the composite primary key can be omitted. Only a field of the embedded key type annotated with @EmbeddedId is defined.

@Entity
public class Timeline {

    @EmbeddedId
    private TimelineId timelineId;
    @ManyToOne
    @JoinColumn(name = "module_id")
    private Module module;

    // getters & setters

}

In both cases the corresponding repositories should be defined like this (TimelineId has to be used for parameter type ID) (here, JpaRepository is used):

public interface TimelineRepository extends JpaRepository<Timeline, TimelineId> {}
**Possibility 3: Don't use a composite PK, but make the columns unique**

Modify your Timeline class

@Entity
@Table(uniqueConstraints = {
    @UniqueConstraint(columnNames = {
        "course_id", "module_id"
    })
})
public class Timeline {

    @Id
    @Column(name = "timeline_id")
    Integer timeline_id;
    @ManyToOne( cascade=CascadeType.ALL)
    @JoinColumn(name = "module_id)
    private Module module;
    @ManyToOne( cascade = CascadeType.ALL)
    @JoinColumn(name = "course_id)
    private Course course;

    // getters & setters

}
Related