休眠更快的实体管理器工厂创建

2022-09-01 17:49:35

在我的桌面应用程序中,新数据库经常被打开。我使用 / 作为 ORM。问题是,创建速度非常慢,在快速的机器上大约需要5-6秒。我知道应该是重量级的,但这对于桌面应用程序来说太慢了,用户希望快速打开新数据库。HibernateJPAEntityManagerFactoryEntityManagerFactory

  1. 我可以关闭一些EntityManagerFactory功能以更快地获取实例吗?或者是否可以懒惰地创建一些EntityManagerFactory以加快cration速度?

  2. 我可以在知道数据库网址之前以某种方式创建EntityManagerFactory对象吗?我很乐意关闭所有验证,以便实现这一点。

  3. 通过这样做,我可以汇集EntityManagerFactorys供以后使用吗?

  4. 任何其他想法如何更快地创建EntityManagerFactory?

使用更多信息和JProfiler分析进行更新

桌面应用程序可以打开保存的文件。我们的应用程序文档文件格式包含1个SQLite数据库+和ZIP文件中的一些二进制数据。打开文档时,将提取 ZIP,并使用休眠打开 db。数据库都具有相同的架构,但数据明显不同。

似乎我第一次打开文件时花费的时间比以下时间长得多。我用JProfiler分析了第一次和第二次运行,并比较了结果。

第一次运行:

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

calltree1.png.

第二次运行:

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

compare_calltree.png.

在调用树比较中,您可以看到某些方法明显更快(以 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

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

screenshot compare_hotspot.png.

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

我不确定是加载Hibernate类还是我的实体类。

第一个改进是在应用程序启动时立即创建一个EMF,只是为了初始化所有必要的类(我有一个空的db文件作为原型已经随我的应用程序一起提供)。@sharakan感谢您的回答,也许延期连接提供者已经是这个问题的解决方案。

接下来我会尝试延期连接提供器!但我们也许能够进一步加快速度。您还有其他建议吗?


答案 1

您应该能够通过实现自己的作为围绕真实.ConnectionProviderConnectionProvider

这里的关键观察结果是,在创建 之前不会使用 (请参阅 注释 中的警告)。因此,您可以创建一个类,并使用它来构造 ,但随后等待用户输入,并在实际创建任何实例之前执行延迟初始化。我把它写成一个包装器,但你应该能够使用任何其他实现作为基础。ConnectionProviderEntityManagersupportsAggressiveRelease()DeferredConnectionProviderEntityManagerFactoryEntityManagerConnectionPoolImplConnectionProvider

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


答案 2

我可以关闭一些EntityManagerFactory功能以更快地获取实例吗?

不要这么认为。EMF 实际上没有太多的功能,除了初始化 JDBC 连接/池。

或者是否可以懒惰地创建一些EntityManagerFactory以加快cration速度?

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

我可以在知道数据库网址之前以某种方式创建EntityManagerFactory对象吗?

否 - 它创建一个 JDBC 连接。

我认为一个更好的问题是:为什么您的应用程序动态发现数据库连接URL?您是说您的数据库是动态创建/可用的,并且无法提前预测连接参数。这确实是要避免的。

通过这样做,我可以汇集EntityManagerFactorys供以后使用吗?

不可以,您不能池化电磁场。这是您可以池化的连接。

任何其他想法如何更快地创建EntityManagerFactory?

我同意 - 6秒对于EMF的初始化来说太慢了。

我怀疑这更多地与您选择的数据库技术有关,而不是JPA / JDBC / JVM。我的猜测是,也许你的数据库在你连接时会初始化自己。您是否在使用 Access?您使用的是哪种数据库?

是否连接到远程数据库?通过广域网?网络速度/延迟是否良好?

客户端电脑的性能是否受到限制?

编辑:在评论后添加

将您自己的ConnectionProvider实现为围绕真实ConnectionProvider的装饰器根本不会加快用户体验。数据库实例仍然需要初始化,创建 EMF 和 EM,并且仍然需要随后建立 JDBC 连接。

选项:

  1. 共享一个通用的预加载数据库实例:对于您的业务场景似乎是不可能的(尽管 JSE 技术支持这一点,并且还支持客户端-服务器设计)。
  2. 更改为启动速度更快的数据库:Derby(又名 Java DB)包含在现代 JVM 中,启动时间约为 1.5 秒(冷)和 0.7 秒(预加载数据)。
  3. 在许多(大多数?)场景中,最快的解决方案是使用 JAXB 和 STAX 将数据直接加载到内存中的 java 对象中。随后,使用内存中的缓存数据(特别是使用映射,哈希和数组列表等智能结构)。就像 JPA 可以将 POJO 类映射到数据库表和列一样,JAXB 也可以将 POJO 类映射到 XML 模式并使用 XML doc 实例。如果您有非常复杂的查询,使用基于 SQL 集的逻辑,具有多个联接和数据库索引的强使用,则不太理想。

(2)可能会为有限的努力提供最好的改进。
此外: - 尝试在部署期间解压缩数据文件,而不是在应用程序使用期间。- 在与UI启动并行运行的启动线程中初始化EMF - 尝试启动数据库初始化作为应用程序的第一步之一(这意味着使用JDBC连接到实际实例)。


推荐