diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
index bf613b0252..b4d2d941ab 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
@@ -38,6 +38,7 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.type.filter.TypeFilter;
/**
@@ -93,6 +94,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
private String[] packagesToScan;
+ private AsyncTaskExecutor bootstrapExecutor;
+
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private Configuration configuration;
@@ -298,6 +301,23 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
this.packagesToScan = packagesToScan;
}
+ /**
+ * Specify an asynchronous executor for background bootstrapping,
+ * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
+ *
{@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
+ * @since 4.3
+ */
+ public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+ this.bootstrapExecutor = bootstrapExecutor;
+ }
+
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
@@ -413,7 +433,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
* @see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
- return sfb.buildSessionFactory();
+ return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
+ sfb.buildSessionFactory());
}
/**
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
index def7931f09..2b83c42b2f 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
@@ -17,9 +17,16 @@
package org.springframework.orm.hibernate5;
import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.Embeddable;
@@ -30,15 +37,18 @@ import javax.transaction.TransactionManager;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
+import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -263,4 +273,79 @@ public class LocalSessionFactoryBuilder extends Configuration {
return false;
}
+ /**
+ * Build the Hibernate {@code SessionFactory} through background bootstrapping,
+ * using the given executor for a parallel initialization phase
+ * (e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}).
+ *
{@code SessionFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
+ * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
+ * However, note that the first actual call to a {@code SessionFactory} method will
+ * then block until Hibernate's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @since 4.3
+ * @see #buildSessionFactory()
+ */
+ public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
+ Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null");
+ return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(),
+ new Class>[] {SessionFactoryImplementor.class},
+ new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor));
+ }
+
+
+ /**
+ * Proxy invocation handler for background bootstrapping, only enforcing
+ * a fully initialized target {@code SessionFactory} when actually needed.
+ */
+ private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler {
+
+ private final Future sessionFactoryFuture;
+
+ public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
+ this.sessionFactoryFuture = bootstrapExecutor.submit(new Callable() {
+ @Override
+ public SessionFactory call() throws Exception {
+ return buildSessionFactory();
+ }
+ });
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ if (method.getName().equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (method.getName().equals("hashCode")) {
+ // Use hashCode of EntityManagerFactory proxy.
+ return System.identityHashCode(proxy);
+ }
+ else if (method.getName().equals("getProperties")) {
+ return getProperties();
+ }
+ // Regular delegation to the target SessionFactory,
+ // enforcing its full initialization...
+ return method.invoke(getSessionFactory(), args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+
+ private SessionFactory getSessionFactory() {
+ try {
+ return this.sessionFactoryFuture.get();
+ }
+ catch (InterruptedException ex) {
+ throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory", ex);
+ }
+ catch (ExecutionException ex) {
+ throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory", ex);
+ }
+ }
+ }
+
}
diff --git a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
index 175dd05b8e..19151579b8 100644
--- a/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
+++ b/spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java
@@ -20,7 +20,6 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.NonUniqueObjectException;
@@ -38,6 +37,7 @@ import org.hibernate.StaleStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.cfg.Environment;
import org.hibernate.dialect.lock.OptimisticEntityLockException;
import org.hibernate.dialect.lock.PessimisticEntityLockException;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@@ -94,9 +94,13 @@ public abstract class SessionFactoryUtils {
*/
public static DataSource getDataSource(SessionFactory sessionFactory) {
if (sessionFactory instanceof SessionFactoryImplementor) {
+ SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory;
+ Object dataSourceValue = sfi.getProperties().get(Environment.DATASOURCE);
+ if (dataSourceValue instanceof DataSource) {
+ return (DataSource) dataSourceValue;
+ }
try {
- ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(
- ConnectionProvider.class);
+ ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
if (cp != null) {
return cp.unwrap(DataSource.class);
}
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
index 872ba91f37..1bc736179b 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,9 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
@@ -48,6 +51,7 @@ import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
@@ -102,6 +106,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
private JpaVendorAdapter jpaVendorAdapter;
+ private AsyncTaskExecutor bootstrapExecutor;
+
private ClassLoader beanClassLoader = getClass().getClassLoader();
private BeanFactory beanFactory;
@@ -109,8 +115,12 @@ public abstract class AbstractEntityManagerFactoryBean implements
private String beanName;
/** Raw EntityManagerFactory as returned by the PersistenceProvider */
- public EntityManagerFactory nativeEntityManagerFactory;
+ private EntityManagerFactory nativeEntityManagerFactory;
+ /** Future for lazily initializing raw target EntityManagerFactory */
+ private Future nativeEntityManagerFactoryFuture;
+
+ /** Exposed client-level EntityManagerFactory proxy */
private EntityManagerFactory entityManagerFactory;
@@ -263,6 +273,30 @@ public abstract class AbstractEntityManagerFactoryBean implements
return this.jpaVendorAdapter;
}
+ /**
+ * Specify an asynchronous executor for background bootstrapping,
+ * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
+ * {@code EntityManagerFactory} initialization will then switch into background
+ * bootstrap mode, with a {@code EntityManagerFactory} proxy immediately returned for
+ * injection purposes instead of waiting for the JPA provider's bootstrapping to complete.
+ * However, note that the first actual call to a {@code EntityManagerFactory} method will
+ * then block until the JPA provider's bootstrapping completed, if not ready by then.
+ * For maximum benefit, make sure to avoid early {@code EntityManagerFactory} calls
+ * in init methods of related beans, even for metadata introspection purposes.
+ * @since 4.3
+ */
+ public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
+ this.bootstrapExecutor = bootstrapExecutor;
+ }
+
+ /**
+ * Return the asynchronous executor for background bootstrapping, if any.
+ * @since 4.3
+ */
+ public AsyncTaskExecutor getBootstrapExecutor() {
+ return this.bootstrapExecutor;
+ }
+
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
@@ -315,13 +349,16 @@ public abstract class AbstractEntityManagerFactoryBean implements
}
}
- this.nativeEntityManagerFactory = createNativeEntityManagerFactory();
- if (this.nativeEntityManagerFactory == null) {
- throw new IllegalStateException(
- "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
+ if (this.bootstrapExecutor != null) {
+ this.nativeEntityManagerFactoryFuture = this.bootstrapExecutor.submit(new Callable() {
+ @Override
+ public EntityManagerFactory call() {
+ return buildNativeEntityManagerFactory();
+ }
+ });
}
- if (this.jpaVendorAdapter != null) {
- this.jpaVendorAdapter.postProcessEntityManagerFactory(this.nativeEntityManagerFactory);
+ else {
+ this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
}
// Wrap the EntityManagerFactory in a factory implementing all its interfaces.
@@ -329,6 +366,23 @@ public abstract class AbstractEntityManagerFactoryBean implements
// application-managed EntityManager proxy that automatically joins
// existing transactions.
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
+ System.out.println("Returning: " + System.currentTimeMillis());
+ }
+
+ private EntityManagerFactory buildNativeEntityManagerFactory() {
+ EntityManagerFactory emf = createNativeEntityManagerFactory();
+ if (emf == null) {
+ throw new IllegalStateException(
+ "JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
+ }
+ if (this.jpaVendorAdapter != null) {
+ this.jpaVendorAdapter.postProcessEntityManagerFactory(emf);
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("Initialized JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
+ }
+ System.out.println("Done: " + System.currentTimeMillis());
+ return emf;
}
/**
@@ -343,9 +397,12 @@ public abstract class AbstractEntityManagerFactoryBean implements
if (this.entityManagerFactoryInterface != null) {
ifcs.add(this.entityManagerFactoryInterface);
}
- else {
+ else if (emf != null) {
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
}
+ else {
+ ifcs.add(EntityManagerFactory.class);
+ }
ifcs.add(EntityManagerFactoryInfo.class);
try {
return (EntityManagerFactory) Proxy.newProxyInstance(
@@ -379,13 +436,13 @@ public abstract class AbstractEntityManagerFactoryBean implements
// JPA 2.1's createEntityManager(SynchronizationType, Map)
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
EntityManager rawEntityManager = (args.length > 1 ?
- this.nativeEntityManagerFactory.createEntityManager((Map, ?>) args[1]) :
- this.nativeEntityManagerFactory.createEntityManager());
+ getNativeEntityManagerFactory().createEntityManager((Map, ?>) args[1]) :
+ getNativeEntityManagerFactory().createEntityManager());
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
}
// Standard delegation to the native factory, just post-processing EntityManager return values
- Object retVal = method.invoke(this.nativeEntityManagerFactory, args);
+ Object retVal = method.invoke(getNativeEntityManagerFactory(), args);
if (retVal instanceof EntityManager) {
// Any other createEntityManager variant - expecting non-synchronized semantics
EntityManager rawEntityManager = (EntityManager) retVal;
@@ -420,7 +477,21 @@ public abstract class AbstractEntityManagerFactoryBean implements
@Override
public EntityManagerFactory getNativeEntityManagerFactory() {
- return this.nativeEntityManagerFactory;
+ if (this.nativeEntityManagerFactory != null) {
+ return this.nativeEntityManagerFactory;
+ }
+ else {
+ System.out.println("Requested: " + System.currentTimeMillis());
+ try {
+ return this.nativeEntityManagerFactoryFuture.get();
+ }
+ catch (InterruptedException ex) {
+ throw new IllegalStateException("Interrupted during initialization of native EntityManagerFactory", ex);
+ }
+ catch (ExecutionException ex) {
+ throw new IllegalStateException("Failed to asynchronously initialize native EntityManagerFactory", ex);
+ }
+ }
}
@Override
diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
index e9fbb6ce54..8cfd8e99d2 100644
--- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
+++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,8 +43,8 @@ import org.springframework.util.ClassUtils;
* up a shared JPA EntityManagerFactory in a Spring application context;
* the EntityManagerFactory can then be passed to JPA-based DAOs via
* dependency injection. Note that switching to a JNDI lookup or to a
- * {@link LocalEntityManagerFactoryBean}
- * definition is just a matter of configuration!
+ * {@link LocalEntityManagerFactoryBean} definition is just a matter of
+ * configuration!
*
* As with {@link LocalEntityManagerFactoryBean}, configuration settings
* are usually read in from a {@code META-INF/persistence.xml} config file,
@@ -329,21 +329,16 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
Class> providerClass = ClassUtils.resolveClassName(providerClassName, getBeanClassLoader());
provider = (PersistenceProvider) BeanUtils.instantiateClass(providerClass);
}
- if (provider == null) {
- throw new IllegalStateException("Unable to determine persistence provider. " +
- "Please check configuration of " + getClass().getName() + "; " +
- "ideally specify the appropriate JpaVendorAdapter class for this provider.");
- }
if (logger.isInfoEnabled()) {
logger.info("Building JPA container EntityManagerFactory for persistence unit '" +
this.persistenceUnitInfo.getPersistenceUnitName() + "'");
}
- this.nativeEntityManagerFactory =
+ EntityManagerFactory emf =
provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap());
- postProcessEntityManagerFactory(this.nativeEntityManagerFactory, this.persistenceUnitInfo);
+ postProcessEntityManagerFactory(emf, this.persistenceUnitInfo);
- return this.nativeEntityManagerFactory;
+ return emf;
}
diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
index 2350190711..ea7ce16c84 100644
--- a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
+++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/hibernate-manager.xml
@@ -10,13 +10,13 @@
-
-
+
+
-
-
-
+
+
+
@@ -25,6 +25,9 @@
+
+
+