Entity Relationships in Spring Data JPA Using Kotlin

Introduction

Welcome to this lesson on Entity Relationships in Spring Data JPA using Kotlin. In the previous lessons, we've covered the basics of JPA repositories and entities, using derived queries for simple methods, and creating query methods for more complex queries. In this lesson, we're going to discuss how to work with relationships between entities in Spring Data JPA.

Types of Relationships

Before diving into implementation, it's essential to understand the different types of relationships in the context of data persistence with Spring Data JPA:

  • One-to-One: For example, a person and their passport. Each person has one passport, and each passport belongs to one person.
  • One-to-Many: For example, one person can be assigned to many different ToDos, but one ToDo cannot be assigned to multiple people.
  • Many-to-One: This is just the previous example but viewed from the opposite direction — many ToDos are assigned to the same person.
  • Many-to-Many: For example, Google Docs and collaborators. Each person can work on many docs, and each doc can have many collaborators.

Understanding One-to-One Relationship

Let’s start with the One-to-One relationship. Here is a diagram to illustrate the One-to-One relationship between a person and their passport.

Explanation:

  • The PERSON and PASSPORT tables are shown with a one-to-one relationship.
  • Each PERSON has one PASSPORT, and each PASSPORT belongs to one PERSON.
  • The ||--|| notation indicates this one-to-one relationship.
  • Both tables contain an ID, and there are foreign keys (passportId in PERSON and personId in PASSPORT) representing the relationship.

Implementing One-to-One Relationship

Let's see how we can define a One-to-One relationship using JPA annotations in Kotlin:

package com.codesignal.entities

import jakarta.persistence.*
import kotlin.jvm.Transient

@Entity
@Table(name = "person")
data class Person(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    val name: String,

    @OneToOne(mappedBy = "person", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
    @Transient
    var passport: Passport? = null
)

@Entity
@Table(name = "passport")
data class Passport(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    val number: String,

    @OneToOne
    @JoinColumn(name = "person_id")
    var person: Person? = null
)

In the Person class, the @OneToOne annotation indicates a one-to-one relationship with the Passport class. The mappedBy attribute specifies the field in the Passport class that owns the relationship. The cascade = CascadeType.ALL attribute indicates that all JPA-related changes (like persist, merge, remove, etc.) made to the Person entity should cascade to the associated Passport entity. The fetch = FetchType.LAZY attribute indicates that the Passport entity should be lazily loaded, meaning it will be fetched from the database only when explicitly accessed.

In the Passport class, the @OneToOne annotation defines the other end of the relationship with the Person class. The @JoinColumn annotation is used to specify the foreign key column (person_id) in the PASSPORT table that maps to the primary key of the PERSON table.

The @Transient annotation is used in the Person class to indicate that the passport property should not be persisted to the database. This means that the passport field will be ignored by the JPA provider and will not be mapped to any database column. It is used here because the passport field already has a relationship mapping and doesn't need its own separate database column.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal