Java jpa one to many

Односторонние и двусторонние отношения в Hibernate

Всем нам хорошо известен ответ на вопрос, какими могут быть отношения между сущностями в Hibernate и JPA. Вариантов всего четыре:

  • OneToOne — один к одному
  • OneToMany — один ко многим
  • ManyToOne — многие к одному
  • ManyToMany — многие ко многим

Для каждого из отношений есть своя аннотация и, казалось бы, на этом можно закончить разговор, но все не так просто. Да и вообще, может ли быть что-то просто в Hibernate 😉 Каждое из выше перечисленных отношений может быть односторонним (unidirectional) или двусторонним (bidirectional), и если не принимать это во внимание, то можно столкнуться с массой проблем и странностей.

Для примера возьмем две простейшие сущности: пользователь и контакт. Очевидно, что каждый контакт связан с пользователем отношением многие к одному, а пользователь с контактами отношением один ко многим.

Односторонние отношения

Односторонним называется отношение, владельцем которого является только одна из двух сторон. Отсюда и название. Следует заметить, что при этом вторая сторона об этом отношении ничего не знает. Hibernate будет считать владельцем отношения ту сущность, в которой будет поставлена аннотация отношения.

Давайте попробуем сделать владельцем отношения сторону контакта. При этом сущности будут выглядеть следующим образом.

@Entity @Table(name = "contacts") public class Contact < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String type; @Column private String data; @ManyToOne private User user; // Конструктор по умолчанию, геттеры, сеттеры и т.д. >@Entity @Table(name = "users") public class User < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; // Конструктор по умолчанию, гетеры, сеттеры и т.д. >

Если запустить этот код, то Hibernate создаст следующую структуру таблиц, которая выглядит для нас вполне привычно. Отношение между таблицами создается при помощи ссылочного поля user_id в таблице contacts.

create table contacts ( id bigint not null auto_increment, data varchar(255), type varchar(255), user_id bigint, primary key (id) ) engine=InnoDB; create table users ( id bigint not null auto_increment, username varchar(128) not null, primary key (id) ) engine=InnoDB

Но выбор сущности Contact в качестве стороны владельца отношений в данном случае не очень удачен. Очевидно, что нам чаще нужна информация обо всех контактах пользователя чем о том, какому пользователю принадлежит контакт. Попробуем сделать владельцем контакта сущность пользователя. Для этого убираем поле user из класса Contact и добавляем поле со списком контактов в класс User. Получаем следующий код.

@Entity @Table(name = "contacts") public class Contact < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String type; @Column private String data; // Конструктор по умолчанию, геттеры, сеттеры и т.д. >@Entity @Table(name = "users") public class User < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @OneToMany private Listcontacts; // Конструктор по умолчанию, гетеры, сеттеры и т.д. >

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

create table contacts ( id bigint not null auto_increment, data varchar(255), type varchar(255), primary key (id) ) engine=InnoDB; create table users ( id bigint not null auto_increment, username varchar(128) not null, primary key (id) ) engine=InnoDB; create table users_contacts ( User_id bigint not null, contacts_id bigint not null ) engine=InnoDB;

Чтобы связать сущности Hibernate создал дополнительную таблицу связи (join table) с именем users_contacts, хотя сущности вполне можно было бы связать через ссылочное поле в таблице contacts, как в предыдущем случае. Честно говоря, я не совсем понимаю, почему Hibernate поступает именно так. Буду рад, если кто-то поможет с этим разобраться в комментариях к статье.

Проблему можно легко решить добавив аннотацию JoinColumn к полю contacts.

 @OneToMany @JoinColumn(name = "user_id") private List contacts;

При таких настройках связь будет проводиться при помощи колонки user_id в таблице contacts, а таблица связи создаваться не будет.

Читайте также:  Using google fonts in css

Двусторонние отношения

У двусторонних отношений помимо стороны — владельца (owning side) имеется ещё и противоположная сторона (inverse side). Т.е. обе стороны отношения обладают информацией о связи. Логично предположить, что из одностороннего отношения можно сделать двустороннее просто добавив поле и аннотацию в класс сущности противоположной стороны, но не все так просто. В чем именно тут проблема очень хорошо видно на примере отношения многие ко многим. Давайте создадим пример такого отношения между сущностями пользователя и роли этого пользователя.

@Entity @Table(name = "users") public class User < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @ManyToMany private Listroles; // Конструктор по умолчанию, гетеры, сеттеры и т.д. > @Entity @Table(name = "roles") public class Role < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String name; @ManyToMany private Listusers; // Конструктор по умолчанию, гетеры, сеттеры и т.д. >

Запускаем код и смотрим на структуру таблиц. Помимо таблиц для пользователей и ролей Hibernate создаст две таблицы связи, хотя нам хватило бы и одной.

create table roles_users ( Role_id bigint not null, users_id bigint not null ) engine=InnoDB; create table users_roles ( User_id bigint not null, roles_id bigint not null ) engine=InnoDB;

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

Для отношения многие ко многим любая из сторон может быть владельцем. В случае с ролями и пользователями выберем сущность пользователя в качестве владельца. Для этого изменим описание поля users в классе Role следующим образом.

 // значение атрибута mappedBy - имя поля связи в классе сущности-владельца отношений @ManyToMany(mappedBy = "roles") private List users;

