JDBC предоставляет вполне достаточный интерфейс для работы с базами данных в Java. Однако этот интерфейс весьма многословен и довольно неудобен и даже Spring JDBC не делает его сильно лучше. По сути дела проблема в том, что реляционные базы данных работают с таблицами и отношениями между ними, в то время как в Java работают с объектами и их иерархиями. Поэтому приходится для каждого объекта или таблицы писать класс отображения одного в другое. Этот процесс называется ORM — object-relational mapping (объектно-реляционное отображение). И, к счастью, существуют готовые ORM решения, которые сами переводят данные из одного вида в другой и обратно.
Hibernate — один из старейших и уж точное наиболее распространённый ORM фреймворк в мире Java. Он может быть использован в качестве одной из JPA реализаций, либо с использованием его собственного API, которое, с одной стороны, сильно напоминает JPA, с другой стороны предоставляет больше возможностей и гибкости, чем строго регламентированный JPA.
Подготовка
В пустой maven проект добавим встраиваемую базу H2 и артефакты Hibernate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.190</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>5.1.0.Final</version>
</dependency>
|
hibernate-core это, собственно, сам Hibernate, API и его реализация, а hibernate-java8 добавляет поддержку новых типов данных (в основном даты/времени), появивишихся в java8.
Entity
Все классы, которые могут быть сохранены в базе данных называются entity(сущность) и на них налагаются определённые требования:
- Наличие публично доступного конструктора без аргументов
- Класс, его методы и сохраняемые поля не должны быть final
- Если объект Entity класса будет передаваться по значению как отдельный объект (detached object), например через удаленный интерфейс (through a remote interface), он так же должен реализовывать Serializable интерфейс.
- Сохраняемые поля должны быть доступны только с использованием методов класса.
Эти требования в точности соответствуют требованиям JPA.
Каждый сохраняемый класс помечается аннотацией @Entity, говорящей Hibernate, что этот класс является сущностью. Помимо того, в каждом классе, помеченном @Entity должно быть поле, имеющее аннотацию @Id, говорящее Hibernate, что это поле может быть использовано как первичный ключ в базе данных и что по значению этого поля Hibernate может отличать один объект от другого. Честно говоря, полей с @Id может быть несколько и механизм первичного ключа несколько сложнее, но я рассмотрю это в отдельной статье. Стоит отметить, что Hibernate использует аннотации из JPA для определения сущностей. Раньше поддерживались и собственные аннотации Hibernate, но с недавних пор они признаны устаревшими.
В примере у нас будет простой 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
|
@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;
}
|
Настройка Hibernate
После создания классов данных и описания их отображения в базу данных, необходимо сконфигурировать Hibernate. Конфигурация может быть выполнена программно, я это обязательно покажу в каком-нибудь из примеров, либо в файле hibernate.cfg.xml, который должен быть доступен в classpath. Строго говоря, имя файла может быть любым, а hibernate.cfg.xml принято по умолчанию.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<mapping class="ru.easyjava.data.hibernate.entity.Greeter"/>
</session-factory>
</hibernate-configuration>
|
В конфигурации определяется метод соединения с базой данных, сама база данных, настройки Hibernate и отображения. Я задал базу H2 и настроил связь с ней. Опция <property name="hibernate.hbm2ddl.auto">update</property> говорит Hibernate, что надо сканировать все классы, имеющие аннотацию @Entity и обновить схему таблиц базы данных сообразно этим классам. Говоря более простым языком — с этой опцией Hibernate сам создаёт таблицы для ваших классов.
Записи <mapping class="ru.easyjava.data.hibernate.entity.Greeter"/> говорят Hibernate, что этот класс следует отображать в базу данных. В отличие от JPA Hibernate требует явного перечисления каждого класса сущности в конфигурации. Кроме того, в Hibernate поддерживаются описание отображений классов и связей между ними на чистом XML, без аннотаций вовсе.
Использование Hibernate
Использование Hibernate состоит, вообщем-то, из двух частей — сохранение объектов в базу и чтение объектов из базы:
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
|
public void setUp() throws Exception {
Greeter greetJpa = new Greeter();
greetJpa.setGreeting("Bye");
greetJpa.setTarget("JPA");
Greeter greetHibernate = new Greeter();
greetHibernate.setGreeting("Hello");
greetHibernate.setTarget("Hibernate");
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure()
.build();
try {
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry);
throw e;
}
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(greetHibernate);
session.save(greetJpa);
session.getTransaction().commit();
session.close();
}
|
Вначале создаётся ServiceRegistry, которое знает о всех настройках и сконфигурированных hibernate persistence units. Из ServiceRegistry создаётся уже SessionFactory. Процесс создания SessionFactory обставлен обработкой исключений, с тем, чтобы очистить ServiceRegistry в случае провала. В случае успешного создания SessionFactory она очистит за собой ServiceRegistry сама. Из SessionFactory уже открываются сессии для работы с объектами в базе данных: Session. SessionFactory/Session могут рассматриваться как DataSource/Connection из JDBC. Затем в Session открывается транзакция, новые объекты записываются в базу данных, транзакция подтверждается и, наконец-то, сессия закрывается.
Попробуем прочитать объекты обратно:
1
2
3
4
5
6
7
8
9
|
Session session = sessionFactory.openSession();
session.beginTransaction();
List<Greeter> greetings = session.createQuery("from Greeter")
.list();
greetings.forEach(g -> System.out.println(String.format("%s, %s!",
g.getGreeting(),
g.getTarget())));
session.getTransaction().commit();
session.close();
|
Опять, открываем сессию, в сессии открываем транзакцию и делаем запрос, используя HQL, язык, подобный SQL, только ориентированный на ORM. Hibernate автоматически создаёт корректный запрос, получает данные из базы данных, создаёт экземпляры объектов и наполняет их данными.
Результат исполнения это подтверждает:
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
|
мая 30, 2016 5:04:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
мая 30, 2016 5:04:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
мая 30, 2016 5:04:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
мая 30, 2016 5:04:20 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
мая 30, 2016 5:04:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
мая 30, 2016 5:04:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [null] at URL [jdbc:h2:mem:test]
мая 30, 2016 5:04:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {}
мая 30, 2016 5:04:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
мая 30, 2016 5:04:20 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
мая 30, 2016 5:04:20 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
мая 30, 2016 5:04:21 PM org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl processGetTableResults
INFO: HHH000262: Table not found: Greeter
мая 30, 2016 5:04:21 PM org.hibernate.tool.schema.extract.internal.InformationExtractorJdbcDatabaseMetaDataImpl processGetTableResults
INFO: HHH000262: Table not found: Greeter
мая 30, 2016 5:04:21 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Hello, Hibernate!
Bye, JPA!
|
Код примера доступен на github.