Руководство по JPA с Hibernate - Базовое сопоставление

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

Вступление

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

Мы составим исчерпывающее руководство по использованию JPA с Hibernate в качестве поставщика. В этой статье мы рассмотрим конфигурацию и базовое отображение в Hibernate:

  • Руководство по JPA с Hibernate: базовое сопоставление ( вы здесь )
  • Руководство по JPA с Hibernate: отображение отношений
  • Руководство по JPA с Hibernate: Сопоставление наследования ( скоро! )
  • Руководство по JPA с Hibernate: запросы ( скоро! )

Что такое JPA?

Java Persistence API

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

Он был разработан как часть JSR 220 группой экспертов по программному обеспечению EJB 3.0, хотя он посвящен не только разработке программного обеспечения EJB.

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

Следовательно, чтобы использовать его, мы должны предоставить реализацию API. К счастью для нас, мы не обязаны писать это сами, уже доступны реализации, называемые поставщиками :

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

Объектно-реляционное сопоставление

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

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

Наш пример

Прежде чем начать, мы представим пример, который мы будем использовать на протяжении всей серии. Идея состоит в том, чтобы отобразить модель школы со студентами, проходящими курсы, проводимые учителями.

Вот как выглядит окончательная модель:

модель предметнойобласти{.ezlazyload}

Как мы видим, есть несколько классов с некоторыми свойствами. И эти классы имеют между собой отношения. К концу этой серии мы сопоставим все эти классы с таблицами базы данных и сможем сохранять и извлекать данные из базы данных с их помощью.

Начиная

Давайте сразу перейдем к делу с работающим, хотя и минималистичным примером. Прежде всего нам нужно импортировать зависимость JPA / Hibernate . Используя Maven, добавим необходимые зависимости в наш pom.xml :

 <dependency> 
 <groupId>org.hibernate</groupId> 
 <artifactId>hibernate-core</artifactId> 
 <version>${version}</version> 
 </dependency> 

Нам также понадобится база данных для работы. H2 легкий и простой, поэтому мы воспользуемся этим:

 <dependency> 
 <groupId>com.h2database</groupId> 
 <artifactId>h2</artifactId> 
 <version>${version}</version> 
 </dependency> 

Затем нам нужно будет создать persistence.xml в нашем пути к классам в каталоге META-INF Этот файл используется для настройки JPA, сообщая поставщика, какую базу данных мы собираемся использовать и как подключиться к ней, какие классы нужно сопоставить и т. Д.

Пока это будет выглядеть так:

 <?xml version="1.0" encoding="UTF-8" ?> 
 <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" 
 version="2.2"> 
 <persistence-unit name="guide-to-jpa-with-hibernate"> 
 <class>com.fdpro.clients.stackabuse.jpa.domain.Student</class> 
 
 <properties> 
 <!-- Database configuration --> 
 <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> 
 <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/> 
 <property name="javax.persistence.jdbc.user" value="user"/> 
 <property name="javax.persistence.jdbc.password" value="password"/> 
 
 <!-- Schema configuration --> 
 <property name="javax.persistence.schema-generation.database.action" value="create"/> 
 </properties> 
 </persistence-unit> 
 </persistence> 

Пока не будем особо утруждать себя смыслом всего этого. Наконец, мы собираемся отобразить наш первый класс, Student :

 @Entity 
 public class Student { 
 @Id 
 private Long id; 
 
 public Long id() { 
 return id; 
 } 
 
 public void setId(Long id) { 
 this.id = id; 
 } 
 } 

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

Теперь давайте посмотрим, как управлять этой сущностью:

 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("guide-to-jpa-with-hibernate"); 
 EntityManager entityManager = entityManagerFactory.createEntityManager(); 
 
 entityManager.getTransaction().begin(); 
 
 Student student = new Student(); 
 student.setId(1L); 
 entityManager.persist(student); 
 
 entityManager.getTransaction().commit(); 
 entityManager.clear(); 
 
 Student foundStudent = entityManager.find(Student.class, 1L); 
 
 assertThat(foundStudent).isEqualTo(student); 
 
 entityManager.close(); 

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

