java Hibernate加快EntityManagerFactory创建

uz75evzq  于 2023-03-21  发布在  Java
关注(0)|答案(2)|浏览(122)

在我的桌面应用程序中,新的数据库经常被打开。我使用Hibernate/JPA作为ORM。问题是,创建EntityManagerFactory非常慢,在快速的机器上大约需要5-6秒。我知道EntityManagerFactory应该是重量级的,但这对于用户期望快速打开新数据库的桌面应用程序来说太慢了。
1.我可以关闭一些EntityManagerFactory特性来更快地获得示例吗?或者可以延迟创建一些EntityManagerFactory来加快创建速度吗?
1.我可以在知道数据库url之前创建EntityManagerFactory对象吗?我很乐意关闭所有验证。
1.通过这样做,我可以池化EntityManagerFactorys供以后使用吗?
1.如何更快地创建EntityManagerFactory?

更新更多信息和JProfiler分析

桌面应用程序可以打开保存的文件。我们的应用程序文档文件格式由1个SQLite数据库+和ZIP文件中的一些二进制数据组成。当打开文档时,ZIP被提取,并使用Hibernate打开数据库。数据库都具有相同的模式,但数据明显不同。
我第一次打开一个文件的时间似乎比后面的时间要长得多。我用JProfiler分析了第一次和第二次运行的情况,并比较了结果。
第1次运行:

create EMF: 4385ms
    build EMF: 3090ms
    EJB3Configuration configure: 900ms
    EJB3Configuration <clinit>: 380ms


.
第2次运行:

create EMF: 1275ms
    build EMF: 970ms
    EJB3Configuration configure: 305ms
    EJB3Configuration <clinit>: not visible, probably 0ms


.
在调用树比较中,您可以看到一些方法明显更快(DatabaseManager.作为起点):

create EMF: -3120ms
    Hibernate create EMF: -3110ms
        EJB3Configuration configure: -595ms
        EJB3Configuration <clinit>: -380ms
        build EMF: -2120ms
            buildSessionFactory: -1945ms
                secondPassCompile: -425ms
                buildSettings: -346ms
                SessionFactoryImpl.<init>: -1040ms

热点比较现在有了有趣的结果:


.

ClassLoader.loadClass: -1686ms
XMLSchemaFactory.newSchema: -184ms
ClassFile.<init>: -109ms

我不确定是Hibernate类的加载还是我的Entity类的加载。
第一个改进是在应用程序启动时创建EMF,以初始化所有必要的类(我有一个空的db文件作为我的应用程序已经附带的原型)。
我将尝试DeferredConnectionProvider下一个!但我们可能能够加快它甚至进一步.你有任何更多的建议?

omqzjyyz

omqzjyyz1#

您应该能够通过将自己的ConnectionProvider实现为真实的的ConnectionProvider的装饰器来实现这一点。
这里的关键观察结果是,在创建EntityManager之前不会使用ConnectionProvider(请参阅supportsAggressiveRelease()中的注解以获得警告)。因此,您可以创建一个DeferredConnectionProvider类,并使用它来构造EntityManagerFactory,但随后等待用户输入,并在实际创建任何EntityManager示例之前进行延迟初始化。我将其作为ConnectionPoolImpl的 Package 器编写,但是您应该能够使用ConnectionProvider的任何其他实现作为基础。

public class DeferredConnectionProvider implements ConnectionProvider {

    private Properties configuredProps;
    private ConnectionProviderImpl realConnectionProvider;

    @Override
    public void configure(Properties props) throws HibernateException {
        configuredProps = props;
    }

    public void finalConfiguration(String jdbcUrl, String userName, String password) {
        configuredProps.setProperty(Environment.URL, jdbcUrl);
        configuredProps.setProperty(Environment.USER, userName);
        configuredProps.setProperty(Environment.PASS, password);

        realConnectionProvider = new ConnectionProviderImpl();
        realConnectionProvider.configure(configuredProps);
    }

    private void assertConfigured() {
        if (realConnectionProvider == null) {
            throw new IllegalStateException("Not configured yet!");
        }
    }        

    @Override
    public Connection getConnection() throws SQLException {
        assertConfigured();

        return realConnectionProvider.getConnection();
    }

