在运行时动态添加实体类

2022-09-01 06:09:07

我有这样的要求,即在运行时将实体类添加到持久性单元,而不是在持久性.xml中指定所有实体类。有人可以帮我做同样的事情吗?

我知道Hibernate有自己的机制来做同样的事情:

AnnotationConfiguration.addAnnotatedClass(Class)等 - 还可以以编程方式添加休眠配置 () 文件。*.hbm.xml

要求是,在不重新启动应用服务器的情况下,我应该能够继续将实体类/它们的配置(映射)文件动态地添加到持久性单元。

但是,在运行时以编程方式将实体类/配置文件添加到持久性单元的解决方案不应特定于 JPA 实现。


答案 1

JPA 尚未提供此功能。以下是您可以查看的三个选项:

编辑:

如果 JPA 提供程序是 Hibernate,则从 Hibernate 4.0 开始,可以将实体直接传递给此 JPA 提供程序,而无需在文件中声明它们。休眠将动态处理实体。persistence.xml

编辑:

以下是 JPA 2.1 + Hibernate 4.3.7.Final 的配置示例,无需声明任何实体:

元 INF/持久性.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>

引用


答案 2

我迟到了,但我认为这将使一些人头疼。我为纯JPA(不需要弹簧等)实现了类路径扫描,如果需要,还可以与guice-persist等集成。

这是您需要执行的操作。

首先,更改持久性.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,我们完成了,您现在可以使用JPA进行休眠启用类路径扫描。


推荐