Все, что нам нужно знать на данный момент, это то, что этот код позволяет нам сохранить объект Student в базе данных, а затем получить его. assertThat() передается, так как на foundStudent деле foundStudent - это тот, который мы ищем.

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

Конфигурация

Пришло время углубиться в API, начав с файла конфигурации persistence.xml Посмотрим, что мы должны туда вставить.

Пространство имен, схема и версия

Прежде всего, вот открывающий тег:

 <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" 
 version="2.2"> 

Здесь мы видим, что мы определяем пространство имен http://xmlns.jcp.org/xml/ns/persistence и расположение схемы http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd (обратите внимание на версию).

Кроме того, хотя мы уже упоминали об этом в расположении схемы, мы снова упоминаем версию.

Итак, здесь мы работаем с версией 2.2 JPA.

Единица настойчивости

Затем сразу после открывающего тега мы объявили <persistence-unit> :

 <persistence-unit name="guide-to-jpa-with-hibernate"> 

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

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

Сопоставленные классы

Затем первое, что мы замечаем в блоке сохранения состояния, - это <class> с квалифицированным именем нашего класса Student

 <class>com.fdpro.clients.stackabuse.jpa.domain.Student</class> 

Это потому, что мы должны вручную определять каждый сопоставленный класс в файле persistence.xml

Такие фреймворки, как Spring, значительно упростили этот процесс, представив нам packagesToScan , которое автоматически сканирует целые пакеты на предмет аннотаций.

База данных

После этого идут свойства, начиная с конфигурации базы данных:

 <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> 
 <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/> 
 <property name="javax.persistence.jdbc.user" value="user"/> 
 <property name="javax.persistence.jdbc.password" value="password"/> 

Здесь несколько строк, давайте рассмотрим их одну за другой:

  • javax.persistence.jdbc.driver : полное имя драйвера, необходимое для связи с базой данных.
  • javax.persistence.jdbc.url : URL-адрес базы данных, здесь мы указываем, что хотим взаимодействовать с находящимся в памяти экземпляром H2 .
  • javax.persistence.jdbc.user : пользователь для подключения к базе данных. На самом деле не имеет значения, что мы туда помещаем, поскольку у экземпляра H2 нет конкретного пользователя. Мы даже могли бы опустить эту строку.
  • javax.persistence.jdbc.password : пароль, соответствующий пользователю. То же самое применимо и к экземпляру H2, мы можем опустить это или поставить все, что захотим.

Схема

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

 <property name="javax.persistence.schema-generation.database.action" value="create"/> 

В производственном приложении с постоянной базой данных мы, вероятно, не будем полагаться на этот механизм для создания схемы нашей базы данных.

Классы сопоставления

Теперь, когда наша минимальная конфигурация рассмотрена, давайте перейдем к основной теме: сопоставлениям. Напоминаем, что отображение - это механизм привязки наших классов Java к таблицам базы данных.

Итак, первое, что мы должны сделать, чтобы сопоставить класс с таблицей базы данных, - это аннотировать его аннотацией @Entity

 @Entity 
 public class Student {} 

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

Но что, если мы хотим сопоставить этот класс с таблицей с другим именем, например STUD ? Затем мы должны использовать @Table , которая принимает атрибут name:

 @Entity 
 @Table(name = "STUD") 
 public class Student {} 

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

Отображение полей

Теперь давайте перейдем к сопоставлению наших полей со столбцами базы данных. В зависимости от полей доступно несколько методов.

Основы

Начнем с простых. JPA автоматически обрабатывает несколько типов:

  • Примитивы
  • Обертки примитивов
  • String
  • BigInteger , BigDecimal
  • Даты (их сопоставление может потребовать некоторой настройки, поэтому у них будет свой собственный раздел)

Когда мы помещаем одно поле одного из этих типов в наши классы, они автоматически сопоставляются с одноименным столбцом.

Итак, если бы мы добавили фамилию и имя нашему Student :

 public class Student { 
 private String lastName; 
 private String firstName; 
 } 

