Skip to content

Commit db1171d

Browse files
committed
Background bootstrapping via "bootstrapExecutor" for LocalContainerEntityManagerFactoryBean and LocalSessionFactoryBean/Builder
Issue: SPR-13732
1 parent 64bd8b7 commit db1171d

File tree

6 files changed

+212
-33
lines changed

6 files changed

+212
-33
lines changed

spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
3939
import org.springframework.core.io.support.ResourcePatternResolver;
4040
import org.springframework.core.io.support.ResourcePatternUtils;
41+
import org.springframework.core.task.AsyncTaskExecutor;
4142
import org.springframework.core.type.filter.TypeFilter;
4243

4344
/**
@@ -93,6 +94,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
9394

9495
private String[] packagesToScan;
9596

97+
private AsyncTaskExecutor bootstrapExecutor;
98+
9699
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
97100

98101
private Configuration configuration;
@@ -298,6 +301,23 @@ public void setPackagesToScan(String... packagesToScan) {
298301
this.packagesToScan = packagesToScan;
299302
}
300303

304+
/**
305+
* Specify an asynchronous executor for background bootstrapping,
306+
* e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
307+
* <p>{@code SessionFactory} initialization will then switch into background
308+
* bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
309+
* injection purposes instead of waiting for Hibernate's bootstrapping to complete.
310+
* However, note that the first actual call to a {@code SessionFactory} method will
311+
* then block until Hibernate's bootstrapping completed, if not ready by then.
312+
* For maximum benefit, make sure to avoid early {@code SessionFactory} calls
313+
* in init methods of related beans, even for metadata introspection purposes.
314+
* @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
315+
* @since 4.3
316+
*/
317+
public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
318+
this.bootstrapExecutor = bootstrapExecutor;
319+
}
320+
301321
@Override
302322
public void setResourceLoader(ResourceLoader resourceLoader) {
303323
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
@@ -413,7 +433,8 @@ public void afterPropertiesSet() throws IOException {
413433
* @see LocalSessionFactoryBuilder#buildSessionFactory
414434
*/
415435
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
416-
return sfb.buildSessionFactory();
436+
return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
437+
sfb.buildSessionFactory());
417438
}
418439

419440
/**

spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java

+85
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@
1717
package org.springframework.orm.hibernate5;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.InvocationHandler;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.lang.reflect.Proxy;
2024
import java.util.Collections;
2125
import java.util.Set;
2226
import java.util.TreeSet;
27+
import java.util.concurrent.Callable;
28+
import java.util.concurrent.ExecutionException;
29+
import java.util.concurrent.Future;
2330
import javax.persistence.AttributeConverter;
2431
import javax.persistence.Converter;
2532
import javax.persistence.Embeddable;
@@ -30,15 +37,18 @@
3037

3138
import org.hibernate.HibernateException;
3239
import org.hibernate.MappingException;
40+
import org.hibernate.SessionFactory;
3341
import org.hibernate.cfg.AvailableSettings;
3442
import org.hibernate.cfg.Configuration;
3543
import org.hibernate.cfg.Environment;
44+
import org.hibernate.engine.spi.SessionFactoryImplementor;
3645

3746
import org.springframework.core.io.Resource;
3847
import org.springframework.core.io.ResourceLoader;
3948
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
4049
import org.springframework.core.io.support.ResourcePatternResolver;
4150
import org.springframework.core.io.support.ResourcePatternUtils;
51+
import org.springframework.core.task.AsyncTaskExecutor;
4252
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
4353
import org.springframework.core.type.classreading.MetadataReader;
4454
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -263,4 +273,79 @@ private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFac
263273
return false;
264274
}
265275

276+
/**
277+
* Build the Hibernate {@code SessionFactory} through background bootstrapping,
278+
* using the given executor for a parallel initialization phase
279+
* (e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}).
280+
* <p>{@code SessionFactory} initialization will then switch into background
281+
* bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
282+
* injection purposes instead of waiting for Hibernate's bootstrapping to complete.
283+
* However, note that the first actual call to a {@code SessionFactory} method will
284+
* then block until Hibernate's bootstrapping completed, if not ready by then.
285+
* For maximum benefit, make sure to avoid early {@code SessionFactory} calls
286+
* in init methods of related beans, even for metadata introspection purposes.
287+
* @since 4.3
288+
* @see #buildSessionFactory()
289+
*/
290+
public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) {
291+
Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null");
292+
return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(),
293+
new Class<?>[] {SessionFactoryImplementor.class},
294+
new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor));
295+
}
296+
297+
298+
/**
299+
* Proxy invocation handler for background bootstrapping, only enforcing
300+
* a fully initialized target {@code SessionFactory} when actually needed.
301+
*/
302+
private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler {
303+
304+
private final Future<SessionFactory> sessionFactoryFuture;
305+
306+
public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) {
307+
this.sessionFactoryFuture = bootstrapExecutor.submit(new Callable<SessionFactory>() {
308+
@Override
309+
public SessionFactory call() throws Exception {
310+
return buildSessionFactory();
311+
}
312+
});
313+
}
314+
315+
@Override
316+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
317+
try {
318+
if (method.getName().equals("equals")) {
319+
// Only consider equal when proxies are identical.
320+
return (proxy == args[0]);
321+
}
322+
else if (method.getName().equals("hashCode")) {
323+
// Use hashCode of EntityManagerFactory proxy.
324+
return System.identityHashCode(proxy);
325+
}
326+
else if (method.getName().equals("getProperties")) {
327+
return getProperties();
328+
}
329+
// Regular delegation to the target SessionFactory,
330+
// enforcing its full initialization...
331+
return method.invoke(getSessionFactory(), args);
332+
}
333+
catch (InvocationTargetException ex) {
334+
throw ex.getTargetException();
335+
}
336+
}
337+
338+
private SessionFactory getSessionFactory() {
339+
try {
340+
return this.sessionFactoryFuture.get();
341+
}
342+
catch (InterruptedException ex) {
343+
throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory", ex);
344+
}
345+
catch (ExecutionException ex) {
346+
throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory", ex);
347+
}
348+
}
349+
}
350+
266351
}

