Я решил открыть этот пост с этой цитаты, потому что я фанат Линуса Торвальдса.

Это моя первая статья. В этом я рассмотрю все возможные случаи в One-to-Many/Many-to-One ассоциации сущностей. Остальные Many-to-Many и One-to-One будут рассмотрены в следующих статьях.

Я надеюсь, что это определенно поможет каждому новичку, который хочет изучить jpa / hibernate, пожалуйста, прочтите весь отрывок: P

ПРИМЕЧАНИЕ.

Здесь я рассмотрел все возможные случаи One-to-Many/Many-to-One сопоставления. Среди всего B idirectional ассоциация `@ OneToMany` - лучший способ сопоставить связь базы данных" один-ко-многим ".

Ассоциация спящего режима подразделяется на One-to-One, One-to-Many/Many-to-One и Many-to-Many.

  • Направление отношений может быть как двунаправленным, так и однонаправленным.
  • У двунаправленных отношений есть как сторона владения, так и обратная сторона.
  • У однонаправленных отношений есть только сторона владения. Сторона-владелец отношения определяет, как среда выполнения Persistence обновляет отношения в базе данных.

Однонаправленные отношения:

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

Двунаправленные отношения:

  • Двунаправленная связь обеспечивает навигационный доступ в обоих направлениях, так что вы можете получить доступ к другой стороне без явных запросов.
  • В двунаправленной связи каждая сущность имеет поле или свойство отношения, которое ссылается на другую сущность. Через поле отношения или свойство код класса сущности может получить доступ к связанному с ним объекту. Если у объекта есть связанное поле, говорят, что объект «знает» о своем связанном объекте. Например, если Заказ знает, какие экземпляры статьи затрат у нее есть, и если статья затрат знает, к какому заказу она принадлежит, они имеют двунаправленную связь.

Двунаправленные отношения должны соответствовать этим правилам.

  • Обратная сторона двунаправленной связи должна относиться к ее стороне-владельцу (сущности, которая содержит внешний ключ) с помощью элемента mappedBy аннотации @OneToOne, @OneToMany или @ManyToMany. Элемент mappedBy обозначает свойство или поле в сущности, которая является владельцем отношения.
  • Многосторонняя сторона @ManyToOne двунаправленных отношений не должна определять элемент mappedBy. В отношениях всегда есть сторона, которая владеет множеством.
  • Для @OneToOne двунаправленных отношений сторона-владелец соответствует стороне, содержащей @JoinColumn, то есть соответствующий внешний ключ.
  • Для @ManyToMany двунаправленных отношений любая сторона может быть стороной-владельцем.

Связь @OneToMany с JPA и Hibernate

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

Когда использовать сопоставление "один ко многим"

Используйте один для сопоставления, чтобы создать отношение 1… N между сущностями или объектами.

Мы должны написать две сущности, то есть Company и Branch, чтобы несколько филиалов можно было связать с одной компанией, но одно отдельное ветвь не может быть разделено между двумя или более компаниями.

Переведите в спящий режим решения для сопоставления от одного ко многим:

  1. Отображение "один ко многим" с ассоциацией внешнего ключа
  2. Сопоставление "один ко многим" с таблицей соединений

Эту проблему можно решить двумя способами.

Один из них - иметь столбец внешнего ключа в таблице ветвей, то есть company_id. Этот столбец будет относиться к первичному ключу таблицы Company. Таким образом, никакие два филиала не могут быть связаны с несколькими компаниями.

Второй подход состоит в том, чтобы иметь общую таблицу соединений, скажем Company_Branch, Эта таблица будет иметь два столбца, то есть company_id, который будет внешним ключом, относящимся к первичному ключу в таблице Company, и аналогично branch_id, который будет внешним ключом, относящимся к первичному ключу таблицы Branch.

Если @ OneToMany / @ ManyToOne не имеет зеркальной ассоциации @ ManyToOne / @ OneToMany соответственно на дочерней стороне, тогда связь @ OneToMany / @ ManyToOne будет однонаправленной.

@OneToMany Unidirectional Relationship

При таком подходе любой субъект будет нести ответственность за установление отношений и их поддержание. Либо Компания объявляет отношения как «один ко многим», либо Филиал объявляет отношения со своего конца как «многие к одному».

CASE 1: (Отображение с ассоциацией внешнего ключа)

Если использовать только @OneToMany, то будет 3 таблицы. Например, company, branch и company_branch.

ПРИМЕЧАНИЕ.
В приведенном выше примере я использовал такие термины, как cascade, orphanRemoval, fetch и targetEntity, которые я объясню в своем следующем сообщении.

Таблица company_branch будет иметь два внешних ключа company_id и branch_id.

Теперь, если мы сохраним одну компанию и два филиала:

Hibernate будет выполнять следующие операторы SQL:

  • Ассоциация @OneToMany по определению является родительской (не владеющей) ассоциацией, даже если она однонаправленная или двунаправленная. Только родительская сторона ассоциации имеет смысл каскадировать переходы от состояния объекта к дочерним.
  • При сохранении сущности Company каскад также распространит операцию сохранения на нижележащие дочерние элементы Branch. После удаления Branch из коллекции ветвей строка ассоциации удаляется из таблицы ссылок, и атрибут orphanRemoval также инициирует удаление ветки.

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

