有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Hibernate加快EntityManagerFactory创建

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

  1. 我可以关闭一些EntityManagerFactory功能来获取实例吗 更快?或者有没有可能懒散地创建一些EntityManagerFactory来加速cration

  2. 我可以在之前创建EntityManagerFactory对象吗 知道数据库url吗?我很乐意关掉所有的 对此进行验证是可能的

  3. 这样,我可以将整个ManagerFactory集中起来供以后使用吗

  4. 还有其他更快创建EntityManagerFactory的方法吗

更新更多信息和JProfiler评测

桌面应用程序可以打开保存的文件。我们的应用程序文档文件格式由1个SQLite database+和一个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感谢您的回答,也许延迟连接提供商已经是这个问题的解决方案

下一步我将尝试延迟连接提供商!但我们或许可以进一步加快速度。你还有什么建议吗


共 (2) 个答案

  1. # 1 楼答案

    你应该能够通过实现你自己的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的初始设置放在一个单独的线程或其他东西上,这样用户就不必等待它了。那么,在指定连接信息之后,他们唯一需要等待的就是连接池的设置,与解析对象模型相比,这应该是相当快的

  2. # 2 楼答案

    Can I turn off some EntityManagerFactory features to get an instance faster?

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

    Or is it possible to create some of the EntityManagerFactory lazily to speed up cration?

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

    Can I somehow create the EntityManagerFactory object before knowing the database url?

    否-它会创建一个JDBC连接

    我认为更好的问题是:为什么应用程序动态地发现数据库连接URL?你是说你的数据库是动态创建的/可用的,没有办法提前预测连接参数。这确实是应该避免的

    By doing so, can I pool EntityManagerFactorys for later use?

    不,你不能共用EMF。这是你可以共享的连接

    Any other idea how to create the EntityManagerFactory faster?

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

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

    你正在连接远程数据库吗?为了一个万?网络速度/延迟是否良好

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

    编辑:在评论后添加

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

    选项:

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

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