Background bootstrapping via "bootstrapExecutor" for LocalContainerEntityManagerFactoryBean and LocalSessionFactoryBean/Builder
Issue: SPR-13732
This commit is contained in:
@@ -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}.
|
||||
* <p>{@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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}).
|
||||
* <p>{@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<SessionFactory> sessionFactoryFuture;
|
||||
|
||||
public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
|
||||
this.sessionFactoryFuture = bootstrapExecutor.submit(new Callable<SessionFactory>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<EntityManagerFactory> 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}.
|
||||
* <p>{@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<EntityManagerFactory>() {
|
||||
@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
|
||||
|
||||
@@ -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!
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<context:annotation-config />
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence-context.xml" />
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
<property name="persistenceXmlLocation" value="org/springframework/orm/jpa/domain/persistence-context.xml"/>
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
|
||||
<property name="database" value="HSQL" />
|
||||
<property name="showSql" value="true" />
|
||||
<property name="generateDdl" value="true" />
|
||||
<property name="database" value="HSQL"/>
|
||||
<property name="showSql" value="true"/>
|
||||
<property name="generateDdl" value="true"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="jpaPropertyMap">
|
||||
@@ -25,6 +25,9 @@
|
||||
<!-- <prop key="hibernate.ejb.use_class_enhancer">true</prop> -->
|
||||
</props>
|
||||
</property>
|
||||
<property name="bootstrapExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
Reference in New Issue
Block a user