26

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

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

  2. 我可以在知道数据库 url 之前以某种方式创建 EntityManagerFactory 对象吗?我很乐意关闭所有验证以使其成为可能。

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

  4. 还有其他想法如何更快地创建 EntityManagerFactory 吗?

更新更多信息和 JProfiler 分析

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

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

第一次运行:

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

调用树1.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

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

截图 compare_hotspot.png.

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

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

第一个改进是在应用程序启动后立即创建一个 EMF,以初始化所有必要的类(我有一个空的 db 文件作为我的应用程序附带的原型)。@sharakan 感谢您的回答,也许 DeferredConnectionProvider 已经可以解决此问题。

接下来我将尝试 DeferredConnectionProvider!但我们也许可以进一步加快速度。你还有什么建议吗?

4

2 回答 2

12

您应该能够通过将自己ConnectionProvider的装饰器实现为真实的ConnectionProvider.

这里的关键观察是,在创建ConnectionProvideran 之前不使用EntityManager(请参阅注释以supportsAggressiveRelease()获取警告)。因此,您可以创建一个DeferredConnectionProvider类,并使用它来构造EntityManagerFactory,然后等待用户输入,并在实际创建任何EntityManager实例之前进行延迟初始化。我把它写成一个包装器ConnectionPoolImpl,但是你应该能够使用任何其他的实现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放在单独的线程或其他东西上,这样用户就不必等待它。然后,在指定连接信息之后,他们唯一需要等待的是连接池的设置,与解析对象模型相比,这应该相当快。

于 2013-03-13T17:54:21.463 回答
3

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

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

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

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

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

否 - 它创建一个 JDBC 连接。

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

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

不,您不能汇集 EMF。这是您可以汇集的连接。

还有其他想法如何更快地创建 EntityManagerFactory 吗?

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

我怀疑这更多地与您选择的数据库技术有关,而不是 JPA/JDBC/JVM。我的猜测是,您的数据库可能会在您连接时自行初始化。您在使用 Access 吗?你用的是什么数据库?

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

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

编辑:评论后添加

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

选项:

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

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

于 2013-03-14T05:29:57.387 回答