Прежде чем мы углубимся в причины использования JPA, позвольте мне быстро объяснить, что это такое. Java Persistence API (JPA) — это спецификация объектно-реляционного отображения в Java. Что касается большинства стандартов в рамках процесса сообщества Java , то он реализуется различными структурами. Самый популярный — Hibernate .
Все реализации JPA поддерживают функции, определенные в спецификации, и часто расширяют их с помощью пользовательских функций. Это обеспечивает 2 основных преимущества:
- Вы можете быстро переключать реализацию JPA, если вы не используете какие-либо проприетарные функции.
- Различные реализации могут добавлять дополнительные функции для инноваций быстрее, чем стандартные. Некоторые из них могут стать частью спецификации на более позднем этапе.
ОК, достаточно теории. Давайте начнем с краткого введения в JPA, а затем рассмотрим некоторые причины его использования.
Начало работы с JPA
Конечно, невозможно объяснить JPA во всей его глубине в одном коротком разделе. Но я хочу показать вам базовый вариант использования, чтобы познакомить вас с общими понятиями.
Начнем с файла persistence.xml. Его структура определяется стандартом JPA и предоставляет конфигурацию поставщику постоянства, в первую очередь, драйверу базы данных и информации о соединении. Вы можете увидеть простой пример конфигурации в следующем фрагменте кода.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="my-persistence-unit"> <description>My Persistence Unit</description> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" /> <property name="hibernate.generate_statistics" value="true" /> <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/test" /> <property name="javax.persistence.jdbc.user" value="postgres" /> <property name="javax.persistence.jdbc.password" value="postgres" /> </properties> </persistence-unit> </persistence>
После того, как вы настроили свой поставщик сохраняемости, вы можете определить свою первую сущность. В следующем фрагменте кода показан пример простого сопоставления сущностей.
@Entity public class Author { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id", updatable = false, nullable = false) private Long id; @Version @Column(name = "version") private int version; @Column private String firstName; @Column private String lastName; @ManyToMany(mappedBy="authors") private Set<Book> books = new HashSet<Book>(); // constructors, getters/setters, // and everything else is as usual }
Аннотация @Entity
определяет класс Author
как сущность. Он сопоставляется с таблицей с тем же именем, в данном случае с таблицей author
.
Атрибут id
является первичным ключом сущности и таблицы базы данных. Реализация JPA автоматически генерирует значение первичного ключа и использует атрибут версии для оптимистической блокировки, чтобы избежать одновременного обновления одной и той же записи базы данных.
Аннотация @Column
указывает, что этот атрибут сопоставлен со столбцом базы данных. Подобно аннотации @Entity
, в качестве имени столбца по умолчанию используется имя атрибута.
Аннотация @ManyToMany
определяет отношение к другому объекту. В этом примере он определяет отношение к сущности Book
которая отображается в другую таблицу базы данных.
Как видите, вам нужно всего лишь добавить несколько аннотаций для сопоставления таблицы базы данных и использовать другие функции, такие как оптимистическая блокировка и генерация первичного ключа.
5 причин
1. Производительность разработчика
Продуктивность разработчика, вероятно, является наиболее часто упоминаемым преимуществом JPA и любых его реализаций. Основная причина этого заключается в том, что вы должны определить соответствие между таблицами базы данных и моделью вашего домена только один раз, чтобы использовать его для всех операций записи и большинства операций чтения. Вдобавок ко всему, вы получаете множество дополнительных функций, которые в противном случае вам необходимо было бы реализовать самостоятельно, таких как генерация первичного ключа, управление параллелизмом и различные оптимизации производительности.
Но это только одна из причин, почему JPA популярен благодаря своей продуктивности для разработчиков. Он также предоставляет простой, но очень эффективный API для реализации основных операций CRUD . Вы можете увидеть пример для этого в следующих 2 фрагментах кода.
В первом я покажу вам, как сохранить новую сущность Author
в базе данных.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Author a = new Author(); a.setFirstName("John"); a.setLastName("Doe"); em.persist(a); em.getTransaction().commit(); em.close();
Как видите, вам не нужно ничего делать. Первые и последние 2 строки в этом примере представляют собой стандартный код, который необходимо запускать только один раз для каждой транзакции, чтобы получить EntityManager
и обработать транзакцию. Если вы используете JPA в контейнере Java EE или приложении Spring, вы можете игнорировать эти строки, потому что ваша инфраструктура позаботится об этом.
Основная работа выполняется в строках 4-7. Я создаю новый объект сущности Author
и вызываю методы установки, чтобы указать имя и фамилию нового автора. Затем я вызываю метод EntityManager
интерфейсе EntityManager
, который говорит реализации JPA сгенерировать оператор SQL INSERT
и отправить его в базу данных.
Код следующего примера выглядит аналогично. На этот раз я хочу обновить существующего автора. Как и в предыдущем примере, первые и последние 2 строки фрагмента представляют собой стандартный код для получения EntityManager и обработки транзакции. Интересная часть этих фрагментов — строки 4 и 5. В строке 4 я использую метод find объекта EntityManager
чтобы получить сущность по ее первичному ключу. Как видите, мне не нужно писать SQL для этого простого запроса. И то же самое для обновления фамилии. Вам просто нужно вызвать методы установки атрибутов, которые вы хотите изменить, и ваша реализация JPA создаст для этого требуемый оператор SQL UPDATE
.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Author a = em.find(Author.class, 1L); a.setLastName("new last name"); em.getTransaction().commit(); em.close();
Как вы уже видели, JPA предоставляет простой в использовании API для реализации общих случаев использования CRUD без написания SQL. Это значительно ускоряет реализацию общих случаев использования, но также дает еще одно преимущество: ваши операторы SQL не распространяются по всему коду. Это означает, что вы можете легко переименовать таблицы или столбцы базы данных. Единственное, что вам нужно адаптировать — это аннотации к вашей сущности.
2. База данных не зависит
Если вы попытаетесь использовать один и тот же код с разными базами данных, вы быстро столкнетесь с проблемами, вызванными разными диалектами SQL. SQL является стандартным языком для взаимодействия с базой данных, но каждая база данных использует немного разные диалекты. Это становится огромной проблемой, если ваши заявления должны выполняться в разных базах данных.
Но нет, если вы используете JPA. Он обеспечивает независимую от базы данных абстракцию поверх SQL. Пока вы не используете собственные запросы , вам не нужно беспокоиться о переносимости базы данных. Ваша реализация JPA адаптирует сгенерированные операторы SQL в каждом вызове API или запросе JPQL к определенному диалекту базы данных и обрабатывает различные типы данных, специфичные для базы данных.
3. Тип и обработка параметров
Поскольку типы данных JDBC и Java не выстраиваются идеально, вам нужно найти правильные комбинации и обязательно предоставить их в качестве параметров запроса.
Если вы никогда не делали этого сами, это может звучать легко. Но если вам пришлось сделать это хотя бы один раз, вы знаете, что легко ошибиться. Хуже того, это отвлекает от реализации бизнес-логики, а также является причиной уязвимостей SQL-инъекций, одной из самых распространенных проблем безопасности в веб-приложениях.
Лучший способ избежать этих проблем и сосредоточиться на бизнес-логике — это использовать среду или спецификацию, такую как JPA, которая автоматически обрабатывает эти вещи.
Как вы видели в начале этого поста, вам не нужно определять какие-либо типы данных SQL, когда вы определяете свое сопоставление сущностей. Ваша реализация JPA скрывает эти преобразования от вашего кода и использует отображение по умолчанию.
Обработка параметров для ваших запросов JPQL использует аналогичный подход. Вы просто устанавливаете параметр в интерфейсе запроса, и ваша реализация JPA обрабатывает его на основе метаданных сущности. Вы можете увидеть пример этого в следующем фрагменте кода.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); TypedQuery<Author> q = em.createQuery("SELECT a FROM Author a WHERE id = :id", Author.class); q.setParameter("id", 1L); Author a = q.getSingleResult(); em.getTransaction().commit(); em.close();
4. Избегайте ненужных запросов
Оптимизация обратной записи — это одна из нескольких оптимизаций производительности, которые вы получаете с помощью JPA. Основная идея заключается в том, чтобы как можно дольше задерживать все операции записи, чтобы несколько операторов обновления можно было объединить в одну. Поэтому ваша реализация JPA хранит все сущности, которые использовались в одной транзакции, в кеше первого уровня.
Из-за этого в следующем фрагменте кода требуется только один оператор SQL UPDATE
, даже если сущность изменяется в разных методах приложения. Это значительно уменьшает количество операторов SQL, особенно в сложных модульных приложениях.
public void updateAuthor() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Author a = em.find(Author.class, 1L); a.setFirstName("New first name"); // execute operations that need to happen // during the transaction and between // updating first and last name a.setLastName("new last name"); em.getTransaction().commit(); em.close(); }
5. Кеширование
Кэширование — это еще одна функция настройки производительности, которую вы получаете почти бесплатно, если используете JPA. Я уже объяснил, как кэш 1-го уровня используется для оптимизации обратной записи. Но это не единственный кеш и не единственный способ извлечь из этого выгоду. JPA определяет 2 разных типа кэшей:
- Кэш первого уровня , который содержит все объекты, используемые в транзакции.
- Кэш второго уровня, в котором хранятся объекты независимо от сеанса.
Оба кэша помогают сократить количество выполняемых операторов SQL, сохраняя объекты в локальной памяти. Это может значительно повысить производительность, если вам приходится читать одну и ту же сущность несколько раз в рамках одной или нескольких транзакций. Лучше всего то, что вам нужно почти ничего не делать, чтобы получить эти преимущества.
Кэш первого уровня всегда активирован, и вам не нужно ничего делать, чтобы его использовать. Ваша реализация JPA использует его для улучшения производительности вашего приложения.
Кэш второго уровня должен быть активирован, и вы можете сделать это для всех или только для определенных объектов. Как только вы активируете кеш, ваша реализация JPA будет использовать его прозрачно. Поэтому вам не нужно учитывать кэширование при реализации своей бизнес-логики, и вы можете активировать или деактивировать ее в любой момент времени без какого-либо рефакторинга. Я всегда рекомендую активировать кэш второго уровня для объектов, которые вы читаете очень часто, не меняя их. Кэширование этих объектов обеспечивает наибольшее повышение производительности и требует лишь небольших накладных расходов на управление кешем.
Активация кэша второго уровня требует двух простых шагов:
- Сконфигурируйте кеш в вашем файле
persistence.xml
. - Отметить объект как кешируемый.
Давайте сначала посмотрим на файл persistence.xml
. Единственное, что вам нужно сделать для настройки кэша второго уровня — это настроить параметр shared-cache-mode. В этом примере я использую режим ENABLE_SELECTIVE
, который позволяет мне включать кэширование для определенных объектов.
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="my-persistence-unit"> … <!-- enable selective 2nd level cache --> <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> … </persistence-unit> </persistence>
В следующем фрагменте кода я добавляю аннотацию @Cacheable
к сущности Author
чтобы активировать для нее кэш второго уровня:
@Entity @Cacheable public class Author { … }
Это все, что вам нужно сделать, чтобы активировать кэш второго уровня для данной сущности и избежать ненужных запросов к базе данных. Как вы видели, базовая конфигурация в JPA требует только одного параметра конфигурации и одной аннотации. Но сам кэш не определяется спецификацией JPA, и вам может потребоваться предоставить для него больше параметров конфигурации.
Резюме
В этом посте я представил лишь небольшую часть функций и преимуществ, предоставляемых JPA. Но, как вы видели, эти функции охватывают широкий спектр тем, таких как производительность труда разработчиков, переносимость баз данных и оптимизация производительности . Поэтому JPA и Hibernate, как его самая популярная реализация, являются наиболее распространенным выбором для реализации доступа к базе данных.
У вас есть вопросы? Не стесняйтесь размещать их в комментариях или связаться со мной в твиттере .
Примечание редактора . Если вам понравилось это высокоуровневое введение в Hibernate, следите за обновлениями, потому что ожидается публикация новых статей других экспертов в этой области. Кстати, вы знали, что для каждого тега есть RSS-канал? Вот один для Java в целом, а затем один для Hibernate в частности .