С другой стороны, двунаправленная @OneToMany ассоциация намного более эффективна, потому что дочерняя сущность контролирует ассоциацию.

CASE 2: (Сопоставление с ассоциацией внешнего ключа)

Если использовать только @ManyToOne, то будет 2 таблицы. Например, компания, филиал.

Этот код выше сгенерирует 2 таблицы Company(company_id,name) и Branch(branch_id,name,company_company_id). Здесь Branch - это сторона-владелец, поскольку у нее есть ассоциация внешнего ключа.

CASE 3: (Сопоставление с ассоциацией внешнего ключа)

Если мы используем и @ManyToOne, и @OneToMany, тогда будут созданы 3 таблицы Company (id, name), Branch (id, name, company_id), Company_Branch (company_id, branch_id)

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

CASE 4: (однонаправленный @OneToMany с @JoinColumn) (сопоставление с ассоциацией внешнего ключа)

Если мы используем @OneToMany с @JoinColumn, то будет 2 таблицы. Например, компания, филиал

В приведенной выше сущности внутри @JoinColumn name относится к имени столбца внешнего ключа, которое равно companyId, то есть company_id, а rferencedColumnName указывает первичный ключ, то есть id сущность (Компания), к которой относится внешний ключ companyId.

CASE 5: (Сопоставление с ассоциацией внешнего ключа)

Если мы используем @ManyToOne с @JoinColumn, то будет 2 таблицы. Например, компания, филиал.

Аннотация @JoinColumn помогает Hibernate определить, что в таблице ветвей есть столбец внешнего ключа company_id, который определяет эту связь.

Branch будет иметь внешний ключ company_id, так что это сторона владельца.

Теперь, если мы сохраним 1 компанию и 2 филиала:

при удалении первой записи из дочерней коллекции:

company.getBranches().remove(0);

Hibernate выполняет два оператора вместо одного:

  • Сначала он сделает поле внешнего ключа нулевым (чтобы разорвать связь с родителем), а затем удалит запись.
Update branch set branch_id = null where where id = 1 delete from branch where  id = 1 ;

CASE 6: (Отображение с таблицей соединения)

Теперь давайте рассмотрим отношение one-to-many, где Person(id,name) связано с несколькими Veichle(id,name,number) и несколько транспортных средств могут принадлежать одному человеку.

Однажды на шоссе шериф Карлос нашел несколько брошенных автомобилей, скорее всего, угнанный. Теперь шериф должен обновить данные о транспортных средствах (номер, имя) в своей базе данных, но основная проблема в том, что у этих транспортных средств нет владельца, поэтому поле person_id(foreign key ) будет оставаться пустым.

Теперь шериф сохраняет два украденных автомобиля в БД следующим образом:

Vehicle vehicle1 = new Vehicle("ford", 1);   
Vehicle vehicle2 = new Vehicle("gmc", 2);   
List<Vehicle> vehicles = new ArrayList<>();   vehicles.add(vehicle1);   
vehicles.add(vehicle2); 
entityManager.persist(veichles);

Hibernate будет выполнять следующие операторы SQL:

insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);
id   |name|person_id|
-----|----|---------|
1    |ford|NULL     |
2    |benz|NULL     |
---------------------

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

Обычно мы думаем о many-to-many отношениях, когда рассматриваем join table, но использование таблицы соединений в этом случае может помочь нам устранить эти нулевые значения:

В этом подходе используется таблица соединений для хранения ассоциаций между объектами Branch и Company. @JoinTable аннотация была использована для создания этой связи.

В этом примере мы применили @JoinTable на стороне автомобиля (многие стороны).

Посмотрим, как будет выглядеть схема базы данных:

Приведенный выше код сгенерирует 3 таблицы, такие как person(id,name), vehicle(id,name) и vehicle_person(vehicle_id,person_id). Здесь vehicle_person будет содержать отношение внешнего ключа к сущностям Person и Vehicle.

Поэтому, когда шериф сохраняет данные о транспортном средстве, нулевое значение не должно сохраняться в таблице транспортных средств, потому что мы сохраняем ассоциацию внешнего ключа в vehicle_person таблицах, а не в таблице транспортных средств.

Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);   
List<Vehicle> vehicles = new ArrayList<>();   vehicles.add(vehicle1);   
vehicles.add(vehicle2); 
entityManager.persist(veichles);

Hibernate будет выполнять следующие операторы SQL:

insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");
id   |name|
-----|----|
1    |ford|
2    |benz|
-----------

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

