Search code examples
springspring-bootkotlinjpa

ManyToMany Relationship with composite primary key


I am trying to express a @ManyToMany relationship where one of the table has a composite primary key. In table_1 I have a column named table2Id which is foreign key to table_2. The primary key in table_2 is a composite key of language and id. I do not know how to express a @ManyToMany relationship between the two tables.

This is the "owning" table

@Entity
@Table(name = "table_1")
class Table1(
    @Column val foo Double,
    @Column val bar: Double,
    @Column table2Id: Long,
    @ManyToMany
    @JoinTable(
        name = "table_1_table_2_link",
        joinColumns = [JoinColumn(name = "table_1_id", referencedColumnName = "id")],
        inverseJoinColumns = [JoinColumn(name = "table_2_id", referencedColumnName = "id")]
)
    val table2: List<Table2>
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0 
}

And this is the table with the composite primary key

@Embeddable
class Table2Id (
    private var id: Long,
    private var language: Languages // enum
) : Serializable


@Entity
@Table(name = "table_2",)
class Table2 (
    @EmbeddedId
    val table2Id: Table2Id,
    @Column val title String,
    @Column val tag String,
) {
    @MapsId
    val id: Long = 0

    @MapsId
    @Enumerated(EnumType.STRING)
    val language: Languages = Languages.en
}

Which gives me

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Cannot invoke "org.hibernate.mapping.PersistentClass.getTable()" because "classMapping" is null


Solution

  • This error typically occurs when there's an issue with how the entities are defined or how their relationships are mapped.

    Make sure you initialize the Table2Id.

    This is what I came up with:

    @Entity
    @Table(name = "table_1")
    class Table1(
        @Column val foo: Double,
        @Column val bar: Double)
    {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Long = 0
    
        // Default constructor required by Hibernate
        constructor() : this(0.0, 0.0)
    
        @ManyToMany
        var table2: List<Table2> = mutableListOf() // Initialize as empty list
    
        fun addTable2(table2: Table2) {
            this.table2 = listOf(table2)
        }
    }
    

    Table2Id

    @Embeddable
    class Table2Id(
        @Column(name = "id")
        var id: Long,
    
        @Column
        @Enumerated(EnumType.STRING)
        var language: Table2.Languages
    ) : Serializable {
    
        constructor() : this(0L, Table2.Languages.en) // Required for JPA
    
        // Implement equals and hashCode methods
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Table2Id) return false
    
            if (id != other.id) return false
            if (language != other.language) return false
    
            return true
        }
    
        override fun hashCode(): Int {
            var result = id.hashCode()
            result = 31 * result + language.hashCode()
            return result
        }
    
    }
    

    Table2

    @Entity
    @Table(name = "table_2")
    class Table2() {
    
        @EmbeddedId
        lateinit var table2Id: Table2Id
    
        @Column
        var title: String = ""
    
        @Column
        var tag: String = ""
    
        constructor(id: Long, title: String, tag: String) : this() { 
            this.table2Id = Table2Id(id, Languages.en) // Initializing Table2Id
            this.title = title
            this.tag = tag
        }
    
        enum class Languages {
            en,
            sw
        }
    }
    

    Sample results when I query

    {
        "foo": 2.2,
        "bar": 2.3,
        "id": 1,
        "table2": [
            {
                "table2Id": {
                    "id": 1,
                    "language": "en"
                },
                "title": "My title",
                "tag": "My tag"
            }
        ]
    }