Search code examples
spring-data-jpahibernate-mapping

Unable to create a composite PK with 3 fields in Hibernate/JPA/Spring boot project


The goal is to have a joining table (FormComponent) which binds a Form (by formId) to a Component (componentId) with an additional column of sortOrder. All three columns together form a unique composite primary key.

My implementation does work if I only define the formId and componentId in my @Embeddable class, but this does not allow me to have duplicates (a form may have the same component multiple times).

If I add the sortOrder-column to the PK and join table class, I get the following error:

    java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
    Caused by: org.springframework.beans.factory.BeanCreationException at AbstractAutowireCapableBeanFactory.java:1786
        Caused by: javax.persistence.PersistenceException at AbstractEntityManagerFactoryBean.java:421
            Caused by: org.hibernate.MappingException at PersistentClass.java:862

The composite PK class:

@EqualsAndHashCode
@AllArgsConstructor
@Getter
@Embeddable
public class FormComponentId implements Serializable {
    @Column(name = "form_id")
    private Long formId;
    @Column(name = "component_id")
    private Long componentId;
    @Column(name = "sort_order")
    private Long sortOrder;
}

The join table class:

@Data
@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "form_component")
public class FormComponent {
    @EmbeddedId
    private FormComponentId id;
    @ManyToOne(fetch = FetchType.EAGER)
    @MapsId("formId")
    private Form            form;
    @ManyToOne(fetch = FetchType.EAGER)
    @MapsId("componentId")
    private Component       component;
    private Long            sortOrder;
}

If I've understood the documentation correctly, I should be able to define a composite PK with more than 2 columns.

The project uses Spring boot 2.5.1.


Solution

  • To answer my own question (after a good nights sleep), the problem was having the sortOrder column defined in the FormComponent class.

    With this the form_component gets created correctly:

    @Data
    @Getter
    @Setter
    @Entity
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(name = "form_component")
    public class FormComponent {
        @EmbeddedId
        private FormComponentId id;
        @ManyToOne(fetch = FetchType.EAGER)
        @MapsId("formId")
        private Form            form;
        @ManyToOne(fetch = FetchType.EAGER)
        @MapsId("componentId")
        private Component       component;
    }
    

    form_component table dumped (from MariaDB):

    CREATE TABLE form_component (
      sort_order bigint(20) NOT NULL,
      component_id bigint(20) NOT NULL,
      form_id bigint(20) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    
    ALTER TABLE form_component
      ADD PRIMARY KEY (component_id,form_id,sort_order),
      ADD KEY FK1d1gjrgj8elln6irre9vj45e (form_id);
    
    ALTER TABLE form_component
      ADD CONSTRAINT FK1d1gjrgj8elln6irre9vj45e FOREIGN KEY (form_id) REFERENCES form (id),
      ADD CONSTRAINT FKlbpvqdblh0i147fqctd7dhs6a FOREIGN KEY (component_id) REFERENCES component (id);