Сохранение Java с JPA и Hibernate, Часть 1: Сущности и отношения

Java Persistence API (JPA) - это спецификация Java, которая устраняет разрыв между реляционными базами данных и объектно-ориентированным программированием. Это руководство из двух частей представляет JPA и объясняет, как объекты Java моделируются как сущности JPA, как определяются отношения между сущностями и как использовать JPA EntityManagerс шаблоном репозитория в ваших приложениях Java.

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

Что такое JPA?

См. «Что такое JPA? Введение в Java Persistence API», чтобы узнать об эволюции JPA и связанных с ним платформ, включая EJB 3.0. и JDBC.

Отношения объектов в JPA

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

Объекты Java в контексте реляционной базы данных определяются как сущности . Сущности помещаются в таблицы, занимающие столбцы и строки. Программисты используют внешние ключи и таблицы соединений для определения отношений между сущностями, а именно отношений «один к одному», «один ко многим» и «многие ко многим». Мы также можем использовать SQL (язык структурированных запросов) для извлечения и взаимодействия с данными в отдельных таблицах и в нескольких таблицах, используя ограничения внешнего ключа. Реляционная модель плоская, но разработчики могут писать запросы для извлечения данных и создания объектов из этих данных.

Несоответствие импеданса объект-отношения

Возможно, вы знакомы с термином несоответствие импеданса объектных отношений , который относится к проблеме сопоставления объектов данных с реляционной базой данных. Это несоответствие возникает из-за того, что объектно-ориентированный дизайн не ограничивается отношениями «один к одному», «один ко многим» и «многие ко многим». Вместо этого в объектно-ориентированном дизайне мы думаем об объектах, их атрибутах и ​​поведении, а также о том, как объекты связаны. Два примера - инкапсуляция и наследование:

  • Если объект содержит другой объект, мы определяем это с помощью инкапсуляции --a имеет-а отношения.
  • Если объект является конкретизацией другого объекта, мы определяем это по наследству --an есть- отношения.

Связь, агрегирование, композиция, абстракция, обобщение, реализация и зависимости - все это концепции объектно-ориентированного программирования, которые может быть сложно сопоставить с реляционной моделью.

ORM: объектно-реляционное отображение

Несоответствие между объектно-ориентированным дизайном и моделированием реляционных баз данных привело к созданию класса инструментов, разработанных специально для объектно-реляционного сопоставления (ORM). Инструменты ORM, такие как Hibernate, EclipseLink и iBatis, переводят модели реляционных баз данных, включая сущности и их отношения, в объектно-ориентированные модели. Многие из этих инструментов существовали до спецификации JPA, но без стандарта их функции зависели от поставщика.

Java Persistence API (JPA), впервые выпущенный как часть EJB 3.0 в 2006 году, предлагает стандартный способ аннотирования объектов, чтобы их можно было отображать и сохранять в реляционной базе данных. В спецификации также определена общая конструкция для взаимодействия с базами данных. Наличие стандарта ORM для Java обеспечивает согласованность реализаций поставщика, а также обеспечивает гибкость и надстройки. Например, хотя исходная спецификация JPA применима к реляционным базам данных, некоторые реализации поставщиков расширили JPA для использования с базами данных NoSQL.

Эволюция JPA

Первый выпуск JPA, версия 1.0, был опубликован в 2006 году посредством Java Community Process (JCP) как Java Specification Request (JSR) 220. Версия 2.0 (JSR 317) была опубликована в 2009 году, версия 2.1 (JSR 338) - в 2013 году, а версия 2.2 (служебный выпуск JSR 338) была опубликована в 2017 году. JPA 2.2 была выбрана для включения и продолжения разработки в Jakarta EE.

Начало работы с JPA

Java Persistence API - это спецификация, а не реализация: он определяет общую абстракцию, которую вы можете использовать в своем коде для взаимодействия с продуктами ORM. В этом разделе рассматриваются некоторые важные части спецификации JPA.

Вы узнаете, как:

  • Определите сущности, поля и первичные ключи в базе данных.
  • Создавайте отношения между сущностями в базе данных.
  • Работа со EntityManagerсвоим и его методами.

Определение сущностей

Чтобы определить сущность, вы должны создать класс, снабженный @Entityаннотацией. @EntityАннотаций является маркером аннотаций , который используется для обнаружения постоянных объектов. Например, если вы хотите создать объект книги, вы должны аннотировать его следующим образом:

 @Entity public class Book { ... } 

По умолчанию этот объект будет сопоставлен с Bookтаблицей, как определено заданным именем класса. Если вы хотите сопоставить этот объект с другой таблицей (и, необязательно, с конкретной схемой), вы можете использовать @Tableаннотацию для этого. Вот как бы вы сопоставили Bookкласс с таблицей BOOKS:

 @Entity @Table(name="BOOKS") public class Book { ... } 

Если таблица BOOKS была в схеме PUBLISHING, вы могли бы добавить схему в @Tableаннотацию:

 @Table(name="BOOKS", schema="PUBLISHING") 

Сопоставление полей с столбцами

Когда объект сопоставлен с таблицей, ваша следующая задача - определить ее поля. Поля определяются как переменные-члены в классе, при этом имя каждого поля сопоставляется с именем столбца в таблице. Вы можете переопределить это сопоставление по умолчанию, используя @Columnаннотацию, как показано здесь:

 @Entity @Table(name="BOOKS") public class Book { private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

В этом примере мы приняли сопоставление по умолчанию для nameатрибута, но указали настраиваемое сопоставление для isbnатрибута. nameАтрибут будет сопоставлен с именем столбца, но isbnатрибут будет отображен в столбце ISBN_NUMBER.

@ColumnАннотаций позволяет определить дополнительные свойства поля / столбца, включая длину, будь то обнуляемым, должен ли он быть уникальным, его точность и масштаб (если это значение в десятичной системе ), является ли вставляться и обновляемым, и так далее .

Указание первичного ключа

Одним из требований к таблице реляционной базы данных является то, что она должна содержать первичный ключ или ключ, который однозначно идентифицирует определенную строку в базе данных. В JPA мы используем @Idаннотацию для обозначения поля как первичного ключа таблицы. Первичный ключ должен быть примитивным типом Java, примитивной оболочкой, такой как Integerили Long, a String, a Date, a BigIntegerили a BigDecimal.

В этом примере мы сопоставляем idатрибут, который является Integer, со столбцом идентификатора в таблице BOOKS:

 @Entity @Table(name="BOOKS") public class Book { @Id private Integer id; private String name; @Column(name="ISBN_NUMBER") private String isbn; ... } 

Также возможно объединить @Idаннотацию с @Columnаннотацией, чтобы перезаписать отображение имени столбца первичного ключа.

Отношения между сущностями

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

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

Индивидуальные отношения

@OneToOneАннотаций используется для определения отношения один-к-одному между двумя сущностями. Например, у вас может быть Userсущность, содержащая имя пользователя, адрес электронной почты и пароль, но вы можете захотеть сохранить дополнительную информацию о пользователе (например, возраст, пол и любимый цвет) в отдельной UserProfileсущности. Такая @OneToOneаннотация упрощает разбиение данных и сущностей.

В Userприведенном ниже классе есть единственный UserProfileэкземпляр. Соответствует UserProfileединственному Userэкземпляру.

 @Entity public class User { @Id private Integer id; private String email; private String name; private String password; @OneToOne(mappedBy="user") private UserProfile profile; ... } 
 @Entity public class UserProfile { @Id private Integer id; private int age; private String gender; private String favoriteColor; @OneToOne private User user; ... } 

Пользы провайдер JPA UserProfile«ы userполе для отображения UserProfileв User. Отображение указывается в mappedByатрибуте @OneToOneаннотации.

Отношения один-ко-многим и многие-к-одному

@OneToManyИ @ManyToOneаннотаций облегчают обе стороны одного и того же отношения. Рассмотрим пример, когда у a Bookможет быть только одна Author, но у a Authorможет быть много книг. BookОрганизация будет определять @ManyToOneотношения с Authorи Authorорганизация будет определять @OneToManyотношения с Book.

 @Entity public class Book { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="AUTHOR_ID") private Author author; ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @OneToMany(mappedBy = "author") private List books = new ArrayList(); ... } 

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

Отношения многие-ко-многим

Finally, the @ManyToMany annotation facilitates a many-to-many relationship between entities. Here's a case where a Book entity has multiple Authors:

 @Entity public class Book { @Id private Integer id; private String name; @ManyToMany @JoinTable(name="BOOK_AUTHORS", [email protected](name="BOOK_ID"), [email protected](name="AUTHOR_ID")) private Set authors = new HashSet(); ... } 
 @Entity public class Author { @Id @GeneratedValue private Integer id; private String name; @ManyToMany(mappedBy = "author") private Set books = new HashSet(); ... } 

In this example, we create a new table, BOOK_AUTHORS, with two columns: BOOK_ID and AUTHOR_ID. Using the joinColumns and inverseJoinColumns attributes tells your JPA framework how to map these classes in a many-to-many relationship. The @ManyToMany annotation in the Author class references the field in the Book class that manages the relationship; namely the authors property.

That's a quick demo for a fairly complex topic. We'll dive further into the @JoinTable and @JoinColumn annotations in the next article.

Working with the EntityManager

EntityManager- это класс, выполняющий взаимодействия с базой данных в JPA. Он инициализируется через файл конфигурации с именем persistence.xml. Этот файл находится в META-INFпапке в вашем CLASSPATH, которая обычно упакована в ваш файл JAR или WAR. persistence.xmlФайл содержит:

  • Именованная «единица сохранения состояния», которая определяет используемую вами структуру сохраняемости, например Hibernate или EclipseLink.
  • Набор свойств, определяющих, как подключиться к вашей базе данных, а также любые настройки в структуре сохраняемости.
  • Список классов сущностей в вашем проекте.

Давайте посмотрим на пример.

Настройка EntityManager

Сначала мы создаем, EntityManagerиспользуя EntityManagerFactoryполученные из Persistenceкласса:

 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books"); EntityManager entityManager = entityManagerFactory.createEntityManager(); 

In this case we've created an EntityManager that is connected to the "Books" persistence unit, which we've configured in the persistence.xml file.

The EntityManager class defines how our software will interact with the database through JPA entities. Here are some of the methods used by EntityManager:

  • find retrieves an entity by its primary key.
  • createQuery creates a Query instance that can be used to retrieve entities from the database.
  • createNamedQuery loads a Query that has been defined in a @NamedQuery annotation inside one of the persistence entities. Named queries provide a clean mechanism for centralizing JPA queries in the definition of the persistence class on which the query will execute.
  • getTransaction defines an EntityTransaction to use in your database interactions. Just like database transactions, you will typically begin the transaction, perform your operations, and then either commit or rollback your transaction. The getTransaction() method lets you access this behavior at the level of the EntityManager, rather than the database.
  • merge() adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using merge(), objects are not managed.
  • persist adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using persist(), objects are managed.
  • refresh refreshes the state of the current entity from the database.
  • flush synchronizes the state of the persistence context with the database.

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