Mapping Entity relationships with JPA annotations

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on November 05, 2017 · 5 mins read

We will look at some practical examples of how to wire in Entity relationships using standard JPA annotations.

Generating Primary Key Id from Sequence

Consider the below SQL:

CREATE SEQUENCE SEQ_GENRE_ID;
CREATE TABLE GENRE (
	id NUMERIC(20, 0) NOT NULL PRIMARY KEY,
	short_name VARCHAR(10) NOT NULL,
	name VARCHAR(100) NOT NULL
);

I would like to auto-generate the ID of GENRE from the SEQ_GENRE_ID.
This is how my Entity would look like:

@Entity
@Table(name = "SECTION")
public class Section implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sectionIdGenerator")
	@SequenceGenerator(name = "sectionIdGenerator", sequenceName = "SEQ_SECTION_ID")
	@Column(name = "id")
	private Long id;
...

Here, the @SequenceGenerator contains the name of the sequence used for ID generation. This can be ignored for DB like MySQL, which has support for AUTO GENERATION at the table level itself. The @GeneratedValue has the strategy used to generate the value.

Simple One to One Relationship

Consider the below tables:

CREATE TABLE CHAPTER (
	id NUMERIC(20, 0) NOT NULL PRIMARY KEY,
	title TEXT NOT NULL,
	plot_summary TEXT,
	content_id NUMERIC(20, 0) NOT NULL,
	FOREIGN KEY (content_id) REFERENCES SECTION(id)
);
CREATE TABLE SECTION (
	id NUMERIC(20, 0) NOT NULL PRIMARY KEY,
	section_text TEXT NOT NULL,
	style TEXT,
	section_length INTEGER NOT NULL
);

Chapter

@Entity
@Table(name = "CHAPTER")
public class Chapter implements Serializable {
	private static final long serialVersionUID = 1L;
	@Column(name = "id")
	private Long id;
	@Column(name = "title")
	private String title;
	@Column(name = "plot_summary")
	private String plotSummary;
	@OneToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "content_id")
	private Section contents;
...

Section

@Entity
@Table(name = "SECTION")
public class Section implements Serializable {
	private static final long serialVersionUID = 1L;
	@Column(name = "id")
	private Long id;
	@Column(name = "section_text")
	private String sectionText;
	@Column(name = "style")
	private String style;
	@Column(name = "section_length")
	private int length;
...

The simple One-to-One can be wired up by using the below 2 annotations:

@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "content_id")
private Section contents;

Specify the name of the column in the CHAPTER table which is the FOREIGN KEY to the SECTION table.

One to Many Relationship using a Mapping table

Consider the below entity:

@Entity
@Table(name = "BOOK")
public class Book implements Serializable {
	private static final long serialVersionUID = 1L;
	private Long id;
	@Column(name = "title")
	private String title;
	@OneToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "BOOK_GENRE", joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "genre_id", referencedColumnName = "id"))
	private Set genres;
...

In terms of SQL, we can have a main table called BOOK and then a mapping table called BOOK_GENRE. This mapping table would contain the IDs of BOOK and GENRE tables.

CREATE TABLE BOOK (
	id NUMERIC(20, 0) NOT NULL PRIMARY KEY,
	title TEXT NOT NULL,
	author_id NUMERIC(20, 0) NOT NULL,
	main_chapter_id NUMERIC(20, 0) NOT NULL,
	FOREIGN KEY (author_id) REFERENCES AUTHOR(id),
	FOREIGN KEY (main_chapter_id) REFERENCES CHAPTER(id)
);
CREATE TABLE BOOK_GENRE (
	book_id NUMERIC(20, 0) NOT NULL,
	genre_id NUMERIC(20, 0) NOT NULL,
	FOREIGN KEY (book_id) REFERENCES BOOK(id),
	FOREIGN KEY (genre_id) REFERENCES GENRE(id)
);

This relationship can be represented by:

@OneToMany(fetch = FetchType.EAGER)
@JoinTable(name = "BOOK_GENRE", joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "genre_id", referencedColumnName = "id"))

The @JoinTable takes in the name of the Mapping table. It has the below 2 attributes:

  1. joinColumns: You need to provide the FOREIGN KEY from the owning part of the relationship, in this case,  BOOK
  2. inverseJoinColumns: You need to provide the FOREIGN KEY of the non-owning side of the entity, in this case, GENRE

Saving Entity relationships

By default, none of the Entity relations are inserted or updated. You need to explicitly specify the cascade attribute in the OneToOne or OneToMany annotation.

@OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name = "main_chapter_id")
private Chapter mainChapter;

If you do not want to save the Entity, do not specify the cascade attribute:

@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "author_id")
private Author author;

Sources

A working example can be found here:
https://github.com/paawak/blog/tree/master/code/one-to-many-example-2