Затем эти поля будут сопоставлены столбцам с именами LASTNAME и FIRSTNAME соответственно.

Опять же, мы определенно хотели бы изменить имена наших столбцов. Для этого нам нужно будет использовать @Column и ее атрибут name

 public class Student { 
 private String lastName; 
 
 @Column(name = "FIRST_NAME") 
 private String firstName; 
 } 

Точно так же наше firstName сопоставляется со столбцом FIRST_NAME

Посмотрим, сработает ли это, найдя студента из базы данных. Прежде всего, давайте создадим файл набора данных, data.sql , который мы поместим в корень нашего пути к классам:

 insert into STUD(ID, LASTNAME, FIRST_NAME) values(2, 'Doe', 'John'); 

Затем скажем JPA загрузить этот набор данных. Это делается с помощью javax.persistence.sql-load-script-source в нашем persistence.xml :

 <property name="javax.persistence.sql-load-script-source" value="data.sql"/> 

Наконец, мы можем написать тест, подтверждающий, что мы получили нашего ученика и его данные верны:

 Student foundStudent = entityManager.find(Student.class, 2L); 
 
 assertThat(foundStudent.id()).isEqualTo(2L); 
 assertThat(foundStudent.lastName()).isEqualTo("Doe"); 
 assertThat(foundStudent.firstName()).isEqualTo("John"); 

Идентификаторы

Теперь давайте быстро поговорим об идентификаторах. О них можно много сказать, но здесь мы коснемся только основ. Чтобы объявить идентификатор, нам нужно использовать аннотацию @Id

 public class Student { 
 @Id 
 private Long id; 
 } 

Но что такое ID? Это отображение первичного ключа нашей таблицы, то есть столбца, идентифицирующего наши строки. Иногда мы хотим, чтобы наши первичные ключи создавались автоматически. Чтобы сделать это в JPA, мы должны затем использовать @GeneratedValue вместе с @Id :

 public class Student { 
 @Id 
 @GeneratedValue 
 private Long id; 
 } 

Существует несколько стратегий создания ценности , которые вы можете указать, установив флаг strategy

 @GeneratedValue(strategy = GenerationType.TYPE) 

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

Даты

Мы упоминали даты ранее, говоря, что они, естественно, обрабатывались JPA, но с некоторыми особенностями.

Итак, прежде всего, давайте вспомним , Java предоставляет нам два даты и времени представления: Один в java.util пакет ( Date , Timestamp и т.д.) и один в java.time пакете ( LocalDate , LocalTime , LocalDateTime , так далее.).

Первые обрабатываются с помощью @Temporal , а вторые обрабатываются из коробки, но только начиная с версии 2.2 JPA. Раньше нам приходилось использовать конвертеры, которые мы увидим позже в этой статье для унаследованных проектов.

Начнем с сопоставления Date , скажем, даты рождения студента:

 public class Student { 
 @Temporal(TemporalType.DATE) 
 private Date birthDate; 
 } 

Мы можем заметить, что @Temporal принимает аргумент типа TemporalType . Это необходимо указать для определения типа столбца в базе данных.

Будет ли у него свидание? Время? Дата и время?

Для каждой из этих возможностей enum DATE , TIME и TIMESTAMP соответственно.

Нам нужно это сделать, потому что Date хранит дату и время вместе, что означает, что мы должны указать, какая часть данных нам действительно нужна.

Новое представление времени в Java упростило для нас эту задачу, поскольку существует определенный тип для даты, времени и даты и времени.

Таким образом, если мы хотим использовать LocalDate вместо Date , мы можем просто сопоставить поле без аннотации @Temporal

 public class Student { 
 private LocalDate birthDate; 
 } 

И так просто, наше поле отображается!

Перечисления

Другой вид полей, требующий особого внимания, - это enum s. @Enumerated JPA предлагает аннотацию для сопоставления enum s - @Enumerated. Эта аннотация принимает аргумент типа EnumType , который представляет собой enum предлагающее значения ORDINAL и STRING .

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

