JDBC предоставляет вполне достаточный интерфейс для работы с базами данных в Java. Однако этот интерфейс весьма многословен и довольно неудобен и даже Spring JDBC не делает его сильно лучше. По сути дела проблема в том, что реляционные базы данных работают с таблицами и отношениями между ними, в то время как в Java работают с объектами и их иерархиями. Поэтому приходится для каждого объекта или таблицы писать класс отображения одного в другое. Этот процесс называется ORM — object-relational mapping (объектно-реляционное отображение). И, к счастью, существуют готовые ORM решения, которые сами переводят данные из одного вида в другой и обратно.
Более того, таких решений настолько много, что в Java даже появился стандартный интерфейс, который направлен на стандартизацию ORM продуктов. Этот интерфейс называется JPA — Java persistence API и описывает требования к объектам, для сохранения их в базах данных, интерфейсы для сохранения объектов и интерфейсы для получения объектов из БД.
Сам JPA является лишь описательным стандартом и пачкой аннотаций, поэтому у него есть несколько реализаций. Одна из самых популярных и, в то же время, одна из не самых стандартных реализаций, называется Hibernate.
Попробуем использовать их вместе.
Подготовка
Как всегда, начнём с пустого maven проекта, в который добавим Hibernate реализацию JPA и встраиваемую базу H2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<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 класс, с тремя полями:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* 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.
1
2
3
4
5
6
7
8
9
10
11
12
|
<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 состоит, вообщем-то, из двух частей — сохранение объектов в базу и чтение объектов из базы:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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!
Попробуем прочитать объекты обратно:
1
2
3
4
5
6
7
|
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 автоматически создаст корректный запрос, получит данные из базы данных, создаст экземпляры объектов и наполнит их данными.
Результат исполнения это подтверждает:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
мар 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!
|
Код примера доступен на github.