    @Override
    public void closeConnection(Connection conn) throws SQLException {
        assertConfigured();

        realConnectionProvider.closeConnection(conn);
    }

    @Override
    public void close() throws HibernateException {
        assertConfigured();

        realConnectionProvider.close();
    }

    @Override
    public boolean supportsAggressiveRelease() {
        // This gets called during EntityManagerFactory construction, but it's 
        // just a flag so you should be able to either do this, or return
        // true/false depending on the actual provider.
        return new ConnectionProviderImpl().supportsAggressiveRelease();
    }
}

一个简单的例子来说明如何使用它:

// Get an EntityManagerFactory with the following property set:
    //     properties.put(Environment.CONNECTION_PROVIDER, DeferredConnectionProvider.class.getName());
    HibernateEntityManagerFactory factory = (HibernateEntityManagerFactory) entityManagerFactory;

    // ...do user input of connection info...

    SessionFactoryImpl sessionFactory = (SessionFactoryImpl) factory.getSessionFactory();
    DeferredConnectionProvider connectionProvider = (DeferredConnectionProvider) sessionFactory.getSettings()
                    .getConnectionProvider();

    connectionProvider.finalConfiguration(jdbcUrl, userName, password);

你可以把EntityManagerFactory的初始设置放在一个单独的线程上,这样用户就不必等待它了。然后,在指定连接信息之后,他们唯一要等待的就是连接池的设置,与解析对象模型相比,这应该是相当快的。

twh00eeo

twh00eeo2#

  • 是否可以关闭一些EntityManagerFactory功能以更快地获取示例?*

EMF并没有太多的特性,除了初始化JDBC连接/池。

  • 或者可以惰性地创建一些EntityManagerFactory来加速创建?*

我建议您不要在用户注意到性能下降时才懒洋洋地创建EMF,而应该朝着相反的方向前进-在用户真正需要EMF之前主动创建EMF。在应用程序初始化期间(或者至少在您了解数据库时)在单独的线程中创建EMF一次。在整个应用程序/数据库存在期间重用它。

  • 在知道数据库url之前,我可以以某种方式创建EntityManagerFactory对象吗?*

不-它创建一个JDBC连接。
我觉得更好的问题是:为什么你的应用程序会动态地发现数据库连接URL?你是说你的数据库是动态创建/提供的,没有办法提前预测连接参数。这真的是要避免的。

  • 这样,我是否可以将EntityManagerFactory池化以供以后使用?*

不,你不能共享电磁场。你可以共享的是连接。

  • 有没有其他方法可以更快地创建EntityManagerFactory?*

我同意- 6秒对于EMF的初始化来说太慢了。
我怀疑这与你选择的数据库技术比JPA/JDBC/JVM更有关系。我的猜测是,也许你的数据库在你连接时正在初始化自己。你使用Access吗?你使用什么DB?
您是否通过WAN连接到远程数据库?网络速度/延迟是否良好?
客户端PC的性能是否受到限制?

编辑:评论后添加

将自己的ConnectionProvider实现为围绕真实的ConnectionProvider的装饰器根本不会加快用户体验。数据库示例仍然需要初始化,EMF & EM创建,JDBC连接仍然需要随后建立。
选项:
1.共享一个公共的预加载数据库示例:对于您的业务场景似乎是不可能的(尽管JSE技术支持这一点,并且还支持客户机-服务器设计)。
1.更改为启动速度更快的DB:Derby(又名Java DB)包含在现代JVM中,启动时间约为1.5秒(冷)和0.7秒(热-预加载数据)。
1.在许多在大多数情况下,最快的解决方案是使用JAXB和STAX将数据直接加载到内存中的java对象中(特别是使用像map、hashing和arraylists这样的智能结构)。就像JPA可以将POJO类Map到数据库表和列一样,因此JAXB可以将POJO类Map到XML模式,并使用XML文档示例。如果您有非常复杂查询,使用基于SQL集的逻辑,并使用多个连接和大量使用DB索引,那么这将是不可取的。
(2)可能会在有限的努力下得到最好的改善。
此外:- 尝试在部署期间而不是在应用程序使用期间解压缩数据文件。-在与UI启动并行运行的启动线程中初始化EMF-尝试启动DB初始化作为应用程序的第一步之一(这意味着使用JDBC连接到实际示例)。

相关问题