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!