JPA связи между Entity

JPA это стандарт позволяющий вам организовать мапинг ваших домен моделей на структуру базы данных, что дает гибкие возможности манипулирования объектами внутри нашей бизнес логики при этом не затрудняя себя работой с JDBC.

Это небольшой гайд, который послужит вам как шпаргалка о том как можно организовать связи в JPA.

Типы связей

Есть 3 типа связи между сущностями:

  • OneToOne - один к одному;
  • OneToMany и ManyToOne - один ко многому и много к одному;
  • ManyToMany - много ко многому.

В целях более комфортного рассмотрения связей представим что у нас есть две сущности:

JPA Entities

Теперь на основе двух этих сущностей рассмотрим как можно организовать между ними связи.

Прежде чем перейти к рассмотрению каждой связи давайте приготовим два наших класса которые будут отображать сущность Author and Book.

Класс Author:

package dev.bcode.entity;

@Entity
@Table(name = "authors")
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;

    // Getters & Setters
}

Класс Book:

package dev.bcode.entity;

@Entity
@Table(name = "books")
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "name")
    private String name;

    // Getters & Setters
}

OneToOne

На рисунку ниже видно что есть две сущности Автор и Книга. Так как сущности имеют связь OneToOne (1 to 1), то с сущности Книга можно получить Автора этой книги, а с сущности Автор можно получить его книгу.

Связь: OneToOne

Связь: OneToOne

Эта связь позволяет каждому автору в вашей базе данных иметь ссылку на только одну книгу а книге ссылку на одного автора. Иными словами, у одной книги один автор и наоборот.

Реализация

Для того чтобы организовать двустороннюю связь OneToOne нам необходимо в Author классе сослаться на Book:

@OneToOne(optional = false)
@JoinColumn(name="book_id", unique = true, nullable = false)
private Book book;

// Setters & Getters

В этом случае в таблице authors будет колонка book_id которая будет хранить id книги на которую он ссылается.

Со стороны Book мы также добавляем OneToOne связь:

@OneToOne(optional = false, mappedBy="book")
public Author author;

// Setters & Getters

В таком случае в таблице books не будет дополнительных колонок, а связь будет построена по уже определённом полю book в сущности Author.

Пояснение

@OneToOne - указывает на тип связи, где:

  • optional - по умолчанию true, указывает на должна ли быть эта связь обязательной;
  • mappedBy - определяет по какому полю в классе Author будет осуществляться связь на основе уже существующего id в таблице.

@JoinColumn - определяет колонку связи с нашей сущностью, где:

  • name - имя этой колонки;
  • unique - указывает на ее уникальность, если true, тогда ссылку по id на эту же книгу не сможет иметь другой автор;
  • nullable - указывает может ли быть значение в этой колонке пустым, то есть null.

OneToMany и ManyToOne

На рисунке ниже мы можем наблюдать следующую связь, где OneToMany и ManyToOne применяется вместе.

Такой тип связи позволяет нам со стороны сущности Author получать список книг которые он написал – это связь OneToMany, а со стороны сущности Book мы можем с нескольких книг получить одного и того же автора который написал данную книгу – это связь ManyToOne.

Связь: OneToMany и ManyToOne

Связь: OneToMany и ManyToOne

Данную связь можно организовать и в обратную сторону. В этом случае у книги может быть несколько авторов, но у Автора только одна книга - что чаще всего в реальном мире исключение.

Реализация

В сущность Book добавляем следующее:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "author_id", nullable = false)
private Author author;

// Setters & Getters

В этом случае в таблице books будет колонка author_id содержащая id автора.

Со стороны сущности Author мы добавляем следующее:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "author")
private Set<Book> books;

// Setters & Getters

Теперь мы имеем возможность у определенного автора получить список его книг, а у конкретной книги узнать ее автора.

Пояснение

@ManyToOne - указывает на тип связи много к одному. В нашем случае много книг к одному автору.

  • fetch - по умолчанию EAGER указывает что данные связи должны быть получены вместе с сущностью. LAZY не будет получен при получении сущности, и только при обращении к связи будет выполнен дополнительный запрос в базу для его получения.
  • cascade - по умолчанию пуст. Указывает на тип каскадной обработки, то есть если мы укажем CascadeType.ALL то при обновлении или удалении нашей сущности все записи ссылающиеся на нашу сущность будут удалены или модифицированы и это чаще все плохо.

@OneToMany - указывает на тип связи один ко многому. В нашем случае один автор ко многим книгам.

  • mappedBy - определяет по какому полю класса будет построена связь сущности.

@JoinColumn - пояснение в разделе OneToOne.

ManyToMany

Ниже приведен пример связи много ко многому. Этот тип связи позволяет Автору иметь несколько книг и книге несколько авторов.

Связь: ManyToMany

Связь: ManyToMany

Для нашего примера книг и авторов этот пример связи подходит наилучшим образом. Ведь бывает когда когда книгу писали несколько авторов, и в тот же момент каждый из этих авторов мог писать и другие книги как самостоятельно так и совместно с другими авторами.

Реализация

В сущность Author добавляем следующее:

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
    name="authors_books",
    joinColumns = @JoinColumn(name="author_id", referencedColumnName="id"),
    inverseJoinColumns = @JoinColumn(name="book_id", referencedColumnName="id")
)
private Set<Book> books;

// Setters & Getters

И для сущности Book добавляем:

@ManyToMany(fetch = FetchType.EAGER, mappedBy = "books")
private Set<Author> users;

// Setters & Getters

Если для организации типов связи OneToOne и OneToMany требовалась дополнительная колонка с id сущности на которую хотим построить связь, то в случае с ManyToMany будет создана новая промежуточная таблица. где каждая запись будет содержать id автора и id книги.

ManyToMany: Промежуточная таблица

Пояснение

@ManyToMany - указывает на тип связи много ко многому, где:

  • fetch - LAZY или EAGER; (детальное пояснение в разделе OneToMany и ManyToOne)
  • mappedBy - определяет по какому полю в классе будет осуществляться связь.

@JoinTable - определяет как и на основании каких полей будет создана связь с помощью промежуточной таблицы.

  • name - имя промежуточной таблицы;
  • joinColumns - поле построения связи для первой сущности;
  • inverseJoinColumns - поле построения связи для второй сущности.