LocalSessionFactoryBuilder provides Hibernate 5.3 BeanContainer support
LocalSessionFactoryBean automatically registers its containing BeanFactory as a BeanContainer when Hibernate 5.3 or higher is on the classpath. Issue: SPR-16305
This commit is contained in:
@@ -33,9 +33,12 @@ import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
|
||||
import org.hibernate.integrator.spi.Integrator;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -46,6 +49,7 @@ import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} that creates a Hibernate {@link SessionFactory}. This is the usual
|
||||
@@ -61,7 +65,7 @@ import org.springframework.lang.Nullable;
|
||||
* @see LocalSessionFactoryBuilder
|
||||
*/
|
||||
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
|
||||
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
|
||||
implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
|
||||
|
||||
@Nullable
|
||||
private DataSource dataSource;
|
||||
@@ -131,6 +135,9 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
|
||||
@Nullable
|
||||
private ResourcePatternResolver resourcePatternResolver;
|
||||
|
||||
@Nullable
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
@Nullable
|
||||
private Configuration configuration;
|
||||
|
||||
@@ -433,6 +440,23 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
|
||||
return this.resourcePatternResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the containing {@link BeanFactory}, registering corresponding Hibernate
|
||||
* {@link org.hibernate.resource.beans.container.spi.BeanContainer} integration for
|
||||
* it if possible. This requires a Spring {@link ConfigurableListableBeanFactory}
|
||||
* and Hibernate 5.3 or higher on the classpath.
|
||||
* @since 5.1
|
||||
* @see LocalSessionFactoryBuilder#setBeanContainer
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
if (beanFactory instanceof ConfigurableListableBeanFactory &&
|
||||
ClassUtils.isPresent("org.hibernate.resource.beans.container.spi.BeanContainer",
|
||||
getClass().getClassLoader())) {
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws IOException {
|
||||
@@ -508,6 +532,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
|
||||
sfb.setJtaTransactionManager(this.jtaTransactionManager);
|
||||
}
|
||||
|
||||
if (this.beanFactory != null) {
|
||||
sfb.setBeanContainer(this.beanFactory);
|
||||
}
|
||||
|
||||
if (this.multiTenantConnectionProvider != null) {
|
||||
sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
|
||||
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.InfrastructureProxy;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@@ -228,6 +229,19 @@ public class LocalSessionFactoryBuilder extends Configuration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a Hibernate {@link org.hibernate.resource.beans.container.spi.BeanContainer}
|
||||
* for the given Spring {@link ConfigurableListableBeanFactory}.
|
||||
* <p>Note: Bean container integration requires Hibernate 5.3 or higher.
|
||||
* It enables autowiring of Hibernate attribute converters and entity listeners.
|
||||
* @since 5.1
|
||||
* @see AvailableSettings#BEAN_CONTAINER
|
||||
*/
|
||||
public LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) {
|
||||
getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
|
||||
* @since 4.3
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.orm.hibernate5;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.resource.beans.container.spi.BeanContainer;
|
||||
import org.hibernate.resource.beans.container.spi.ContainedBean;
|
||||
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Spring's implementation of Hibernate 5.3's {@link BeanContainer} SPI,
|
||||
* delegating to a Spring {@link ConfigurableListableBeanFactory}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.1
|
||||
*/
|
||||
final class SpringBeanContainer implements BeanContainer {
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
|
||||
public SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <B> ContainedBean<B> getBean(
|
||||
Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
|
||||
|
||||
SpringContainedBean<?> bean;
|
||||
if (lifecycleOptions.canUseCachedReferences()) {
|
||||
bean = this.beanCache.get(beanType);
|
||||
if (bean == null) {
|
||||
bean = createBean(beanType, lifecycleOptions);
|
||||
this.beanCache.put(beanType, bean);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bean = createBean(beanType, lifecycleOptions);
|
||||
}
|
||||
return (SpringContainedBean<B>) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <B> ContainedBean<B> getBean(
|
||||
String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
|
||||
|
||||
if (!this.beanFactory.containsBean(name)) {
|
||||
return getBean(beanType, lifecycleOptions, fallbackProducer);
|
||||
}
|
||||
|
||||
SpringContainedBean<?> bean;
|
||||
if (lifecycleOptions.canUseCachedReferences()) {
|
||||
bean = this.beanCache.get(name);
|
||||
if (bean == null) {
|
||||
bean = createBean(name, beanType, lifecycleOptions);
|
||||
this.beanCache.put(name, bean);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bean = createBean(name, beanType, lifecycleOptions);
|
||||
}
|
||||
return (SpringContainedBean<B>) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary);
|
||||
this.beanCache.clear();
|
||||
}
|
||||
|
||||
|
||||
private SpringContainedBean<?> createBean(Class<?> beanType, LifecycleOptions lifecycleOptions) {
|
||||
if (lifecycleOptions.useJpaCompliantCreation()) {
|
||||
return new SpringContainedBean<>(
|
||||
this.beanFactory.createBean(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false),
|
||||
this.beanFactory::destroyBean);
|
||||
}
|
||||
else {
|
||||
return new SpringContainedBean<>(this.beanFactory.getBean(beanType));
|
||||
}
|
||||
}
|
||||
|
||||
private SpringContainedBean<?> createBean(String name, Class<?> beanType, LifecycleOptions lifecycleOptions) {
|
||||
if (lifecycleOptions.useJpaCompliantCreation()) {
|
||||
Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
|
||||
this.beanFactory.applyBeanPropertyValues(bean, name);
|
||||
this.beanFactory.initializeBean(bean, name);
|
||||
return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance));
|
||||
}
|
||||
else {
|
||||
return new SpringContainedBean<>(this.beanFactory.getBean(name, beanType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class SpringContainedBean<B> implements ContainedBean<B> {
|
||||
|
||||
private final B beanInstance;
|
||||
|
||||
@Nullable
|
||||
private Consumer<B> destructionCallback;
|
||||
|
||||
public SpringContainedBean(B beanInstance) {
|
||||
this.beanInstance = beanInstance;
|
||||
}
|
||||
|
||||
public SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) {
|
||||
this.beanInstance = beanInstance;
|
||||
this.destructionCallback = destructionCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B getBeanInstance() {
|
||||
return this.beanInstance;
|
||||
}
|
||||
|
||||
public void destroyIfNecessary() {
|
||||
if (this.destructionCallback != null) {
|
||||
this.destructionCallback.accept(this.beanInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.orm.jpa.domain;
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
@@ -26,6 +27,7 @@ import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
|
||||
/**
|
||||
@@ -34,6 +36,7 @@ import org.springframework.tests.sample.beans.TestBean;
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(PersonListener.class)
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
@@ -52,6 +55,8 @@ public class Person {
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String last_name;
|
||||
|
||||
public transient ApplicationContext postLoaded;
|
||||
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
@@ -91,8 +96,8 @@ public class Person {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + ":(" + hashCode() + ") id=" + id + "; firstName=" + first_name + "; lastName="
|
||||
+ last_name + "; testBean=" + testBean;
|
||||
return getClass().getName() + ":(" + hashCode() + ") id=" + id + "; firstName=" + first_name +
|
||||
"; lastName=" + last_name + "; testBean=" + testBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.orm.jpa.domain;
|
||||
|
||||
import javax.persistence.PostLoad;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class PersonListener {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@PostLoad
|
||||
public void postLoad(Person person) {
|
||||
person.postLoaded = this.context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.hibernate.query.Query;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.orm.jpa.AbstractContainerEntityManagerFactoryIntegrationTests;
|
||||
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
@@ -40,6 +41,9 @@ public class HibernateNativeEntityManagerFactoryIntegrationTests extends Abstrac
|
||||
@Autowired
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
|
||||
@Override
|
||||
protected String[] getConfigLocations() {
|
||||
@@ -53,18 +57,29 @@ public class HibernateNativeEntityManagerFactoryIntegrationTests extends Abstrac
|
||||
assertFalse("Must not have introduced config interface", entityManagerFactory instanceof EntityManagerFactoryInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testEntityListener() {
|
||||
String firstName = "Tony";
|
||||
insertPerson(firstName);
|
||||
|
||||
List<Person> people = sharedEntityManager.createQuery("select p from Person as p").getResultList();
|
||||
assertEquals(1, people.size());
|
||||
assertEquals(firstName, people.get(0).getFirstName());
|
||||
assertSame(applicationContext, people.get(0).postLoaded);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testCurrentSession() {
|
||||
// Add with JDBC
|
||||
String firstName = "Tony";
|
||||
insertPerson(firstName);
|
||||
|
||||
Query q = sessionFactory.getCurrentSession().createQuery("select p from Person as p");
|
||||
List<Person> people = q.getResultList();
|
||||
|
||||
assertEquals(1, people.size());
|
||||
assertEquals(firstName, people.get(0).getFirstName());
|
||||
assertSame(applicationContext, people.get(0).postLoaded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user