Hibernate Учебник по аннотации ко многим
1. Вступление
В этом кратком руководстве по Hibernate мы рассмотрим пример сопоставленияone-to-many с использованием аннотаций JPA - альтернативы XML.
Мы также узнаем, что такое двунаправленные отношения, как они могут создавать несоответствия и как помогает идея сканированияownership .
Дальнейшее чтение:
Spring Boot с Hibernate
Краткое практическое введение в интеграцию Spring Boot и Hibernate / JPA.
Обзор идентификаторов в Hibernate
Узнайте, как отобразить идентификаторы сущностей с помощью Hibernate.
2. Описание
Проще говоря,one-to-many mapping means that one row in a table is mapped to multiple rows in another table.
Давайте посмотрим на следующую диаграмму отношений сущностей, чтобы увидеть ассоциациюone-to-many:
Для этого примера мы реализуем систему корзины, в которой у нас есть таблица для каждой корзины и еще одна таблица для каждого товара. One cart can have many items, so here we have a one-to-many mapping.с
На уровне базы данных это работает так: у нас естьcart_id в качестве первичного ключа в таблицеcart, а такжеcart_id в качестве внешнего ключа вitems.
И способ, которым мы делаем это в коде, - это@OneToMany.
Давайте сопоставим классCart с объектомItems таким образом, чтобы отразить отношения в базе данных:
public class Cart {
//...
@OneToMany(mappedBy="cart")
private Set items;
//...
}
Мы также можем добавить ссылку наCart вItems, используя@ManyToOne, сделав это соотношениемbidirectional. Bidirectional означает, чтоwe are able to access items from carts, and also carts from items.
СвойствоmappedBy - это то, что мы используем, чтобы сообщить Hibernate, какую переменную мы используем для представления родительского класса в нашем дочернем классе.
Следующие технологии и библиотеки используются для разработки примера приложения Hibernate, реализующего ассоциациюone-to-many:
-
JDK 1.8 или новее
-
Hibernateс 5
-
Maven 3 или новее
-
База данных H2
3. Настроить
3.1. Настройка базы данных
Ниже представлен сценарий нашей базы данных для таблицCart иItems. Мы используем ограничение внешнего ключа для отображенияone-to-many:
CREATE TABLE `Cart` (
`cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `Items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cart_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `cart_id` (`cart_id`),
CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
Наша база данных готова, давайте перейдем к созданию примера проекта Hibernate.
3.2. Maven Зависимости
Затем мы добавим зависимости драйвера Hibernate и H2 в наш файлpom.xml. Зависимость Hibernate использует ведение журнала JBoss и автоматически добавляется как переходные зависимости:
-
Версия гибернации 5.2.7.Final
-
Версия драйвера H2 1.4.197
3.3. Конфигурация гибернации
Вот конфигурация Hibernate:
org.h2.Driver
jdbc:h2:mem:spring_hibernate_one_to_many
sa
org.hibernate.dialect.H2Dialect
thread
true
3.4. HibernateAnnotationUtil Класс
С классомHibernateAnnotationUtil нам просто нужно сослаться на новый файл конфигурации Hibernate:
private static SessionFactory sessionFactory;
private SessionFactory buildSessionFactory() {
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().
configure("hibernate-annotation.cfg.xml").build();
Metadata metadata = new MetadataSources(serviceRegistry).getMetadataBuilder().build();
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
return sessionFactory;
}
public SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
4. Модели
Связанные с отображением конфигурации будут выполнены с использованием аннотаций JPA в модельных классах:
@Entity
@Table(name="CART")
public class Cart {
//...
@OneToMany(mappedBy="cart")
private Set items;
// getters and setters
}
Обратите внимание, что аннотация@OneToMany используется для определения свойства в классеItems, которое будет использоваться для сопоставления переменнойmappedBy. Вот почему у нас есть свойство с именем «cart» в классеItems:
@Entity
@Table(name="ITEMS")
public class Items {
//...
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
public Items() {}
// getters and setters
}
Важно отметить, что аннотация@ManyToOne связана с переменной классаCart. Аннотация@JoinColumn ссылается на сопоставленный столбец.
5. В бою
В тестовой программе мы создаем класс с методомmain () для получения сеанса Hibernate и сохранения объектов модели в базе данных, реализующей ассоциациюone-to-many:
sessionFactory = HibernateAnnotationUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
System.out.println("Session created");
tx = session.beginTransaction();
session.save(cart);
session.save(item1);
session.save(item2);
tx.commit();
System.out.println("Cart ID=" + cart.getId());
System.out.println("item1 ID=" + item1.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());
System.out.println("item2 ID=" + item2.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());
Это результат нашей тестовой программы:
Session created
Hibernate: insert into CART values ()
Hibernate: insert into ITEMS (cart_id)
values (?)
Hibernate: insert into ITEMS (cart_id)
values (?)
Cart ID=7
item1 ID=11, Foreign Key Cart ID=7
item2 ID=12, Foreign Key Cart ID=7
Closing SessionFactory
6. Аннотация@ManyToOne
Как мы видели в разделе 2, мы можем указать отношениеmany-to-one с помощью аннотации@ManyToOne. Отображениеmany-to-one означает, что многие экземпляры этого объекта отображаются на один экземпляр другого объекта -many items in one cart.
The @ManyToOne annotation lets us create bidirectional relationships, too. Мы подробно рассмотрим это в следующих нескольких подразделах.
6.1. Несоответствия и право собственности
Теперь, еслиCart ссылается наItems, аItems, в свою очередь, не ссылается наCart,our relationship would be unidirectional. The objects would also have a natural consistency.с
Однако в нашем случае связь двунаправленная,bringing in the possibility of inconsistency.
Представим ситуацию, когда разработчик хочет добавитьitem1 кcart иitem2 кcart2, но делает ошибку, так что ссылки междуcart2 иitem2становятся несовместимыми:
Cart cart1 = new Cart();
Cart cart2 = new Cart();
Items item1 = new Items(cart1);
Items item2 = new Items(cart2);
Set itemsSet = new HashSet();
itemsSet.add(item1);
itemsSet.add(item2);
cart1.setItems(itemsSet); // wrong!
Как показано выше,item2 ссылается наcart2 whereascart2 не ссылается наitem2 —, и это плохо.
How should Hibernate save item2 to the database? Будет ли ссылка на внешний ключitem2cart1 илиcart2?
Мы разрешаем эту двусмысленность, используя идею отношенияowning side - ссылки, принадлежащие стороне-владельцу, имеют приоритет и сохраняются в базе данных.
6.2. items как сторона-владелец
Как указано вJPA specification в разделе 2.9,it’s a good practice to mark many-to-one side as the owning side.
Другими словами,Items может быть обратной сторонойowning side andCart, что мы и делали ранее.
Итак, как мы достигли этого?
Включая атрибутmappedBy в классCart, мы отмечаем его как обратную сторону.
В то же время мы также аннотируем полеItems.cart с помощью@ManyToOne, делаяItems стороной-владельцем.
Возвращаясь к нашему примеру «несогласованности», теперь Hibernate знает, чтоitem2‘s reference is more important and will save item2‘s reference to the database.
Проверим результат:
item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=2
Хотяcart ссылается наitem2 в нашем фрагменте, ссылкаitem2 наcart2 сохраняется в базе данных.
6.3. Cart как сторона-владелец
Также можно отметить сторонуone-to-many как сторону-владелец, а сторонуmany-to-one как обратную сторону.
Хотя это не рекомендуется, давайте попробуем.
Приведенный ниже фрагмент кода показывает реализацию стороныone-to-many как стороны-владельца:
public class ItemsOIO {
// ...
@ManyToOne
@JoinColumn(name = "cart_id", insertable = false, updatable = false)
private CartOIO cart;
//..
}
public class CartOIO {
//..
@OneToMany
@JoinColumn(name = "cart_id") // we need to duplicate the physical information
private Set items;
//..
}
Обратите внимание, как мы удалили элементmappedBy и установилиmany-to-one @JoinColumn какinsertable иupdatable какfalse.
Если мы запустим один и тот же код, результат будет противоположным:
item1 ID=1, Foreign Key Cart ID=1
item2 ID=2, Foreign Key Cart ID=1
Как показано выше, теперьitem2 принадлежитcart.
7. Заключение
Мы видели, как легко реализовать связьone-to-many с Hibernate ORM и базой данных H2 с помощью аннотаций JPA.
Также мы узнали о двунаправленных отношениях и о том, как реализовать понятие «владеющая сторона».
Исходный код этого руководства можно найтиover on GitHub.