@OneToMany Bi-directional Relationship

  • Двунаправленная @OneToMany ассоциация также требует @ManyToOne ассоциации на дочерней стороне. Хотя модель предметной области предоставляет две стороны для навигации по этой связи, за кулисами реляционная база данных имеет только один внешний ключ для этой связи.
  • У каждой двунаправленной ассоциации должна быть только одна сторона-владелец (дочерняя сторона), а другая сторона называется обратной (или themappedBy) стороной.
  • Если мы используем @OneToMany с установленным атрибутом mappedBy, тогда у нас будет двунаправленная ассоциация, то есть нам нужна @ManyToOne ассоциация на дочерней стороне, на которую ссылается mappedBy.
  • Элемент mappedBy определяет двунаправленную связь. Этот атрибут позволяет ссылаться на связанные сущности с обеих сторон.

Лучший способ сопоставить @OneToMany ассоциацию - положиться на @ManyToOne сторону для распространения всех изменений состояния объекта.

Если мы сохраним 2 ветки

Hibernate генерирует только один оператор SQL для каждой сохраненной сущности Branch:

insert into company (name, id) values ("company1",1); 
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);

Если мы удалим ветку:

Company company = entityManager.find( Company.class, 1L );   Branch branch = company.getBranches().get(0);   company.removeBranches(branch);

Выполняется только одна инструкция SQL удаления:

delete from Branch where id = 1

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

@JoinColumn Определяет столбец для присоединения к ассоциации сущностей или коллекции элементов. Аннотация @JoinColumn указывает, что этот объект является владельцем отношения. То есть в соответствующей таблице есть столбец с внешним ключом для указанной таблицы.

В приведенном выше примере ветка сущности-владельца имеет столбец соединения с именем company_id, который имеет внешний ключ для сущности компании, не являющейся владельцем.

При формировании двунаправленной связи разработчик приложения должен убедиться, что обе стороны всегда синхронизированы. addBranches() и removeBranches() - это служебные методы, которые синхронизируют оба конца при добавлении или удалении дочернего элемента (например, Branch).

В отличие от однонаправленного @OneToMany, двунаправленное связывание намного более эффективно при управлении состоянием сохраняемости коллекции.

Для удаления каждого элемента требуется только одно обновление (в котором для столбца внешнего ключа установлено значение NULL), и если жизненный цикл дочерней сущности привязан к своему родительскому объекту, так что дочерний элемент не может существовать без своего родителя, тогда мы можем аннотировать ассоциация с атрибутом удаления сироты и отсоединение дочернего элемента также вызовет оператор удаления для фактической строки дочерней таблицы.

ЗАМЕТКА:

  • В приведенном выше двунаправленном примере термин "родительский элемент" и "потомок" в Entity / OO / Model (т.е. в классе java) относится к невыигрышу / инверсии и стороне-владельцу в SQL соответственно.

С точки зрения java, компания здесь является родительской, а ветвь - дочерней. Поскольку ветка не может существовать без родителя.

С точки зрения SQL Branch - это сторона владельца, а компания - невыигрышная (обратная) сторона. Поскольку на N филиалов приходится 1 компания, каждый филиал содержит внешний ключ компании, к которой он принадлежит. Это означает, что филиал «владеет» (или буквально содержит) соединение (информацию). Это полная противоположность миру объектно-ориентированных моделей / моделей.

@OneToMany Bi-directional Relationship (Сопоставление с таблицей соединения)

Начиная с CASE 6 у нас есть однонаправленное сопоставление с @JoinTable, поэтому, если мы добавим атрибут mappedBy к сущности Person, связь станет двунаправленной.

РЕЗЮМЕ

На вышеупомянутом сопоставлении следует отметить несколько моментов:

  • В ассоциации @ManyToOne используется FetchType.LAZY, потому что в противном случае мы бы вернулись к выборке EAGER, что плохо сказывается на производительности.
  • Двунаправленные ассоциации всегда должны обновляться с обеих сторон, поэтому родительская сторона должна содержать комбинацию addChild и removeChild (компания на родительской стороне содержит два служебных метода addBranches и removeBranches). Эти методы гарантируют, что мы всегда синхронизируем обе стороны ассоциации, чтобы избежать проблем с повреждением объектов или реляционных данных.
  • Дочерняя сущность Branch реализует методы equals и hashCode. Поскольку мы не можем полагаться на естественный идентификатор для проверки равенства, нам нужно вместо этого использовать идентификатор объекта. Однако нам нужно сделать это правильно, чтобы равенство было согласованным для всех переходов состояний сущностей. Поскольку мы полагаемся на равенство для removeBranches, рекомендуется переопределить equals и hashCode для дочерней сущности в двунаправленной ассоциации.
  • Ассоциация @OneToMany по определению является родительской, даже если она однонаправленная или двунаправленная. Только родительская сторона ассоциации имеет смысл каскадировать переходы от состояния сущности к дочерним.

Цитаты:

[1]: Red Hat Hibernate Documentation
[2]: Hibernate Documentation
[3]: High-Performance Java Persistence Book, автор vlad mihalcea

Я считаю, что вы, должно быть, кое-что почерпнули из моей статьи, и буду рад, если вы поделитесь со мной своими мыслями.

Аплодируйте и подписывайтесь на меня, если вам понравилась эта статья!

"Вопросов? Комментарии?"