Кроме того, если мы работаем с устаревшей базой данных, мы можем быть вынуждены использовать имена, уже сохраненные в наших enum , что нам может не понадобиться, если эти имена не имеют смысла. Тогда решением было бы дать enum поле, представляющее значение базы данных, позволяющее нам выбрать любое имя константы, которое мы сочтем подходящим, и использовать преобразователь для сопоставления типа enum Мы увидим конвертеры в следующем разделе.

Итак, что все это говорит о нашем примере со Student Допустим, мы хотим добавить ученику пол, который представлен enum :

 public enum Gender { 
 MALE, 
 FEMALE 
 } 
 
 public class Student { 
 private Gender gender; 
 } 

Затем мы должны добавить @Enumerated к нашему гендерному полю, чтобы оно было сопоставлено:

 public class Student { 
 @Enumerated 
 private Gender gender; 
 } 

Но как насчет аргумента, о котором мы говорили ранее? По умолчанию выбранный EnumType - ORDINAL . Мы могли бы изменить это на STRING :

 public class Student { 
 @Enumerated(EnumType.STRING) 
 private Gender gender; 
 } 

И вот мы, пол студентов теперь будет отображаться в базе данных MALE и FEMALE

Конвертеры

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

Скажем, например, у нас есть столбец, в котором сообщается, хочет ли ученик получать школьный информационный бюллетень или нет, но данные, хранящиеся в этом столбце, представляют собой Y и N для «да» и «нет» соответственно. Тогда у нас есть несколько возможностей:

  • Сопоставьте столбец со String , но это будет неудобно использовать в коде.
  • Сопоставьте столбец с каким-то enum YesNo , но это кажется излишним.
  • Сопоставьте столбец с Boolean , и теперь мы чего-то добились!

Итак, как нам достичь этого последнего? Используя конвертер. Прежде всего, мы должны создать YesNoBooleanConverter , который реализует интерфейс AttributeConverter

 public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> { 
 @Override 
 public String convertToDatabaseColumn(Boolean attribute) { 
 return null; 
 } 
 
 @Override 
 public Boolean convertToEntityAttribute(String dbData) { 
 return null; 
 } 
 } 

Мы замечаем, что есть два метода для реализации. Первый преобразует наше boolean значение в String для хранения в базе данных, а другой преобразует значение базы данных в boolean . Реализуем их:

 public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> { 
 @Override 
 public String convertToDatabaseColumn(Boolean attribute) { 
 return attribute ? "Y" : "N"; 
 } 
 
 @Override 
 public Boolean convertToEntityAttribute(String dbData) { 
 return dbData.equals("Y"); 
 } 
 } 

Здесь мы считаем, что наш столбец всегда будет содержать значение, несмотря ни на что, и что это значение всегда будет Y или N Возможно, нам придется написать немного больше кода в более сложных случаях (например, для обработки null значений).

Что нам теперь с этим делать? Мы сопоставим наше студенческое поле с @Convert , которая принимает наш класс в качестве аргумента:

 public class Student { 
 @Convert(converter = YesNoBooleanConverter.class) 
 private boolean wantsNewsletter; 
 } 

Обратите внимание, как мы сопоставили наше поле с примитивным boolean , а не с типом оболочки. Мы можем это сделать, потому что знаем, что наш столбец всегда будет содержать значение и что написанный нами преобразователь никогда не возвращает null в качестве значения.

Но мы еще не закончили. Мы все равно должны добавить конвертер в наш файл persistence.xml

 <class>com.fdpro.clients.stackabuse.jpa.domain.converters.YesNoBooleanConverter</class> 

И теперь это работает. Однако что мы можем сделать, если в нашей базе данных есть несколько столбцов типа «да / нет», и нам утомительно постоянно повторять @Convert для этих типов? Затем мы можем добавить @Converter к нашему YesNoBooleanConverter и передать ему аргумент autoApply = true

Затем каждый раз, когда у нас есть String в базе данных, которое мы хотим отобразить как Boolean в нашем коде, этот преобразователь будет применяться. Добавим:

 @Converter(autoApply = true) 
 public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> 

Затем удалите @Convert из класса Student:

 public class Student { 
 private boolean wantsNewsletter; 
 } 

Встроенный

Наконец, поговорим о встроенных типах. Для чего они? Представим, что наша STUD содержит информацию об адресах учащихся: улица, номер и город. Но в нашем коде мы хотели бы использовать Address , сделав его многоразовым и, прежде всего, объектом (потому что мы все еще занимаемся объектно-ориентированным программированием!).

Теперь давайте сделаем это в коде:

 public class Address { 
 private String street; 
 private String number; 
 private String city; 
 } 
 
 public class Student { 
 private Address address; 
 } 

Конечно, пока так не пойдет. Мы должны сообщить JPA, какое отношение он имеет к этому полю. Для этого @Embedded аннотации @Embeddable и @Embedded. Первый пойдет в наш Address а второй - в поле:

 @Embeddable 
 public class Address { 
 private String street; 
 private String number; 
 private String city; 
 } 
 
 public class Student { 
 @Embedded 
 private Address address; 
 } 

Давайте снова посмотрим наш набор данных:

 insert into STUD(ID, LASTNAME, FIRST_NAME, BIRTHDATE, GENDER, WANTSNEWSLETTER, STREET, NUMBER, CITY) 
 values(2, 'Doe', 'John', TO_DATE('2000-02-18', 'YYYY-MM-DD'), 'MALE', 'Y', 'Baker Street', '221B', 'London'); 

С самого начала он немного изменился. Здесь вы можете видеть, что мы добавили все столбцы из предыдущих разделов, а также улицу, номер и город. Мы сделали это так, как будто поля принадлежат Student , а не классу Address

Итак, наша сущность по-прежнему отображается правильно? Давайте попробуем:

 Student foundStudent = entityManager.find(Student.class, 2L); 
 
 assertThat(foundStudent.id()).isEqualTo(2L); 
 assertThat(foundStudent.lastName()).isEqualTo("Doe"); 
 assertThat(foundStudent.firstName()).isEqualTo("John"); 
 assertThat(foundStudent.birthDateAsDate()).isEqualTo(DateUtil.parse("2000-02-18")); 
 assertThat(foundStudent.birthDateAsLocalDate()).isEqualTo(LocalDate.parse("2000-02-18")); 
 assertThat(foundStudent.gender()).isEqualTo(Gender.MALE); 
 assertThat(foundStudent.wantsNewsletter()).isTrue(); 
 
 Address address = new Address("Baker Street", "221B", "London"); 
 assertThat(foundStudent.address()).isEqualTo(address); 

Он все еще работает хорошо!

А что, если мы захотим повторно использовать Address для других сущностей, но имена столбцов будут другими? Не будем паниковать, JPA покрывает @AttributeOverride аннотацией @AttributeOverride.

Скажем, STUD таблицы STUD для адреса: ST_STREET , ST_NUMBER и ST_CITY . Может показаться, что мы становимся креативными, но давайте будем честными, устаревший код и базы данных - определенно творческие места.

Затем мы должны сообщить JPA, что мы переопределяем сопоставление по умолчанию:

 public class Student { 
 @AttributeOverride(name = "street", column = @Column(name = "ST_STREET")) 
 @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER")) 
 @AttributeOverride(name = "city", column = @Column(name = "ST_CITY")) 
 private Address address; 
 } 

И вот оно, наше отображение исправлено. Следует отметить, что, начиная с JPA 2.2 , @AttributeOverride является повторяемой.

До этого нам пришлось бы обернуть их аннотацией @AttributeOverrides

 public class Student { 
 @AttributeOverrides({ 
 @AttributeOverride(name = "street", column = @Column(name = "ST_STREET")), 
 @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER")), 
 @AttributeOverride(name = "city", column = @Column(name = "ST_CITY")) 
 }) 
 private Address address; 
 } 

Заключение

В этой статье мы подробно рассмотрим, что такое JPA и Hibernate, а также их взаимосвязь. Мы настроили Hibernate в проекте Maven и погрузились в базовое объектно-реляционное сопоставление.

Код для этой серии можно найти на GitHub .

comments powered by Disqus