Skip to content

Commit

Permalink
Background bootstrapping via "bootstrapExecutor" for LocalContainerEn…
Browse files Browse the repository at this point in the history
…tityManagerFactoryBean and LocalSessionFactoryBean/Builder

Issue: SPR-13732
  • Loading branch information
jhoeller committed Dec 19, 2015
1 parent 64bd8b7 commit db1171d
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;

/**
Expand Down Expand Up @@ -93,6 +94,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator

private String[] packagesToScan;

private AsyncTaskExecutor bootstrapExecutor;

private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

private Configuration configuration;
Expand Down Expand Up @@ -298,6 +301,23 @@ public void setPackagesToScan(String... packagesToScan) {
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);
Expand Down Expand Up @@ -413,7 +433,8 @@ public void afterPropertiesSet() throws IOException {
* @see LocalSessionFactoryBuilder#buildSessionFactory
*/
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return sfb.buildSessionFactory();
return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
sfb.buildSessionFactory());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,15 +37,18 @@

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;
Expand Down Expand Up @@ -263,4 +273,79 @@ private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFac
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);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.NonUniqueObjectException;
Expand All @@ -38,6 +37,7 @@
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;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -30,6 +30,9 @@
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;
Expand All @@ -48,6 +51,7 @@
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;
Expand Down Expand Up @@ -102,15 +106,21 @@ public abstract class AbstractEntityManagerFactoryBean implements

private JpaVendorAdapter jpaVendorAdapter;

private AsyncTaskExecutor bootstrapExecutor;

private ClassLoader beanClassLoader = getClass().getClassLoader();

private BeanFactory beanFactory;

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;


Expand Down Expand Up @@ -263,6 +273,30 @@ public JpaVendorAdapter getJpaVendorAdapter() {
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;
Expand Down Expand Up @@ -315,20 +349,40 @@ public final void afterPropertiesSet() throws PersistenceException {
}
}

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.
// This allows interception of createEntityManager methods to return an
// 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;
}

/**
Expand All @@ -343,9 +397,12 @@ protected EntityManagerFactory createEntityManagerFactoryProxy(EntityManagerFact
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(
Expand Down Expand Up @@ -379,13 +436,13 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
// 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;
Expand Down Expand Up @@ -420,7 +477,21 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {

@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
Expand Down
Loading

0 comments on commit db1171d

Please sign in to comment.