Теперь Hibernate создаст только одну таблицу связи users_roles.

И напоследок давайте сделаем двусторонним отношение между пользователями и контактами. Следует отметить, что в отношении один ко многим стороной-владельцем может быть только сторона многих (many), поэтому атрибут mappedBy есть только в аннотации @OneToMany . В нашем случае владельцем отношения будет сторона контакта (класс Contact).

@Entity @Table(name = "contacts") public class Contact < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String type; @Column private String data; @ManyToOne private User user; // Конструктор по умолчанию, геттеры, сеттеры и т.д. >@Entity @Table(name = "users") public class User < @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @OneToMany(mappedBy = "user") private Listcontacts; // Конструктор по умолчанию, гетеры, сеттеры и т.д. >

Для такого кода Hibernate создаст привычную нам структуру из двух таблиц со ссылкой на пользователя в таблице контактов.

Читайте также:  Multiple threads in java

На этом все на этот раз! Благодарю, что дочитали до конца и надеюсь, что статья была полезной! Разумеется, очень жду от вас обратную связь в виде голосов и комментариев!

Возможно, будет продолжение!

Источник

Let’s crate a project with spring-boot-cli. If you are not familiar with spring-cli, it’s pretty handy tool to have. You can install it from this link .

You can type the following command in your terminal.

Spring Initializr

Alternatively, You can generate the project from Spring Initializr web tool by following the instructions below —

  1. Go to http://start.spring.io
  2. click Switch to full version link to see all the options
  3. Enter Artifact as “jpa-one-to-many”
  4. Change Package Name to “com.techyowls” / or anything you like
  5. Select Web, JPA and Mysql dependencies.
  6. Click Generate Project to download the project.

Our generate Folder Structure will be like following screenshot. To achieve that let’s create package name entity, exception, repository, resource inside of your /src/main/java

One to Many Relationship can be Unidirectional and Bidirectional. And it may be used based on the business requirement.

Database Configuration:

We will use a docker-compose file which will make available mysql database to which our application will connect.

If you want to know more about the docker-compose I have explained in great detail in another post Mysql Docker Compose our mysql.yaml compose file looks like this.

and our application.properties file looks like bellow

Ways to map one to many relationship in hibernate:

  1. Hibernate is an old friend of mine and I have found it from experience that the best way to map one to many relationship in hibernate is using @ManyToOneannotation on child entity.
  2. We can also achieve bi-directionalone to many relationship as well and to that we will have @OneToMany annotation on parent side and @ManyToOne annotation on children side.
  3. Also, we can achieve Unidirectional @OneToMany with @JoinColumn
Читайте также:  Python сделать снимок камеры

@ManyToOne on child entity

We have used lombok to make remove the verbosity of java. If you are not familiar with lombok. Then take a look at my previous post In previous post Our Development Good Friend Lombok

Let’s create Our Author and Book entity class inside our entity package.

Our Author and Book Class looks like this.

Author Model

Book Model

We have only mapped the child class with @ManyToOne annotation for this scenario and we made optional=false meaning is that author field is required.

@JoinColumn name Spring Boot Hibernate Jpa One to Many Mapping with Pagination and Rest API»>

Get paginated Authors GET /authors?page=0&size=2&sort=createdAt,desc

Spring Boot Hibernate Jpa One to Many Mapping with Pagination and Sorting Rest API

Create Book POST /authors//book

Spring Boot Hibernate Jpa One to Many Mapping with Pagination and Sorting Rest API create Book

Get paginated books GET /authors//books?page=0&size=3&sort=id,desc

Spring Boot Hibernate Jpa One to Many Mapping with Pagination and Sorting Rest API get books

Bi-directional one to many mapping

Let’s see how our entities will look like if we want to map our Book and Author as bi-directional

Author Entity

Book Entity

In a bi-directional one-to-many association

  • A collection of child entities are kept and tracked by parent entity.
  • Enables us to persist and retrieve the child entities via the parent
  • cascade = CascadeType.ALLtells hibernate to handle the persist operation of child entities..

For example, here is how we can persist books via authors entity in the bidirectional mapping —

Hibernate automatically issues insert statements and saves the comments added to the post.

Problems with bidirectional one-to-many mapping

  • A bidirectional mapping tightly couples the many-side of the relationship to the one-side.
  • If we load books via the author entity, we won’t be able to limit the number of books loaded and which leads to not being able to paginate.

When to use bi-directional one to many relationship?

  • When we know the number of child entities are limited then then bi-directionalone to many mapping is best fit.
  • For example, A mcq Question which can have max 4/5 options and it’s predictable and for this use case we want this kind of tight coupling.

Unidirectional @One to many with @JoinColumn

In this kind of mapping parent entity will hold list of child entities and in child entity there will be no mapping related information.

@JoinColumn annotation will create a column author_id column in books table and as CascadeType.ALL is used so parent entity will take the responsibility to persist the child book entities.

Conclusion

That’s all for now! In this post, we have learned how to map a one-to-many database relationship using JPA and Hibernate.

You can find the code for the sample project that we built in this article in my jpa-one-to-many-demo repository on github.

More Resources:

You may wanna check out the following articles by Vlad Mihalcea to learn more about Hibernate and it’s performance —

Источник

Оцените статью