Hibernate更快地创建EntityManagerFactory

时间:2013-02-23 02:11:20

标签: java sql hibernate jpa orm

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

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

  2. 我可以以某种方式创建EntityManagerFactory对象     知道数据库网址?我很乐意关闭所有     验证是可能的。

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

  4. 如何更快地创建EntityManagerFactory?

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

    桌面应用程序可以打开已保存的文件。我们的应用程序文档文件格式包含1个SQLite数据库+以及ZIP文件中的一些二进制数据。打开文档时,将解压缩ZIP并使用Hibernate打开数据库。数据库都具有相同的模式,但显然有不同的数据。

    似乎我第一次打开文件所需的时间比以下时间长得多。 我用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感谢您的回答,也许DeferredConnectionProvider已经是这个问题的解决方案。

    我接下来会尝试DeferredConnectionProvider!但我们可能会进一步加快速度。你有什么建议吗?

2 个答案:

答案 0 :(得分:11)

您应该可以通过在真实ConnectionProvider周围实施自己的ConnectionProvider作为装饰器来实现此目的。

这里的关键观察是ConnectionProvider在创建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的初始设置放在单独的线程或其他内容上,以便用户永远不必等待它。然后,在指定连接信息之后,他们唯一要等待的是设置连接池,与解析对象模型相比,这应该相当快。

答案 1 :(得分:2)

  

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

不要相信。除初始化JDBC连接/池外,EMF实际上没有太多功能。

  

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

当用户注意到性能受到影响时,我建议您应该向相反的方向前进 - 在用户实际需要之前主动创建EMF,而不是懒惰地创建EMF。在应用程序初始化期间(或者至少在您了解数据库时),可能在一个单独的线程中创建一次,预先创建它。在应用程序/数据库的整个存在过程中重复使用它。

  

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

否 - 它创建了一个JDBC连接。

我认为更好的问题是:为什么您的应用程序动态发现数据库连接URL?您是说您的数据库是在运行中创建/提供的,并且无法提前预测连接参数。真的要避免。

  

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

不,你不能集合EMF。这是你可以联合的连接。

  

如何更快地创建EntityManagerFactory?

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

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

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

客户端PC性能是否有限?

编辑:在评论后添加

在真正的ConnectionProvider周围实现自己的ConnectionProvider作为装饰器不会加速用户的体验。数据库实例仍然需要初始化,EMF&amp;创建EM并且仍然需要建立JDBC连接。

选项:

  1. 共享一个共同的预加载数据库实例:您的业务场景似乎不可能(尽管JSE技术支持此功能并且还支持客户端 - 服务器设计)。
  2. 更改为启动速度更快的数据库:Derby(a.k.a. Java DB)包含在现代JVM中,启动时间约为1.5秒(冷)和0.7秒(暖 - 数据预加载)。
  3. 在许多(大多数?)场景中,最快的解决方案是使用带有STAX的JAXB将数据直接加载到内存中的java对象中。随后,使用内存缓存数据(特别是使用智能结构,如地图,散列和arraylists)。正如JPA可以将POJO类映射到数据库表&amp;列,因此JAXB可以将POJO类映射到XML模式&amp;使用XML doc实例。如果您使用基于SQL集合的逻辑进行非常复杂的查询,并且使用多个连接并强烈使用DB索引,那么这将是不太理想的。
  4. (2)可能会为有限的努力提供最好的改进 另外:   - 尝试在部署期间而不是在应用程序使用期间解压缩数据文件。   - 在与UI启动并行运行的启动线程中初始化EMF - 尝试启动数据库初始化作为应用程序的第一步(即使用JDBC连接到实际实例)。