In my desktop application new databases get opened quite often. I use Hibernate
/JPA
as an ORM.
The problem is, creating the EntityManagerFactory
is quite slow, taking about 5-6 Seconds on a fast machine. I know that the EntityManagerFactory
is supposed to be heavyweight but this is just too slow for a desktop application where the user expects the new database to be opened quickly.
Can I turn off some EntityManagerFactory features to get an instance faster? Or is it possible to create some of the EntityManagerFactory lazily to speed up cration?
Can I somehow create the EntityManagerFactory object before knowing the database url? I would be happy to turn off all validation for this to be possible.
By doing so, can I pool EntityManagerFactorys for later use?
Any other idea how to create the EntityManagerFactory faster?
Update with more Information and JProfiler profiling
The desktop application can open saved files. Our application document file format constists of 1 SQLite database + and some binary data in a ZIP file. When opening a document, the ZIP gets extracted and the db is opened with Hibernate. The databases all have the same schema, but different data obviously.
It seems that the first time I open a file it takes significantly longer than the following times. I profiled the first and second run with JProfiler and compared the results.
1st Run:
create EMF: 4385ms build EMF: 3090ms EJB3Configuration configure: 900ms EJB3Configuration <clinit>: 380ms
.
2nd Run:
create EMF: 1275ms build EMF: 970ms EJB3Configuration configure: 305ms EJB3Configuration <clinit>: not visible, probably 0ms
.
In the Call tree comparison you can see that some methods are significantly faster (DatabaseManager. as starting point):
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
The Hot spot comparison now has the interesting results:
.
ClassLoader.loadClass: -1686ms XMLSchemaFactory.newSchema: -184ms ClassFile.<init>: -109ms
I am not sure if it is the loading of Hibernate classes or my Entity classes.
A first improvement would be to create an EMF as soon as the application starts just to initialize all necessary classes (I have an empty db file as a prototype already shipped with my Application). @sharakan thank you for your answer, maybe a DeferredConnectionProvider would already be a solution for this problem.
I will try the DeferredConnectionProvider next! But we might be able to speed it up even further. Do you have any more suggestions?
Advertisement
Answer
You should be able to do this by implementing your own ConnectionProvider
as a decorator around a real ConnectionProvider
.
The key observation here is that the ConnectionProvider
isn’t used until an EntityManager
is created (see comment in supportsAggressiveRelease()
for a caveat to that). So you can create a DeferredConnectionProvider
class, and use it to construct the EntityManagerFactory
, but then wait for user input, and do the deferred initialization before actually creating any EntityManager
instances. I’m written this as a wrapper around ConnectionPoolImpl
, but you should be able to use any other implementation of ConnectionProvider
as the base.
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(); } }
a rough example of how to use it:
// 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);
You could put the initial set up of the EntityManagerFactory
on a separate thread or something, so that the user never has to wait for it. Then the only thing they’ll wait for, after specifying the connection info, is the setting up of the connection pool, which should be fairly quick compared to parsing the object model.