spring-orm-hibernate5/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import org.apache.commons.logging.Log;
2222
import org.apache.commons.logging.LogFactory;
23-
2423
import org.hibernate.HibernateException;
2524
import org.hibernate.JDBCException;
2625
import org.hibernate.NonUniqueObjectException;
@@ -38,6 +37,7 @@
3837
import org.hibernate.TransientObjectException;
3938
import org.hibernate.UnresolvableObjectException;
4039
import org.hibernate.WrongClassException;
40+
import org.hibernate.cfg.Environment;
4141
import org.hibernate.dialect.lock.OptimisticEntityLockException;
4242
import org.hibernate.dialect.lock.PessimisticEntityLockException;
4343
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@@ -94,9 +94,13 @@ public abstract class SessionFactoryUtils {
9494
*/
9595
public static DataSource getDataSource(SessionFactory sessionFactory) {
9696
if (sessionFactory instanceof SessionFactoryImplementor) {
97+
SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory;
98+
Object dataSourceValue = sfi.getProperties().get(Environment.DATASOURCE);
99+
if (dataSourceValue instanceof DataSource) {
100+
return (DataSource) dataSourceValue;
101+
}
97102
try {
98-
ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(
99-
ConnectionProvider.class);
103+
ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class);
100104
if (cp != null) {
101105
return cp.unwrap(DataSource.class);
102106
}

spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java

+84-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,9 @@
3030
import java.util.Map;
3131
import java.util.Properties;
3232
import java.util.Set;
33+
import java.util.concurrent.Callable;
34+
import java.util.concurrent.ExecutionException;
35+
import java.util.concurrent.Future;
3336
import javax.persistence.EntityManager;
3437
import javax.persistence.EntityManagerFactory;
3538
import javax.persistence.PersistenceException;
@@ -48,6 +51,7 @@
4851
import org.springframework.beans.factory.DisposableBean;
4952
import org.springframework.beans.factory.FactoryBean;
5053
import org.springframework.beans.factory.InitializingBean;
54+
import org.springframework.core.task.AsyncTaskExecutor;
5155
import org.springframework.dao.DataAccessException;
5256
import org.springframework.dao.support.PersistenceExceptionTranslator;
5357
import org.springframework.util.Assert;
@@ -102,15 +106,21 @@ public abstract class AbstractEntityManagerFactoryBean implements
102106

103107
private JpaVendorAdapter jpaVendorAdapter;
104108

109+
private AsyncTaskExecutor bootstrapExecutor;
110+
105111
private ClassLoader beanClassLoader = getClass().getClassLoader();
106112

107113
private BeanFactory beanFactory;
108114

109115
private String beanName;
110116

111117
/** Raw EntityManagerFactory as returned by the PersistenceProvider */
112-
public EntityManagerFactory nativeEntityManagerFactory;
118+
private EntityManagerFactory nativeEntityManagerFactory;
119+
120+
/** Future for lazily initializing raw target EntityManagerFactory */
121+
private Future<EntityManagerFactory> nativeEntityManagerFactoryFuture;
113122

123+
/** Exposed client-level EntityManagerFactory proxy */
114124
private EntityManagerFactory entityManagerFactory;
115125

116126

@@ -263,6 +273,30 @@ public JpaVendorAdapter getJpaVendorAdapter() {
263273
return this.jpaVendorAdapter;
264274
}
265275

276+
/**
277+
* Specify an asynchronous executor for background bootstrapping,
278+
* e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
279+
* <p>{@code EntityManagerFactory} initialization will then switch into background
280+
* bootstrap mode, with a {@code EntityManagerFactory} proxy immediately returned for
281+
* injection purposes instead of waiting for the JPA provider's bootstrapping to complete.
282+
* However, note that the first actual call to a {@code EntityManagerFactory} method will
283+
* then block until the JPA provider's bootstrapping completed, if not ready by then.
284+
* For maximum benefit, make sure to avoid early {@code EntityManagerFactory} calls
285+
* in init methods of related beans, even for metadata introspection purposes.
286+
* @since 4.3
287+
*/
288+
public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
289+
this.bootstrapExecutor = bootstrapExecutor;
290+
}
291+
292+
/**
293+
* Return the asynchronous executor for background bootstrapping, if any.
294+
* @since 4.3
295+
*/
296+
public AsyncTaskExecutor getBootstrapExecutor() {
297+
return this.bootstrapExecutor;
298+
}
299+
266300
@Override
267301
public void setBeanClassLoader(ClassLoader classLoader) {
268302
this.beanClassLoader = classLoader;
@@ -315,20 +349,40 @@ public final void afterPropertiesSet() throws PersistenceException {
315349
}
316350
}
317351

318-
this.nativeEntityManagerFactory = createNativeEntityManagerFactory();
319-
if (this.nativeEntityManagerFactory == null) {
320-
throw new IllegalStateException(
321-
"JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
352+
if (this.bootstrapExecutor != null) {
353+
this.nativeEntityManagerFactoryFuture = this.bootstrapExecutor.submit(new Callable<EntityManagerFactory>() {
354+
@Override
355+
public EntityManagerFactory call() {
356+
return buildNativeEntityManagerFactory();
357+
}
358+
});
322359
}
323-
if (this.jpaVendorAdapter != null) {
324-
this.jpaVendorAdapter.postProcessEntityManagerFactory(this.nativeEntityManagerFactory);
360+
else {
361+
this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
325362
}
326363

327364
// Wrap the EntityManagerFactory in a factory implementing all its interfaces.
328365
// This allows interception of createEntityManager methods to return an
329366
// application-managed EntityManager proxy that automatically joins
330367
// existing transactions.
331368
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
369+
System.out.println("Returning: " + System.currentTimeMillis());
370+
}
371+
372+
private EntityManagerFactory buildNativeEntityManagerFactory() {
373+
EntityManagerFactory emf = createNativeEntityManagerFactory();
374+
if (emf == null) {
375+
throw new IllegalStateException(
376+
"JPA PersistenceProvider returned null EntityManagerFactory - check your JPA provider setup!");
377+
}
378+
if (this.jpaVendorAdapter != null) {
379+
this.jpaVendorAdapter.postProcessEntityManagerFactory(emf);
380+
}
381+
if (logger.isInfoEnabled()) {
382+
logger.info("Initialized JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
383+
}
384+
System.out.println("Done: " + System.currentTimeMillis());
385+
return emf;
332386
}
333387

334388
/**
@@ -343,9 +397,12 @@ protected EntityManagerFactory createEntityManagerFactoryProxy(EntityManagerFact
343397
if (this.entityManagerFactoryInterface != null) {
344398
ifcs.add(this.entityManagerFactoryInterface);
345399
}
346-
else {
400+
else if (emf != null) {
347401
ifcs.addAll(ClassUtils.getAllInterfacesForClassAsSet(emf.getClass(), this.beanClassLoader));
348402
}
403+
else {
404+
ifcs.add(EntityManagerFactory.class);
405+
}
349406
ifcs.add(EntityManagerFactoryInfo.class);
350407
try {
351408
return (EntityManagerFactory) Proxy.newProxyInstance(
@@ -379,13 +436,13 @@ else if (method.getName().equals("createEntityManager") && args != null && args.
379436
// JPA 2.1's createEntityManager(SynchronizationType, Map)
380437
// Redirect to plain createEntityManager and add synchronization semantics through Spring proxy
381438
EntityManager rawEntityManager = (args.length > 1 ?
382-
this.nativeEntityManagerFactory.createEntityManager((Map<?, ?>) args[1]) :
383-
this.nativeEntityManagerFactory.createEntityManager());
439+
getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
440+
getNativeEntityManagerFactory().createEntityManager());
384441
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
385442
}
386443

387444
// Standard delegation to the native factory, just post-processing EntityManager return values
388-
Object retVal = method.invoke(this.nativeEntityManagerFactory, args);
445+
Object retVal = method.invoke(getNativeEntityManagerFactory(), args);
389446
if (retVal instanceof EntityManager) {
390447
// Any other createEntityManager variant - expecting non-synchronized semantics
391448
EntityManager rawEntityManager = (EntityManager) retVal;
@@ -420,7 +477,21 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
420477

421478
@Override
422479
public EntityManagerFactory getNativeEntityManagerFactory() {
423-
return this.nativeEntityManagerFactory;
480+
if (this.nativeEntityManagerFactory != null) {
481+
return this.nativeEntityManagerFactory;
482+
}
483+
else {
484+
System.out.println("Requested: " + System.currentTimeMillis());
485+
try {
486+
return this.nativeEntityManagerFactoryFuture.get();
487+
}
488+
catch (InterruptedException ex) {
489+
throw new IllegalStateException("Interrupted during initialization of native EntityManagerFactory", ex);
490+
}
491+
catch (ExecutionException ex) {
492+
throw new IllegalStateException("Failed to asynchronously initialize native EntityManagerFactory", ex);
493+
}
494+
}
424495
}
425496

426497
@Override

0 commit comments

Comments
 (0)