Спецификация JPA в Spring, доступная в пакете org.springframework.orm.jpa
, содержит комплексные средства поддержки Java Persistence API аналогично интеграции с Hibernate, при этом учитывая базовую реализацию для предоставления дополнительных функций.
Три варианта настройки JPA в окружении Spring
Поддержка JPA в Spring предполагает три способа настройки EntityManagerFactory
из JPA, которая используется приложением для получения диспетчера сущностей.
-
Использование
LocalEntityManagerFactoryBean
-
Получение
EntityManagerFactory
изJNDI
-
Использование
LocalContainerEntityManagerFactoryBean
Использование LocalEntityManagerFactoryBean
Этот вариант можно использовать только в простых окружениях развертывания, таких как автономные приложения и интеграционные тесты.
LocalEntityManagerFactoryBean
создает фабрику EntityManagerFactory
, подходящую для простых окружений развертывания, где приложение использует только JPA для доступа к данным. Бин-фабрика использует механизм автоматического определения PersistenceProvider
из JPA (в соответствии с начальной загрузкой JPA в Java SE) и в большинстве случаев требует задать только имя единицы сохраняемости. В следующем примере на XML сконфигурирован такой бин:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
Эта форма развертывания JPA является самой простой и самой ограниченной. Ссылаться на существующее определение бина DataSource
из JDBC нельзя, а поддержка глобальных транзакций отсутствует. Кроме того, связывание (преобразование байт-кода) постоянных классов зависит от конкретного поставщика, и часто требуется задать конкретного агента JVM при запуске. Этой опции будет достаточно только для автономных приложений и тестовых окружений, для которых разработана спецификация JPA.
Получение EntityManagerFactory из JNDI
Этот вариант можно использовать при развертывании на сервере Java EE. Проверьте документацию для вашего сервера, чтобы узнать, как развернуть кастомный поставщик JPA на вашем сервере, что позволит использовать поставщик, отличный от поставщика сервера по умолчанию.
Получение EntityManagerFactory
из JNDI (например, в окружении Java EE) – это вопрос изменения конфигурации XML, как показано в следующем примере:
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
Это действие предполагает стандартную начальную загрузку Java EE. Сервер Java EE автоматически обнаруживает единицы сохраняемости (по сути, файлы META-INF/persistence.xml
в jar-файлах приложений) и записи persistence-unit-ref
в дескрипторе развертывания Java EE (например, web.xml
) и определяет местоположение контекста именования окружения для этих единиц сохраняемости.
В таком сценарии развертывание единиц сохраняемости, включая связывание (преобразование байт-кода) классов постоянного хранения, целиком возлагается на сервер Java EE. DataSource
из JDBC определяется через местоположение JNDI в файле META-INF/persistence.xml
. Транзакции EntityManager
являются неотъемлемой частью JTA-подсистемы сервера. Spring просто использует полученную EntityManagerFactory
, передавая ее объектам приложения через внедрение зависимостей и управляя транзакциями для единицы сохраняемости (обычно через JtaTransactionManager
).
Если в одном приложении используется несколько единиц сохраняемости, имена бинов таких единиц сохраняемости, получаемых из JNDI, должны совпадать с именами единиц сохраняемости, которые приложение использует для ссылки на них (например, в аннотациях @PersistenceUnit
и @PersistenceContext
).
Использование LocalContainerEntityManagerFactoryBean
Этот вариант можно использовать для полноценного использования средств JPA в окружении приложений на базе Spring. Это подразумевает веб-контейнеры, такие как Tomcat, автономные приложения и интеграционные тесты со сложными требованиями к постоянному хранению.
LocalSessionFactoryBean
из Hibernate вместо обычного LocalContainerEntityManagerFactoryBean
из JPA, что позволит ему взаимодействовать с кодом доступа JPA, а также с нативным кодом доступа Hibernate. Подробности см. в разделе "Нативная настройка Hibernate для взаимодействия с JPA".
LocalContainerEntityManagerFactoryBean
дает полный контроль над конфигурацией EntityManagerFactory
и подходит для окружений, в которых требуется тонкая настройка. LocalContainerEntityManagerFactoryBean
создает экземпляр PersistenceUnitInfo
на основе файла persistence.xml
, предоставленной стратегии dataSourceLookup
и заданного loadTimeWeaver
. Таким образом, можно работать со кастомными источниками данных вне JNDI и управлять процессом привязывания. В следующем примере показано типичное определение бина для LocalContainerEntityManagerFactoryBean
:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
В следующем примере показан типичный файл persistence.xml
:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
<exclude-unlisted-classes/>
указывает, что сканирование аннотированных классов сущностей не должно осуществляться. Явное значение "true" (<exclude-unlisted-classes>true</exclude-unlisted-classes/>
) также означает отсутствие проверки. <exclude-unlisted-classes>false</exclude-unlisted-classes/>
вызывает проверку. Однако рекомендуем опустить элемент exclude-unlisted-classes
, если нужно осуществлять сканирование классов сущностей.Использование LocalContainerEntityManagerFactoryBean
является наиболее эффективным вариантом настройки JPA, обеспечивающим гибкую локальную конфигурацию в приложении. Он поддерживает ссылки на существующий DataSource
из JDBC, поддерживает локальные и глобальные транзакции и так далее. Однако он также предъявляет требования к окружению выполнения, такие как наличие загрузчика классов с возможностью привязывания, если поставщику сохраняемости требуется преобразование байт-кода.
Этот вариант может конфликтовать со встроенными средствами JPA сервера Java EE. В полнофункциональном окружении Java EE рассмотрите возможность получения EntityManagerFactory
из JNDI. Как вариант, задайте кастомное persistenceXmlLocation
в определении LocalContainerEntityManagerFactoryBean
(например, META-INF/my-persistence.xml) и включите только дескриптор с этим именем в jar-файлы вашего приложения. Поскольку сервер Java EE ищет только стандартные файлы META-INF/persistence.xml
, он игнорирует такие кастомные единицы сохраняемости, что, следовательно, позволяет избежать конфликтов с настройкой JPA на основе Spring. (Это относится, например, к Resin 3.1).
Интерфейс LoadTimeWeaver
– это класс, содержащийся в Spring, который позволяет подключать экземпляры ClassTransformer
из JPA определенным образом, в зависимости от того, является ли окружение веб-контейнером или сервером приложений. Подключение ClassTransformers
через agent обычно неэффективно. Агенты работают по всей виртуальной машине и проверяют каждый загружаемый класс, что обычно нежелательно в окружении производственных серверов.
Spring содержит несколько реализаций LoadTimeWeaver
для различных окружений, что позволяет применять экземпляры ClassTransformer
только для каждого загрузчика классов, а не для каждой виртуальной машины.
Более подробную информацию о реализаций LoadTimeWeaver
и их настройке, как типизированной, так и адаптированной к различным платформам (таким как Tomcat, JBoss и WebSphere), см. в разделе, посвященному конфигурированию Spring в главе по АОП.
Как описано в разделе, посвященному конфигурации Spring, сконфигурировать LoadTimeWeaver
для всего контекста можно с помощью аннотации @EnableLoadTimeWeaving
или XML-элемента context:load-time-weaver
. Такой глобальный инструмент привязывания автоматически подхватывается всеми экземплярами LocalContainerEntityManagerFactoryBean
из JPA. В следующем примере показан предпочтительный способ настройки инструмента привязывания во время загрузки, обеспечивающий автоматическое обнаружение платформы (например, загрузчик классов Tomcat с поддержкой привязывания или агент JVM из Spring) и автоматическое распространение средства привязывания на все бины, поддерживающие инструмент привязывания:
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
Однако при необходимости можно вручную задать выделенный инструмент привязывания через свойство loadTimeWeaver
, как показано в следующем примере:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
Независимо от того, как настроено привязывание во время загрузки (LTW), с помощью этой техники приложения JPA, полагающиеся на инструментацию, могут выполняться на целевой платформе (например, Tomcat) без необходимости использования агента. Это особенно важно, если приложения хоста используют различные реализации JPA, поскольку преобразователи JPA применяются только на уровне класса-загрузчика и, таким образом, изолированы друг от друга.
Работа с несколькими единицами сохраняемости
Для приложений, которые используют несколько местоположений единиц сохраняемости (хранящихся, например, в различных JAR-файлах в classpath), Spring предлагает PersistenceUnitManager
, который действует как центральный репозиторий и позволяет избежать процесса обнаружения единиц сохраняемости, что может быть затратным. Реализация по умолчанию позволяет задавать несколько местоположений. Эти местоположения парсится и впоследствии извлекаются через имя единицы сохраняемости. (По умолчанию в classpath выполняется поиск файлов META-INF/persistence.xml
). В следующем примере сконфигурировано несколько местоположений:
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/package/**/custom-persistence.xml</value>
<value>classpath*:META-INF/persistence.xml</value>
</list>
</property>
<property name="dataSources">
<map>
<entry key="localDataSource" value-ref="local-db"/>
<entry key="remoteDataSource" value-ref="remote-db"/>
</map>
</property>
<!-- если источник данных не указан, используйте этот -->
<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
<property name="persistenceUnitName" value="myCustomUnit"/>
</bean>
Реализация по умолчанию позволяет настраивать экземпляры PersistenceUnitInfo
(до их передачи поставщику JPA) либо декларативно (через его свойства, которые влияют на все размещаемые (hosted) единицы), либо программно (через PersistenceUnitPostProcessor
, который позволяет производить выборку единиц сохраняемости). Если PersistenceUnitManager
не задан, то он будет создан и использован внутри LocalContainerEntityManagerFactoryBean
.
Фоновая начальная загрузка
LocalContainerEntityManagerFactoryBean
поддерживает функцию фоновой начальной загрузки через свойство bootstrapExecutor
, как показано в следующем примере:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
Фактическая начальная загрузка поставщика JPA передается указанному исполнителю, а затем, параллельно, потоку начальной загрузки приложения. Открытый прокси EntityManagerFactory
может быть внедрен в другие компоненты приложения и даже способен отвечать на проверку конфигурации EntityManagerFactoryInfo
. Однако, если к реальному поставщику JPA обращаются другие компоненты (например, вызывая createEntityManager
), эти вызовы блокируются до завершения фоновой начальной загрузки. В частности, если вы используете Spring Data JPA, не забудьте настроить отложенную начальную загрузку и для его репозиториев.
Реализация DAO на основе JPA: EntityManagerFactory
и EntityManager
EntityManagerFactory
являются потокобезопасными, экземпляры EntityManager
таковыми не являются. Внедренный EntityManager
из JPA ведет себя как EntityManager
, полученный из окружения JNDI сервера приложений, как определено спецификацией JPA. Он делегирует все вызовы текущему транзакционному EntityManager
, если таковой имеется. В противном случае он возвращается к вновь созданному EntityManager
для каждой операции, фактически делая его использование потокобезопасным.Можно писать код на обычном JPA без каких-либо зависимостей Spring, используя внедренную EntityManagerFactory
или EntityManager
. Spring может распознавать аннотации @PersistenceUnit
и @PersistenceContext
как на уровне поля, так и на уровне метода, если активирован PersistenceAnnotationBeanPostProcessor
. В следующем примере показана обычная реализация DAO на JPA, использующая аннотацию @PersistenceUnit
:
public class ProductDaoImpl implements ProductDao {
private EntityManagerFactory emf;
@PersistenceUnit
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
public Collection loadProductsByCategory(String category) {
EntityManager em = this.emf.createEntityManager();
try {
Query query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.getResultList();
}
finally {
if (em != null) {
em.close();
}
}
}
}
class ProductDaoImpl : ProductDao {
private lateinit var emf: EntityManagerFactory
@PersistenceUnit
fun setEntityManagerFactory(emf: EntityManagerFactory) {
this.emf = emf
}
fun loadProductsByCategory(category: String): Collection<*> {
val em = this.emf.createEntityManager()
val query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.resultList;
}
}
Предшествующий DAO не зависит от Spring и по-прежнему хорошо вписывается в контекст приложения Spring. Более того, DAO использует преимущества аннотаций, чтобы запрашивать внедрение стандартной EntityManagerFactory
, как показано в следующем примере определения бина:
<beans>
<!-- постпроцессор бинов для JPA-аннотаций -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
В качестве альтернативы явному определению PersistenceAnnotationBeanPostProcessor
, рассмотрите возможность использования XML-элемента context:annotation-config
из Spring в конфигурации контекста приложения. Таким образом, все стандартные постпроцессоры Spring для конфигурации на основе аннотаций, включая CommonAnnotationBeanPostProcessor
и так далее, будут автоматически регистрироваться.
Рассмотрим следующий пример:
<beans>
<!-- постпроцессоры для всех стандартных аннотаций конфигурации -->
<context:annotation-config/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
Основная проблема с таким DAO заключается в том, что он всегда создает новый EntityManager
через фабрику. Можно избежать этого, запросив транзакционный EntityManager
(также называемый "общим EntityManager", поскольку он является общим, потокобезопасным прокси для реального транзакционного EntityManager), который будет внедрен вместо фабрики. В следующем примере показано, как это сделать:
public class ProductDaoImpl implements ProductDao {
@PersistenceContext
private EntityManager em;
public Collection loadProductsByCategory(String category) {
Query query = em.createQuery("from Product as p where p.category = :category");
query.setParameter("category", category);
return query.getResultList();
}
}
class ProductDaoImpl : ProductDao {
@PersistenceContext
private lateinit var em: EntityManager
fun loadProductsByCategory(category: String): Collection<*> {
val query = em.createQuery("from Product as p where p.category = :category")
query.setParameter("category", category)
return query.resultList
}
}
Аннотация @PersistenceContext
имеет необязательный атрибут type
, который по умолчанию имеет значение PersistenceContextType.TRANSACTION
. Можно использовать это значение по умолчанию для получения общего прокси EntityManager
. Альтернатива, а именно PersistenceContextType.EXTENDED
, является совершенно другим делом. В результате получается так называемый расширенный EntityManager
, который не является потокобезопасным и, следовательно, не должен использоваться в компоненте, к которому открыт одновременный доступом, такому как, например, управляемый Spring бин-одиночка. Экземпляры расширенного EntityManager
предназначены для использования исключительно в компонентах, сохраняющих состояние, которые, например, содержаться в сессии, при этом жизненный цикл EntityManager
не привязан к текущей транзакции, а полностью зависит от приложения.
Внедряемый EntityManager
управляется Spring (учитывает текущую транзакцию). Даже если новая реализация DAO использует внедрение EntityManager
на уровне методов вместо EntityManagerFactory
, никаких изменений в XML контекста приложения не требуется благодаря использованию аннотаций.
Главное преимущество этого стиля DAO заключается в том, что он зависит только от Java Persistence API. Импорт какого-либо класса Spring не требуется. Более того, поскольку аннотации JPA распознаваемые, внедрение применяется контейнером Spring автоматически. Это привлекательно с точки зрения неагрессивности и может казаться более естественным для разработчиков JPA.
Транзакции JPA, управляемые Spring
Рекомендуемая стратегия для JPA – локальные транзакции с помощью встроенной поддержки транзакций JPA. JpaTransactionManager
из Spring предлагает множество средств, известных по локальным транзакциям из JDBC (такие как специфичные для транзакций уровни изоляции и оптимизация режима "только для чтения" на уровне ресурсов), в отношении любого обычного пула соединений JDBC (без требований XA).
JPA через Spring также позволяет сконфигурированному JpaTransactionManager
открывать транзакцию JPA для кода доступа JDBC, который обращается к тому же DataSource
, при условии, что зарегистрированный JpaDialect
поддерживает получение базового Connection
из JDBC. Spring содержит диалекты для реализаций EclipseLink и Hibernate через JPA.
HibernateTransactionManager
из Spring способен взаимодействовать с кодом доступа JPA, адаптируясь к некоторым особенностям Hibernate и обеспечивая взаимодействие с JDBC. Это имеет особый смысл в сочетании с настройкой LocalSessionFactoryBean
. Подробнее см. в разделе "Нативная настройка Hibernate для взаимодействия с JPA".
Основные сведения о JpaDialect
и JpaVendorAdapter
В качестве расширенной функции JpaTransactionManager
и подклассы AbstractEntityManagerFactoryBean
позволяют передавать пользовательский JpaDialect
в свойство бина jpaDialect
. Реализация JpaDialect
может включать следующие расширенные возможности, поддерживаемые Spring, обычно в зависимости от производителя:
-
Применение специфической семантики транзакций (например, кастомного уровня изоляции или времени ожидания транзакции)
-
Получение транзакционного
Connection
JDBC (чтобы открыть его на DAO-объектам на основе JDBC) -
Расширенное преобразование
PersistenceExceptions
вDataAccessExceptions
из Spring
Это особенно полезно в случае специальной семантики транзакций и для расширенного преобразования исключений. Реализация по умолчанию(DefaultJpaDialect
) не предоставляет никаких специальных возможностей, и если требуются перечисленные ранее функции, необходимо задавать соответствующий диалект.
JpaVendorAdapter
, являясь еще более широким средством адаптации поставщика в первую очередь для полнофункциональной настройки LocalContainerEntityManagerFactoryBean
из Spring, объединяет возможности JpaDialect
с другими специфическими для поставщика вариантами по умолчанию. Задание HibernateJpaVendorAdapter
или EclipseLinkJpaVendorAdapter
является наиболее удобным способом автоматической настройки EntityManagerFactory
для Hibernate или EclipseLink соответственно. Обратите внимание, что эти адаптеры поставщиков в первую очередь предназначены для использования при управлении транзакциями на основе Spring (то есть для использования с JpaTransactionManager
).См. javadoc по JpaDialect
и JpaVendorAdapter
для получения более подробной информации об их работе и о том, как они используются в рамках средств поддержки Spring для JPA.
Настройка JPA с помощью управления транзакциями через JTA
Как альтернатива JpaTransactionManager
, Spring также позволяет координировать транзакции с несколькими ресурсами через JTA либо в окружении Java EE, либо с помощью отдельного координатора транзакций, такого как Atomikos. Помимо выбора JtaTransactionManager
вместо JpaTransactionManager
, необходимо предпринять еще несколько шагов:
-
Основные пулы соединений JDBC должны быть совместимы с XA и интегрированы с вашим координатором транзакций. Обычно в окружении Java EE это делается просто, путем открытия
DataSource
другого типа через JNDI. Подробности см. в документации по вашему серверу приложений. Аналогичным образом, автономный координатор транзакций обычно поставляется со специализированными вариантамиDataSource
, интегрированными с XA. Опять же, ознакомьтесь с документацией. -
Настройка
EntityManagerFactory
из JPA должна быть сконфигурирована для JTA. Все зависит от производителя, но обычно это делается через специальные свойства, которые должны быть заданы какjpaProperties
дляLocalContainerEntityManagerFactoryBean
. В случае Hibernate эти свойства даже зависят от версии. Подробности см. в документации по Hibernate. -
HibernateJpaVendorAdapter
из Spring применяет определенные Spring-ориентированные значения по умолчанию, такие как режим освобождения соединения,on-close
, который совпадает с собственным значению по умолчанию из Hibernate в Hibernate 5.0, но уже не совпадает с ним в Hibernate 5.1+. Для настройки JTA убедитесь, что тип транзакции вашей единицы сохраняемости объявлен как "JTA". Как вариант, установите свойствоhibernate.connection.handling_mode
из Hibernate 5.2 вDELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
, чтобы восстановить собственное значение Hibernate по умолчанию. Соответствующие заметки см. в разделе "Ложные предупреждения сервера приложений при использовании Hibernate". -
В качестве альтернативы, рассмотрите возможность получения
EntityManagerFactory
с самого сервера приложений (то есть через JNDI-поиск вместо локально объявленногоLocalContainerEntityManagerFactoryBean
). Предоставляемая серверомEntityManagerFactory
может требовать специальных определений в конфигурации вашего сервера (что делает развертывание менее платформонезависимым), но она настроена для JTA-окружения сервера.
Нативная настройка Hibernate и нативные транзакции Hibernate для взаимодействия с JPA
Нативная настройка LocalSessionFactoryBean
в сочетании с HibernateTransactionManager
позволяет взаимодействовать с аннотациями @PersistenceContext
и другим кодом доступа JPA. SessionFactory
из Hibernate теперь нативно реализует интерфейс EntityManagerFactory
из JPA, а дескриптор Session
из Hibernate нативно является EntityManager
для JPA. Средства поддержки JPA в Spring автоматически определяют собственные сессии Hibernate.
Поэтому такая нативная настройка Hibernate может служить заменой стандартной комбинации LocalContainerEntityManagerFactoryBean
и JpaTransactionManager
из JPA во многих сценариях, позволяя взаимодействовать с SessionFactory.getCurrentSession()
(а также HibernateTemplate
) наряду с аннотацией @PersistenceContext EntityManager
в рамках одной локальной транзакции. Такая настройка также обеспечивает более тесную интеграцию с Hibernate и большую гибкость конфигурации, поскольку она не ограничена контрактами загрузочного шаблона JPA.
В таком случае конфигурация HibernateJpaVendorAdapter
не нужно, поскольку встроенная в Spring настройка Hibernate предоставляет еще больше функциональных возможностей (например, кастомную настройку Hibernate Integrator, интеграцию контейнера бинов Hibernate 5.3 и более строгую оптимизация для транзакций в режиме "только для чтения"). И последнее, но не по важности: вы также можете выразить нативную настройку Hibernate через LocalSessionFactoryBuilder
, с легкостью выполняя итерацию конфигурацию на основе аннотации @Bean
(без использования FactoryBean
).
LocalSessionFactoryBean
и LocalSessionFactoryBuilder
поддерживают фоновую начальную загрузку, как и LocalContainerEntityManagerFactoryBean
из JPA. Вводную информацию см. в разделе "Фоновая начальная загрузка".
Для LocalSessionFactoryBean
это доступно через свойство bootstrapExecutor
. В программном LocalSessionFactoryBuilder
перегруженный метод buildSessionFactory
принимает аргумент исполнителя начальной загрузки.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