Hibernate Учебник по аннотации ко многим

Hibernate Учебник по аннотации ко многим

1. Вступление

В этом кратком руководстве по Hibernate мы рассмотрим пример сопоставленияone-to-many с использованием аннотаций JPA - альтернативы XML.

Мы также узнаем, что такое двунаправленные отношения, как они могут создавать несоответствия и как помогает идея сканированияownership .

Дальнейшее чтение:

Spring Boot с Hibernate

Краткое практическое введение в интеграцию Spring Boot и Hibernate / JPA.

Read more

Обзор идентификаторов в Hibernate

Узнайте, как отобразить идентификаторы сущностей с помощью Hibernate.

Read more

2. Описание

Проще говоря,one-to-many mapping means that one row in a table is mapped to multiple rows in another table.

Давайте посмотрим на следующую диаграмму отношений сущностей, чтобы увидеть ассоциациюone-to-many:

image

Для этого примера мы реализуем систему корзины, в которой у нас есть таблица для каждой корзины и еще одна таблица для каждого товара. 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

Посетите центральный репозиторий Maven для получения последних версий зависимостейHibernate иH2.

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.