Skip to content

Commit c0d4cb5

Browse files
committed
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
1 parent 5dc8b5d commit c0d4cb5

File tree

6 files changed

+256
-6
lines changed

6 files changed

+256
-6
lines changed

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@
3333
import org.hibernate.integrator.spi.Integrator;
3434
import org.hibernate.service.ServiceRegistry;
3535

36+
import org.springframework.beans.factory.BeanFactory;
37+
import org.springframework.beans.factory.BeanFactoryAware;
3638
import org.springframework.beans.factory.DisposableBean;
3739
import org.springframework.beans.factory.FactoryBean;
3840
import org.springframework.beans.factory.InitializingBean;
41+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3942
import org.springframework.context.ResourceLoaderAware;
4043
import org.springframework.core.io.ClassPathResource;
4144
import org.springframework.core.io.Resource;
@@ -46,6 +49,7 @@
4649
import org.springframework.core.task.AsyncTaskExecutor;
4750
import org.springframework.core.type.filter.TypeFilter;
4851
import org.springframework.lang.Nullable;
52+
import org.springframework.util.ClassUtils;
4953

5054
/**
5155
* {@link FactoryBean} that creates a Hibernate {@link SessionFactory}. This is the usual
@@ -61,7 +65,7 @@
6165
* @see LocalSessionFactoryBuilder
6266
*/
6367
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
64-
implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
68+
implements FactoryBean<SessionFactory>, ResourceLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
6569

6670
@Nullable
6771
private DataSource dataSource;
@@ -131,6 +135,9 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator
131135
@Nullable
132136
private ResourcePatternResolver resourcePatternResolver;
133137

138+
@Nullable
139+
private ConfigurableListableBeanFactory beanFactory;
140+
134141
@Nullable
135142
private Configuration configuration;
136143

@@ -433,6 +440,23 @@ public ResourceLoader getResourceLoader() {
433440
return this.resourcePatternResolver;
434441
}
435442

443+
/**
444+
* Accept the containing {@link BeanFactory}, registering corresponding Hibernate
445+
* {@link org.hibernate.resource.beans.container.spi.BeanContainer} integration for
446+
* it if possible. This requires a Spring {@link ConfigurableListableBeanFactory}
447+
* and Hibernate 5.3 or higher on the classpath.
448+
* @since 5.1
449+
* @see LocalSessionFactoryBuilder#setBeanContainer
450+
*/
451+
@Override
452+
public void setBeanFactory(BeanFactory beanFactory) {
453+
if (beanFactory instanceof ConfigurableListableBeanFactory &&
454+
ClassUtils.isPresent("org.hibernate.resource.beans.container.spi.BeanContainer",
455+
getClass().getClassLoader())) {
456+
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
457+
}
458+
}
459+
436460

437461
@Override
438462
public void afterPropertiesSet() throws IOException {
@@ -508,6 +532,10 @@ public void afterPropertiesSet() throws IOException {
508532
sfb.setJtaTransactionManager(this.jtaTransactionManager);
509533
}
510534

535+
if (this.beanFactory != null) {
536+
sfb.setBeanContainer(this.beanFactory);
537+
}
538+
511539
if (this.multiTenantConnectionProvider != null) {
512540
sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
513541
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
4747
import org.hibernate.engine.spi.SessionFactoryImplementor;
4848

49+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4950
import org.springframework.core.InfrastructureProxy;
5051
import org.springframework.core.io.Resource;
5152
import org.springframework.core.io.ResourceLoader;
@@ -228,6 +229,19 @@ else if (jtaTransactionManager instanceof TransactionManager) {
228229
return this;
229230
}
230231

232+
/**
233+
* Set a Hibernate {@link org.hibernate.resource.beans.container.spi.BeanContainer}
234+
* for the given Spring {@link ConfigurableListableBeanFactory}.
235+
* <p>Note: Bean container integration requires Hibernate 5.3 or higher.
236+
* It enables autowiring of Hibernate attribute converters and entity listeners.
237+
* @since 5.1
238+
* @see AvailableSettings#BEAN_CONTAINER
239+
*/
240+
public LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) {
241+
getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
242+
return this;
243+
}
244+
231245
/**
232246
* Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
233247
* @since 4.3
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.hibernate5;
18+
19+
import java.util.Map;
20+
import java.util.function.Consumer;
21+
22+
import org.hibernate.resource.beans.container.spi.BeanContainer;
23+
import org.hibernate.resource.beans.container.spi.ContainedBean;
24+
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
25+
26+
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
27+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
28+
import org.springframework.lang.Nullable;
29+
import org.springframework.util.ConcurrentReferenceHashMap;
30+
31+
/**
32+
* Spring's implementation of Hibernate 5.3's {@link BeanContainer} SPI,
33+
* delegating to a Spring {@link ConfigurableListableBeanFactory}.
34+
*
35+
* @author Juergen Hoeller
36+
* @since 5.1
37+
*/
38+
final class SpringBeanContainer implements BeanContainer {
39+
40+
private final ConfigurableListableBeanFactory beanFactory;
41+
42+
private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>();
43+
44+
45+
public SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) {
46+
this.beanFactory = beanFactory;
47+
}
48+
49+
50+
@Override
51+
@SuppressWarnings("unchecked")
52+
public <B> ContainedBean<B> getBean(
53+
Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
54+
55+
SpringContainedBean<?> bean;
56+
if (lifecycleOptions.canUseCachedReferences()) {
57+
bean = this.beanCache.get(beanType);
58+
if (bean == null) {
59+
bean = createBean(beanType, lifecycleOptions);
60+
this.beanCache.put(beanType, bean);
61+
}
62+
}
63+
else {
64+
bean = createBean(beanType, lifecycleOptions);
65+
}
66+
return (SpringContainedBean<B>) bean;
67+
}
68+
69+
@Override
70+
@SuppressWarnings("unchecked")
71+
public <B> ContainedBean<B> getBean(
72+
String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
73+
74+
if (!this.beanFactory.containsBean(name)) {
75+
return getBean(beanType, lifecycleOptions, fallbackProducer);
76+
}
77+
78+
SpringContainedBean<?> bean;
79+
if (lifecycleOptions.canUseCachedReferences()) {
80+
bean = this.beanCache.get(name);
81+
if (bean == null) {
82+
bean = createBean(name, beanType, lifecycleOptions);
83+
this.beanCache.put(name, bean);
84+
}
85+
}
86+
else {
87+
bean = createBean(name, beanType, lifecycleOptions);
88+
}
89+
return (SpringContainedBean<B>) bean;
90+
}
91+
92+
@Override
93+
public void stop() {
94+
this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary);
95+
this.beanCache.clear();
96+
}
97+
98+
99+
private SpringContainedBean<?> createBean(Class<?> beanType, LifecycleOptions lifecycleOptions) {
100+
if (lifecycleOptions.useJpaCompliantCreation()) {
101+
return new SpringContainedBean<>(
102+
this.beanFactory.createBean(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false),
103+
this.beanFactory::destroyBean);
104+
}
105+
else {
106+
return new SpringContainedBean<>(this.beanFactory.getBean(beanType));
107+
}
108+
}
109+
110+
private SpringContainedBean<?> createBean(String name, Class<?> beanType, LifecycleOptions lifecycleOptions) {
111+
if (lifecycleOptions.useJpaCompliantCreation()) {
112+
Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
113+
this.beanFactory.applyBeanPropertyValues(bean, name);
114+
this.beanFactory.initializeBean(bean, name);
115+
return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance));
116+
}
117+
else {
118+
return new SpringContainedBean<>(this.beanFactory.getBean(name, beanType));
119+
}
120+
}
121+
122+
123+
private static final class SpringContainedBean<B> implements ContainedBean<B> {
124+
125+
private final B beanInstance;
126+
127+
@Nullable
128+
private Consumer<B> destructionCallback;
129+
130+
public SpringContainedBean(B beanInstance) {
131+
this.beanInstance = beanInstance;
132+
}
133+
134+
public SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) {
135+
this.beanInstance = beanInstance;
136+
this.destructionCallback = destructionCallback;
137+
}
138+
139+
@Override
140+
public B getBeanInstance() {
141+
return this.beanInstance;
142+
}
143+
144+
public void destroyIfNecessary() {
145+
if (this.destructionCallback != null) {
146+
this.destructionCallback.accept(this.beanInstance);
147+
}
148+
}
149+
}
150+
151+
}

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

Lines changed: 8 additions & 3 deletions
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-2018 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.
@@ -19,13 +19,15 @@
1919
import javax.persistence.Basic;
2020
import javax.persistence.CascadeType;
2121
import javax.persistence.Entity;
22+
import javax.persistence.EntityListeners;
2223
import javax.persistence.FetchType;
2324
import javax.persistence.GeneratedValue;
2425
import javax.persistence.GenerationType;
2526
import javax.persistence.Id;
2627
import javax.persistence.JoinColumn;
2728
import javax.persistence.OneToOne;
2829

30+
import org.springframework.context.ApplicationContext;
2931
import org.springframework.tests.sample.beans.TestBean;
3032

3133
/**
@@ -34,6 +36,7 @@
3436
* @author Rod Johnson
3537
*/
3638
@Entity
39+
@EntityListeners(PersonListener.class)
3740
public class Person {
3841

3942
@Id
@@ -52,6 +55,8 @@ public class Person {
5255
@Basic(fetch = FetchType.LAZY)
5356
private String last_name;
5457

58+
public transient ApplicationContext postLoaded;
59+
5560

5661
public Integer getId() {
5762
return id;
@@ -91,8 +96,8 @@ public DriversLicense getDriversLicense() {
9196

9297
@Override
9398
public String toString() {
94-
return getClass().getName() + ":(" + hashCode() + ") id=" + id + "; firstName=" + first_name + "; lastName="
95-
+ last_name + "; testBean=" + testBean;
99+
return getClass().getName() + ":(" + hashCode() + ") id=" + id + "; firstName=" + first_name +
100+
"; lastName=" + last_name + "; testBean=" + testBean;
96101
}
97102

98103
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.jpa.domain;
18+
19+
import javax.persistence.PostLoad;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.context.ApplicationContext;
23+
24+
/**
25+
* @author Juergen Hoeller
26+
*/
27+
public class PersonListener {
28+
29+
@Autowired
30+
ApplicationContext context;
31+
32+
@PostLoad
33+
public void postLoad(Person person) {
34+
person.postLoaded = this.context;
35+
}
36+
37+
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.Test;
2424

2525
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.context.ApplicationContext;
2627
import org.springframework.orm.jpa.AbstractContainerEntityManagerFactoryIntegrationTests;
2728
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
2829
import org.springframework.orm.jpa.domain.Person;
@@ -40,6 +41,9 @@ public class HibernateNativeEntityManagerFactoryIntegrationTests extends Abstrac
4041
@Autowired
4142
private SessionFactory sessionFactory;
4243

44+
@Autowired
45+
private ApplicationContext applicationContext;
46+
4347

4448
@Override
4549
protected String[] getConfigLocations() {
@@ -53,18 +57,29 @@ public void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() {
5357
assertFalse("Must not have introduced config interface", entityManagerFactory instanceof EntityManagerFactoryInfo);
5458
}
5559

60+
@Test
61+
@SuppressWarnings("unchecked")
62+
public void testEntityListener() {
63+
String firstName = "Tony";
64+
insertPerson(firstName);
65+
66+
List<Person> people = sharedEntityManager.createQuery("select p from Person as p").getResultList();
67+
assertEquals(1, people.size());
68+
assertEquals(firstName, people.get(0).getFirstName());
69+
assertSame(applicationContext, people.get(0).postLoaded);
70+
}
71+
5672
@Test
5773
@SuppressWarnings("unchecked")
5874
public void testCurrentSession() {
59-
// Add with JDBC
6075
String firstName = "Tony";
6176
insertPerson(firstName);
6277

6378
Query q = sessionFactory.getCurrentSession().createQuery("select p from Person as p");
6479
List<Person> people = q.getResultList();
65-
6680
assertEquals(1, people.size());
6781
assertEquals(firstName, people.get(0).getFirstName());
82+
assertSame(applicationContext, people.get(0).postLoaded);
6883
}
6984

7085
}

0 commit comments

Comments
 (0)