Динамическое добавление классов сущностей во время выполнения
У меня есть это требование, чтобы добавить классы сущностей в модуль персистентности во время выполнения, а не указывать их все в файле persistence.xml. Может ли кто-нибудь помочь мне с тем же?
Я знаю, что Hibernate имеет свой собственный механизм, который делает то же самое, используя:
AnnotationConfiguration.addAnnotatedClass(Class)
и т. д. - Вы также можете добавить hibernate config (*.hbm.xml
) файлы программно.
Требование заключается в том, что без перезапуска сервера приложений я смогу динамически добавлять классы сущностей / их файлы конфигурации (сопоставления) в модуль персистентности.
Но решение для программного добавления классов сущности / файлов конфигурации во время выполнения к блоку персистентности не должно быть специфичным для реализации JPA.
2 ответа
JPA пока не предлагает эту функцию. Вот три варианта, которые вы можете проверить:
Программно загружать классы сущностей с помощью JPA 2.0?
Этот вопрос на ТА похож на ваш. Ответ сообщает, что это возможно с весны.JPA 2.0: автоматическое добавление классов сущностей в PersistenceUnit * из разных jar*
Хорошие указатели приведены здесь.И последнее, но не менее важное, простое решение:
1. Создайтеpersistence.xml
на лету (простое создание файла XML) с новым модулем персистентности.
2. Добавить файл персистентности в classpath динамически (URLCLassLoader
)
3. СпроситеPersistenceProvider
загрузить новый блок персистентности (createEntityManagerFactory
)
РЕДАКТИРОВАТЬ:
Если провайдером JPA является Hibernate, начиная с Hibernate 4.0, можно напрямую передавать сущности этому провайдеру JPA, не объявляя их в persistence.xml
файл. Hibernate будет обрабатывать объекты на лету.
РЕДАКТИРОВАТЬ:
Вот пример конфигурации JPA 2.1 + Hibernate 4.3.7. Окончательный вариант без объявления каких-либо объектов:
META-INF / persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="my-persistence-unit"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!-- Database Properties -->
<property name="javax.persistence.jdbc.url"
value="jdbc:postgresql://localhost:5432/my-database" />
<property name="javax.persistence.jdbc.user" value="login" />
<property name="javax.persistence.jdbc.password" value="password" />
<!-- Hibernate Properties -->
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.default_schema" value="public" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.format_sql" value="true" />
<!-- Connection Pool -->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="500" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="2000" />
</properties>
</persistence-unit>
</persistence>
Рекомендации
Я опаздываю на вечеринку, но думаю, что это избавит некоторых людей от головной боли. Я реализовал сканирование пути к классам для чистого JPA (не требуется пружина и т. Д.), Которое также интегрируется, например, с guice-persist, если это необходимо.
Вот что тебе нужно сделать.
Сначала измените файл persistence.xml и добавьте свою собственную реализацию, например:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="my.persistence.unit" transaction-type="RESOURCE_LOCAL">
<provider>my.custom.package.HibernateDynamicPersistenceProvider</provider>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.max_fetch_depth" value="30" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
Чтобы провайдеры были узнаваемы, вам нужно сделать их обнаруживаемыми. JPA обнаруживает использование механизма загрузки сервисов, поэтому мы добавляем:
/src/main/resources/META-INF/services/javax.persistence.spi.PersistenceProvider
Этот файл имеет ровно одну строку:
my.custom.package.HibernateDynamicPersistenceProvider
Наконец, добавьте своего собственного провайдера и основывайте его на HibernateProvider (я основываю его на этом, так как хочу использовать hibernate):
public class HibernateDynamicPersistenceProvider extends HibernatePersistenceProvider implements PersistenceProvider {
private static final Logger log = Logger.getLogger(HibernateDynamicPersistenceProvider.class);
public static final String CUSTOM_CLASSES = "CUSTOM_CLASSES";
@Override
protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader) {
if(persistenceUnitDescriptor instanceof ParsedPersistenceXmlDescriptor) {
ParsedPersistenceXmlDescriptor tmp = (ParsedPersistenceXmlDescriptor) persistenceUnitDescriptor;
Object object = integration.get("CUSTOM_CLASSES");
}
return super.getEntityManagerFactoryBuilder(persistenceUnitDescriptor, integration, providedClassLoader);
}
protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties, ClassLoader providedClassLoader) {
log.debug( String.format("Attempting to obtain correct EntityManagerFactoryBuilder for persistenceUnitName : %s", persistenceUnitName ));
final Map integration = wrap( properties );
final List<ParsedPersistenceXmlDescriptor> units;
try {
units = PersistenceXmlParser.locatePersistenceUnits( integration );
}
catch (Exception e) {
log.debug( "Unable to locate persistence units", e );
throw new PersistenceException( "Unable to locate persistence units", e );
}
log.debug( String.format("Located and parsed %s persistence units; checking each", units.size() ));
if ( persistenceUnitName == null && units.size() > 1 ) {
// no persistence-unit name to look for was given and we found multiple persistence-units
throw new PersistenceException( "No name provided and multiple persistence units found" );
}
for ( ParsedPersistenceXmlDescriptor persistenceUnit : units ) {
log.debug( String.format(
"Checking persistence-unit [name=%s, explicit-provider=%s] against incoming persistence unit name [%s]",
persistenceUnit.getName(),
persistenceUnit.getProviderClassName(),
persistenceUnitName
));
final boolean matches = persistenceUnitName == null || persistenceUnit.getName().equals( persistenceUnitName );
if ( !matches ) {
log.debug( "Excluding from consideration due to name mis-match" );
continue;
}
// See if we (Hibernate) are the persistence provider
String extractRequestedProviderName = ProviderChecker.extractRequestedProviderName(persistenceUnit, integration);
if ( ! ProviderChecker.isProvider( persistenceUnit, properties ) && !(this.getClass().getName().equals(extractRequestedProviderName))) {
log.debug( "Excluding from consideration due to provider mis-match" );
continue;
}
return getEntityManagerFactoryBuilder( persistenceUnit, integration, providedClassLoader );
}
log.debug( "Found no matching persistence units" );
return null;
}
}
Сначала мне пришлось переписать 2 метода:
protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(
PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader)
Это метод перехвата. Я добавил пользовательское свойство "CUSTOM_CLASSES", которое на самом деле должно называться "CUSTOM_PACKAGES", в котором будут перечислены все пакеты, которые необходимо отсканировать. На данный момент я немного ленив и пропущу фактическое сканирование пути к классам, но вы можете сделать это самостоятельно - это довольно просто. Вы можете позвонить
tmp.addClasses("class1", "class2");
Где классы те, которые вы обнаружили.
Второй метод, который мы переопределяем:
protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties, ClassLoader providedClassLoader)
Это потому, что провайдер, которого мы расширяем, жестко запрограммирован, чтобы только классы гибернации могли создавать EMF. Поскольку у нас есть собственный класс, перехватывающий конструкцию, наши имена не складываются. И я добавил:
String extractRequestedProviderName = ProviderChecker.extractRequestedProviderName(persistenceUnit, integration);
if ( ! ProviderChecker.isProvider( persistenceUnit, properties ) && !(this.getClass().getName().equals(extractRequestedProviderName))) {
log.debug( "Excluding from consideration due to provider mis-match" );
continue;
}
Это расширяет обычную проверку гибернации, чтобы в нее также входил мой пользовательский поставщик.
Wola, мы закончили, теперь у вас есть hibernate с включенным сканированием пути к классам с помощью JPA.