Browse Source

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
pull/1870/head
Juergen Hoeller 7 years ago
parent
commit
c0d4cb55c7
  1. 30
      spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java
  2. 14
      spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java
  3. 151
      spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java
  4. 11
      spring-orm/src/test/java/org/springframework/orm/jpa/domain/Person.java
  5. 37
      spring-orm/src/test/java/org/springframework/orm/jpa/domain/PersonListener.java
  6. 19
      spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java

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

@ -33,9 +33,12 @@ import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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);
}

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

@ -46,6 +46,7 @@ import org.hibernate.context.spi.CurrentTenantIdentifierResolver; @@ -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 { @@ -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

151
spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java

@ -0,0 +1,151 @@ @@ -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);
}
}
}
}

11
spring-orm/src/test/java/org/springframework/orm/jpa/domain/Person.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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;
}
}

37
spring-orm/src/test/java/org/springframework/orm/jpa/domain/PersonListener.java

@ -0,0 +1,37 @@ @@ -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;
}
}

19
spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java

@ -23,6 +23,7 @@ import org.hibernate.query.Query; @@ -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 @@ -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 @@ -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);
}
}

Loading…
Cancel
Save