Hello, JPA (и Hibernate)

JDBC предоставляет вполне достаточный интерфейс для работы с базами данных в Java. Однако этот интерфейс весьма многословен и довольно неудобен и даже Spring JDBC не делает его сильно лучше. По сути дела проблема в том, что реляционные базы данных работают с таблицами и отношениями между ними, в то время как в Java работают с объектами и их иерархиями. Поэтому приходится для каждого объекта или таблицы писать класс отображения одного в другое. Этот процесс называется ORM — object-relational mapping (объектно-реляционное отображение). И, к счастью, существуют готовые ORM решения, которые сами переводят данные из одного вида в другой и обратно.

Более того, таких решений настолько много, что в Java даже появился стандартный интерфейс, который направлен на стандартизацию ORM продуктов. Этот интерфейс называется JPA — Java persistence API и описывает требования к объектам, для сохранения их в базах данных, интерфейсы для сохранения объектов и интерфейсы для получения объектов из БД.

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

Попробуем использовать их вместе.

Подготовка

Как всегда, начнём с пустого maven проекта, в который добавим Hibernate реализацию JPA и встраиваемую базу H2:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <h2.version>1.4.190</h2.version>
  <hibernate.version>5.1.0.Final</hibernate.version>
</properties>


<dependencies>
  <dependency>
    <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>${h2.version}</version>
  </dependency>
  <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-entitymanager</artifactId>
     <version>${hibernate.version}</version>
  </dependency>
</dependencies>

Кроме того я добавил project lombok для упрощения кода и библиотеки модульного тестирования.

Entity

Все классы, которые могут быть сохранены в базе данных называются entity(сущность) и на них налагаются определённые требования:

  • Наличие публично доступного конструктора без аргументов
  • Класс, его методы и сохраняемые поля не должны быть final
  •  Если объект Entity класса будет передаваться по значению как отдельный объект (detached object), например через удаленный интерфейс (through a remote interface), он так же должен реализовывать Serializable интерфейс.
  • Сохраняемые поля должны быть доступны только с использованием методов класса.

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

У нас будет простой entity класс, с тремя полями:

/**
* Greeting entity.
*/
@Entity
public class Greeter {
    /**
     * Primary key.
     */
    @Id
    @GeneratedValue
    @Getter
    @Setter
    private Integer id;


    /**
     * Greeting.
     */
    @Getter
    @Setter
    private String greeting;


    /**
     * Greeting target.
     */
    @Getter
    @Setter
    private String target;
}

Настройка JPA

Вторым важным шагом после создания объектов для данных будет настройка JPA. Для этого создадим файл META-INF/persistence.xml и напишем в него конфигурацию JPA.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    <persistence-unit name="ru.easyjava.data.jpa.hibernate">
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
        </properties>
    </persistence-unit>
</persistence>

Главный и обязательный тег в этом в файле:  <persistence-unit name=«ru.easyjava.data.jpa.hibernate»>, который задаёт имя конкретного persistence unit (а их может быть несколько) и его опции.

Вложенные опции относятся уже к конкретной реализации (Hibernate в моём случае) и настраивают поведение реализации. Я задаю базу H2 и соединение с этой базой. Кроме того, опция  <property name=«hibernate.hbm2ddl.auto» value=«update»/> говорит Hibernate, что надо сканировать все классы, имеющие аннотацию @Entity  и обновить схему таблицы базы данных сообразно этим классам. Говоря более простым языком — с этой опцией Hibernate сам создаёт таблицы для ваших классов.

Использование JPA

Использование JPA состоит, вообщем-то, из двух частей — сохранение объектов в базу и чтение объектов из базы:

Greeter greetJpa = new Greeter();
greetJpa.setGreeting("Hello");
greetJpa.setTarget("JPA");


Greeter greetHibernate = new Greeter();
greetHibernate.setGreeting("Hello");
greetHibernate.setTarget("Hibernate");


entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate");
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(greetJpa);
em.persist(greetHibernate);
em.getTransaction().commit();
em.close();

Вначале создаётся EntityManagerFactory  для конкретного persistence unit, заданного своим именем. Из фабрики по мере надобности получаются EntityManager , с которыми уже и работают. Можно рассматривать их как аналог DataSource/Connection. Затем открывается транзакция, заранее созданные объекты сохраняются в базу, транзакция подтверждается и EntityManager  закрывается. И ни капли SQL!

Попробуем прочитать объекты обратно:

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.createQuery("from Greeter", Greeter.class)
  .getResultList()
  .forEach(g -> System.out.println(String.format("%s, %s!", g.getGreeting(), g.getTarget())));
em.getTransaction().commit();
em.close();

Опять, получаем EntityManager, открываем транзакцию и делаем запрос. Запрос делается на JPQL, языке подобном SQL, только ориентированном на ORM. JPA автоматически создаст корректный запрос, получит данные из базы данных, создаст экземпляры объектов и наполнит их данными.

Результат исполнения это подтверждает:

мар 03, 2016 11:56:45 AM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
name: ru.easyjava.data.jpa.hibernate
...]
мар 03, 2016 11:56:45 AM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
мар 03, 2016 11:56:45 AM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
мар 03, 2016 11:56:45 AM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
мар 03, 2016 11:56:45 AM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
мар 03, 2016 11:56:46 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
мар 03, 2016 11:56:46 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [null] at URL [jdbc:h2:mem:test]
мар 03, 2016 11:56:46 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {}
мар 03, 2016 11:56:46 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
мар 03, 2016 11:56:46 AM org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
мар 03, 2016 11:56:46 AM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
мар 03, 2016 11:56:47 AM org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl processGetTableResults
INFO: HHH000262: Table not found: Greeter
мар 03, 2016 11:56:47 AM org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl processGetTableResults
INFO: HHH000262: Table not found: Greeter
мар 03, 2016 11:56:47 AM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Hello, JPA!
Hello, Hibernate!

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *