Русский
Русский
English
Статистика
Реклама

Односторонние и двусторонние отношения в 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,    email varchar(255),    password varchar(512) not null,    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 List<Contact> contacts;    // Конструктор по умолчанию, гетеры, сеттеры и т.д.}

Теперь владельцем отношения является сущность пользователя, что более логично, но если запустить данный код и посмотреть на созданную 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,    email varchar(255),    password varchar(512) not null,    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<Contact> contacts;

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

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

У двусторонних отношений помимо стороны - владельца (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 List<Role> roles;    // Конструктор по умолчанию, гетеры, сеттеры и т.д.}@Entity@Table(name = "roles")public class Role {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Long id;    @Column    private String name;    @ManyToMany    private List<User> users;    // Конструктор по умолчанию, гетеры, сеттеры и т.д.}

Запускаем код и смотрим на структуру таблиц. Помимо таблиц для пользователей и ролей 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<User> 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 List<Contact> contacts;    // Конструктор по умолчанию, гетеры, сеттеры и т.д.}

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

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

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

Источник: habr.com
К списку статей
Опубликовано: 14.02.2021 16:19:02
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Java

Hibernate

Jpa

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru