Рубрики
Без рубрики

Руководство по JPA с гибернацией – Базовое сопоставление

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

Автор оригинала: François Dupire.

Вступление

API сохраняемости Java (JPA) является стандартом сохраняемости экосистемы Java. Это позволяет нам сопоставлять нашу модель предметной области непосредственно со структурой базы данных, а затем дает нам возможность гибко манипулировать только объектами в нашем коде. Это позволяет нам не возиться с громоздкими компонентами JDBC, такими как Соединение , Набор результатов и т.д.

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

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

Что такое JPA?

API сохранения Java

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

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

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

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

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

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

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

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

Наш Пример

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

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

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

приступая к работе

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


    org.hibernate
    hibernate-core
    ${version}

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


    com.h2database
    h2
    ${version}

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

На данный момент это будет выглядеть так:



    
        com.fdpro.clients.stackabuse.jpa.domain.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() передается как найденный студент действительно тот, кого мы ищем.

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

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

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

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

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


Здесь мы видим, что определяем пространство имен, 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.

Блок Стойкости

Затем, сразу после открывающего тега, мы объявили <единица сохранения> тег:


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

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

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

Затем первое, что мы замечаем в блоке сохранения, – это тег <класс> с полным именем нашего Ученика класса:

com.fdpro.clients.stackabuse.jpa.domain.Student

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

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

База данных

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





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

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

Схема

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


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

Классы Отображения

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

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

@Entity
public class Student {}

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

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

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

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

Поля Сопоставления

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

Основы

Давайте начнем с самых простых. Существует множество типов, которые автоматически обрабатываются JPA:

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

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

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

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

Затем эти поля будут сопоставлены столбцам с именами ФАМИЛИЯ и ИМЯ соответственно.

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

public class Student {
    private String lastName;

    @Column(name = "FIRST_NAME")
    private String firstName;
}

Точно так же наше поле имя сопоставляется со столбцом ИМЯ|/.

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

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

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

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


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

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;
}

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

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

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

@GeneratedValue(strategy = GenerationType.TYPE)

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

Даты

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

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

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

Давайте начнем с отображения поля Дата , скажем, дата рождения студента:

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

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

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

Для каждой из этих возможностей существует перечисление значение: ДАТА , ВРЕМЯ и МЕТКА ВРЕМЕНИ соответственно.

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

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

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

public class Student {
    private LocalDate birthDate;
}

И вот так просто, наше поле нанесено на карту!

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

Еще одним видом поля, требующим особого внимания, являются перечисления с. Из коробки JPA предлагает аннотацию для сопоставления перечисления s – @Перечислено . Эта аннотация принимает аргумент типа Перечисления , который является перечислением , предлагающим значения ПОРЯДКОВЫЙ НОМЕР и СТРОКА .

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

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

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

public enum Gender {
    MALE,
    FEMALE
}

public class Student {
    private Gender gender;
}

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

public class Student {
    @Enumerated
    private Gender gender;
}

Но как насчет спора, о котором мы говорили ранее? По умолчанию выбранный Тип перечисления является ПОРЯДКОВЫМ . Возможно, мы захотим изменить это на STRING , хотя:

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

И вот мы здесь, пол студентов теперь будет отображаться как МУЖСКОЙ и ЖЕНСКИЙ в базе данных.

Преобразователи

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

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

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

Итак, как нам достичь этого последнего? С помощью конвертера. Прежде всего, мы должны создать Да Нет логического преобразователя класс, который реализует интерфейс AttributeConverter :

public class YesNoBooleanConverter implements AttributeConverter {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return null;
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return null;
    }
}

Затем мы замечаем, что существует два метода для реализации. Первый преобразует наш логический в Строку для хранения в базе данных, в то время как другой преобразует значение базы данных в логическое . Давайте их реализуем:

public class YesNoBooleanConverter implements AttributeConverter {
    @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;
}

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

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

com.fdpro.clients.stackabuse.jpa.domain.converters.YesNoBooleanConverter

И как это работает. Однако что мы можем сделать, если в нашей базе данных есть несколько столбцов ” да “/”нет”, и мы считаем утомительным постоянно повторять аннотацию @Convert для этих типов? Затем мы можем добавить @Конвертер аннотацию к вашему Да Нет логического преобразователя классу и передать ему аргумент автоматическое применение .

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

@Converter(autoApply = true)
public class YesNoBooleanConverter implements AttributeConverter

А затем удалите @Convert аннотацию из класса “Студент”:

public class Student {
    private boolean wantsNewsletter;
}

Встроенный

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

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

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

public class Student {
    private Address address;
}

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

@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 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);

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

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

Допустим, столбцы таблицы STUDY для адреса: 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 и их взаимоотношения. Мы настроили режим гибернации в проекте Maven и погрузились в базовое объектно-реляционное сопоставление.

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