Blog

Spring Boot JPA Relationship Quick Guide

This week we return to a more technical topic, Spring Boot JPA, and unwind its complexities into practical examples of common persistence patterns.

Spring Boot JPA has a ton of documentation, references, and articles. That’s a lot of material to sort through, so I created this Spring Boot JPA Relationship Quick Guide. I hope that as you work with Spring Boot JPA for the first time, or return to it after a long absence, your search for answers will be quick.

Spring from storage to application data

There are three basic entity relationships that we will cover in this quick start:

  • One-to-One
  • One-to-Many/Many-to-one
  • Many-to-Many

Consider the following entity relationship diagram of a musical application that ranks Artists.

  • Artist to Ranking: One-to-one
  • Artist to Recording: One-to-Many
  • Recording to Release: Many-to-ManyNote that the reference table between Recording and Release is called Track

One-to-One

In a one-to-one scenario, the primary key of one table exists as a foreign key on another table. In this case, assume the artist primary key exists on the rankings table. Here we would say artist is the parent and ranking is the child.

Parent

@Entity
@Table(name = "artist")
public class Artist {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "artist_id")
    private Long artistId;

 ...

    @OneToOne(mappedBy = "artist", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Ranking ranking;

Child

@Entity
@Table(name = "ranking")
public class Ranking {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ranking_id")
    private Long rankingId;

...
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "artist_id")
    private Artist artist;
    

Keys

  • The entity with the foreign key in its table is the child
  • The parent should use @OneToOne(mappedBy = “artist”, cascade = CascadeType.ALL, fetch = FetchType.LAZY)

The key here is mappedBy. This tells JPA that rankings are mapped by the artist entity on the ranking object

  • The child should have @OneToOne and @JoinColumn(name = “artist_id”). This tells JPA that Artist is mapped to ranking via the artist_id.

One-to-Many

In a one-to-many scenario is similar to the one-to-one scenario. The primary key of one table exists as a foreign key on another table. Given the artist primary key exists on the recording table, we would say artist is the parent and recording is the child.

Parent

@Entity
@Table(name = "artist")
public class Artist {

...

    @OneToMany(mappedBy = "artist")
    private Set<Recording> recordings = new HashSet<>();

Child

@Entity
@Table(name = "recording")
public class Recording {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "recording_id")
    private Long recordingId;

...

    @ManyToOne
    @JoinColumn(name = "artist_id")
    private Artist artist;

Keys

  • The entity with the foreign key in its table is the child
  • The parent should use @OneToMany(mappedBy = “artist”, cascade = CascadeType.ALL, fetch = FetchType.LAZY)

The key here is mappedBy. This tells JPA that rankings are mapped by the artist entity on the ranking object

  • The child should have @ManyToOne and @JoinColumn(name = “artist_id”). This tells JPA that Artist is mapped to ranking via the artist_id.

Many-to-Many

In a many-to-many scenario, the association between the two entities is tracked by a cross-reference table. This is a table that holds both of the related entities’ primary keys as foreign keys. JPA can handle this without an entity that represents the cross-reference. In this scenario the cross-reference table is track.

Parent

@Entity
@Table(name = "recording")
public class Recording {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "recording_id")
    private Long recordingId;

...

    @ManyToMany(cascade = { CascadeType.ALL })
    @JoinTable(
            name = "track",
            joinColumns = { @JoinColumn(name = "recording_id") },
            inverseJoinColumns = { @JoinColumn(name = "release_id") }

Child

@Entity
@Table(name = "release")
public class Release {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "release_id")
    private Long releaseId;

    @ManyToMany(mappedBy = "releases")
    private Set<Recording> recordings = new HashSet<>();

Keys

  • Choose one of the entities, doesn’t matter which one, to hold the @JoinTable, consider this the parent entity
  • The parent must define its own foreign key as the join column joinColumns = { @JoinColumn(name = “recording_id”) },.
  • The parent entity must define the child as the inverseJoinColumnsinverseJoinColumns = { @JoinColumn(name = “release_id”)
  • The child uses @ManyToMany(mappedBy = “releases”) to map the parent to itself.

Notes:

A many-to-many relationship is comprised of two one-to-many relationships to the cross reference table. The many-to-many relationship must be broken down this way if the cross reference table is to carry data other than the foreign keys.

Summary

Spring Boot and JPA are amazing tools that can allow for rapid application development. However, to the uninitiated or to someone who hasn’t used it in a long time, the syntax can be a bit confusing. The plethora of documentation can often help compound the confusion. This Spring Boot JPA Relationship Quick Guide is here to help you to clear up one aspect of that confusion.

Source can be found at https://github.com/djchi82/SpringBootJPARelationshipExampleBlog

Categories: Blog

Tags: , , , , , ,

Ryan Van Fleet
10 Apr, 2018