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.