Hibernate更快地创建EntityManagerFactory

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

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

  2. 在知道数据库url之前,我可以以某种方式创建EntityManagerFactory对象吗? 我很乐意关闭所有可能的validation。

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

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

更新更多信息和JProfiler分析

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

看来我第一次打开文件时需要比以下时间长得多。 我用JProfiler描述了第一次和第二次运行并比较了结果。

第一次运行:

 create EMF: 4385ms build EMF: 3090ms EJB3Configuration configure: 900ms EJB3Configuration : 380ms 

calltree1.png

第二轮:

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

compare_calltree.png

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

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

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

截图compare_hotspot.png

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

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

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

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

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

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

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

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

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

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

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

否 – 它创建JDBC连接。

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

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

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

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

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

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

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

客户端PC的性能是否有限?

编辑:评论后添加

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

选项:

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

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