Introduce (Annotation)SessionFactoryBuilder types

Large refactoring of existing *SessionFactoryBean hierarchy designed to
support configuration of Hibernate SessionFactory objects within
@Configuration class @Bean methods without forcing use of a
FactoryBean type, which is generally discouraged due to awkwardness
of programming model and lifecycle issues.  Refactored and new types
include:

    * Removal of AbstractSessionFactoryBean, replacing it with
      SessionFactoryBeanSupport abstract base class

    * Introduction of SessionFactoryBuilder and
      AnnotationSessionFactoryBuilder types, both direct subclasses of
      SessionFactoryBuilderSupport. These types are intended for direct
      use within @Bean methods. They expose method-chainable set*
      methods allowing for concise and convenient use. See JavaDoc
      on both types for usage examples.

    * LocalSessionFactoryBean and AnnotationSessionFactoryBean types are
      now subclasses, respectively, of the *Builder types above.

LSFB and ASFB backward-compatibility has been maintained almost
entirely. The one exception is that there is no longer a protected
convertHibernateAccessException() method available in the hierarchy.
This method was not likely often used anyway and has been replaced
by the new (and public) setPersistenceExceptionTranslator() which
accepts instances of type HibernateExceptionTranslator as introduced in
SPR-8076.

LSFB and ASFB setter method signatures have changed. They no longer
return void in standard JavaBeans style but rather, and due to extending
the *Builder types above, return the 'this' reference. This posed a
problem because the Spring container has to date been unable to detect
and provide dependency injection against non-void returning setter
methods. This limitation was due to the way that the default JavaBeans
Introspector class and its getBeanInfo() method works, and prompted the
introduction and intergration of ExtendedBeanInfo, already completed in
SPR-8079. So have no concern if you notice this signature change - it
all works.

Certain deprecations have been made:

    * All LSFB/ASFB methods related to Hibernate's CacheProvider SPI,
      reflecting its deprecation in Hibernate 3.3 in favor of the new
      RegionFactory SPI. Note these methods have been preserved only
      on the FactoryBean types. The new *SessionFactoryBuilder
      supertypes do not expose CacheProvider services at all.

    * All protected LSFB/ASFB methods that accept a Hibernate
      Configuration parameter, such as newSessionFactory(Configuration),
      postProcessMappings(Configuration) and
      postProcessConfiguration(Configuation), in favor of no-arg methods
      with the same names. Due to the nature of the hierarchy
      refactoring mentioned above, the Configuration instance is always
      available when these methods are called, thus no need to pass it
      in as a parameter.

In the process, our approach to automatic detection of Hibernate dialect
has been improved (it was in fact broken before). Thanks to James
Roper for his suggestion in SPR-7936 as to how to fix this.

See HibernateSessionFactoryConfigurationTests as a starting point for
understanding the new builder-style approach to SessionFactory creation.
Note especially use of the SessionFactoryBuilder#doWithConfiguration
method which allows for direct programmatic configuration of the Native
Hibernate (Annotation)Configuration API.

As a final note, AnnotationConfiguration has been deprecated in
Hibernate 3.6, and great pains have been taken to ensure that users
of any supported Hibernate version (3.2 -> 3.6) will never need to
(a) cast from Configuration to AnnotationConfiguration or (b)
experience deprecation warnings due to being forced to use the
AnnotationConfiguration API. Explore the JavaDoc around the
doWithConfiguration() method and HibernateConfigurationCallback type
for complete details.

Issue: SPR-8066, SPR-7936, SPR-8076, SPR-8098
This commit is contained in:
Chris Beams
2011-03-31 12:29:12 +00:00
parent b0b1116f4b
commit f5768fe00b
40 changed files with 3073 additions and 1780 deletions

View File

@@ -0,0 +1,352 @@
/*
* Copyright 2002-2011 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.hibernate3;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.util.List;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.classic.Session;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBuilder;
import org.springframework.orm.hibernate3.scannable.Foo;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.config.TxAnnotationDriven;
/**
* Integration tests for configuring Hibernate SessionFactory types
* without using a FactoryBean, e.g., within a {@link Configuration} class.
*
* @author Chris Beams
* @since 3.1
*/
public class HibernateSessionFactoryConfigurationTests {
@Test
public void usingLocalSessionFactoryBean() {
saveAndRetriveEntity(LocalSessionFactoryBeanXmlConfig.class);
}
@Test
public void usingAnnotationSessionFactoryBean() {
saveAndRetriveEntity(AnnotationSessionFactoryBeanXmlConfig.class);
}
@Ignore @Test
public void usingNativeHibernateConfiguration() {
saveAndRetriveEntity(NativeHibernateConfig.class);
}
@Test
public void usingSessionFactoryBuilder_withConfigurationCallback() {
saveAndRetriveEntity(SessionFactoryConfig_withConfigurationCallback.class);
}
@Test
public void usingAnnotationSessionFactoryBuilder() {
saveAndRetriveEntity(AnnotationSessionFactoryConfig.class);
}
@Test
public void usingAnnotationSessionFactoryBuilder_withConfigurationCallback() {
saveAndRetriveEntity(AnnotationSessionFactoryConfig_withConfigurationCallback.class);
}
@Test
public void usingAnnotationSessionFactoryBuilder_withPackagesToScan() {
saveAndRetriveEntity(AnnotationSessionFactoryConfig_withPackagesToScan.class);
}
@Test
public void usingAnnotationSessionFactoryBuilder_withAnnotatedClasses() {
saveAndRetriveEntity(AnnotationSessionFactoryConfig_withAnnotatedClasses.class);
}
@Test(expected=DataAccessException.class)
public void exceptionTranslation_withLocalSessionFactoryBean() {
causeException(LocalSessionFactoryBeanXmlConfig.class, RepositoryConfig.class);
}
@Test(expected=DataAccessException.class)
public void exceptionTranslation_withSessionFactoryBuilder() {
causeException(SessionFactoryConfig_withConfigurationCallback.class,
RepositoryConfig.class);
}
@Test
public void usingSessionFactoryBuilder_withCustomConfigurationClass() {
saveAndRetriveEntity(SessionFactoryConfig_withCustomConfigurationClass.class);
}
@Test(expected=IllegalStateException.class)
public void usingSessionFactoryBuilder_withLateCustomConfigurationClass() throws Throwable {
try {
saveAndRetriveEntity(SessionFactoryConfig_withLateCustomConfigurationClass.class);
} catch (BeanCreationException ex) {
Throwable cause = ex.getRootCause();
assertThat(cause.getMessage().startsWith("setConfigurationClass() must be called before"), is(true));
throw cause;
}
}
private void saveAndRetriveEntity(Class<?> configClass) {
SessionFactory sessionFactory = new AnnotationConfigApplicationContext(configClass).getBean(SessionFactory.class);
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Foo foo = new Foo();
foo.setName("f1");
session.save(foo);
Foo f1 = (Foo) session.createQuery("from Foo where name = 'f1'").uniqueResult();
assertThat("No foo with name='f1' found!", f1, notNullValue());
assertThat(f1.getName(), is("f1"));
tx.rollback();
}
private void causeException(Class<?>... configClasses) {
FooRepository fooRepository = new AnnotationConfigApplicationContext(configClasses).getBean(FooRepository.class);
fooRepository.findAll(); // will throw
}
@Configuration
static class DataConfig {
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.build();
}
}
@Configuration
@ImportResource("org/springframework/orm/hibernate3/LocalSessionFactoryBeanXmlConfig-context.xml")
static class LocalSessionFactoryBeanXmlConfig extends DataConfig {
}
interface FooRepository {
List<Foo> findAll();
}
@Repository
public static class HibernateFooRepository implements FooRepository {
private final SessionFactory sessionFactory;
public HibernateFooRepository(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Transactional
@SuppressWarnings("unchecked")
public List<Foo> findAll() {
return sessionFactory.getCurrentSession().createQuery("from Bogus").list();
}
}
@Configuration
@Import(TxConfig.class)
static class RepositoryConfig {
@Inject SessionFactory sessionFactory;
@Bean
FooRepository fooRepository() {
return new HibernateFooRepository(sessionFactory);
}
@Bean
PlatformTransactionManager txManager() {
return new HibernateTransactionManager(sessionFactory);
}
@Bean
PersistenceExceptionTranslationPostProcessor exceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
@Bean
PersistenceExceptionTranslator exceptionTranslator() {
return new HibernateExceptionTranslator();
}
}
@FeatureConfiguration
static class TxConfig {
@Feature
TxAnnotationDriven tx(PlatformTransactionManager txManager) {
return new TxAnnotationDriven(txManager);
}
}
@Configuration
@ImportResource("org/springframework/orm/hibernate3/AnnotationSessionFactoryBeanXmlConfig-context.xml")
static class AnnotationSessionFactoryBeanXmlConfig extends DataConfig {
}
@Configuration
static class NativeHibernateConfig {
@Bean
SessionFactory sessionFactory() {
org.hibernate.cfg.Configuration cfg = new AnnotationConfiguration();
return cfg.buildSessionFactory();
}
}
@Configuration
static class AnnotationSessionFactoryConfig extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new AnnotationSessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.doWithConfiguration(new HibernateConfigurationCallback<AnnotationConfiguration>() {
public void configure(AnnotationConfiguration configuration) {
configuration.addAnnotatedClass(Foo.class);
}
})
.buildSessionFactory();
}
}
@Configuration
static class AnnotationSessionFactoryConfig_withPackagesToScan extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new AnnotationSessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.setPackagesToScan(Foo.class.getPackage().getName())
.buildSessionFactory();
}
}
@Configuration
static class AnnotationSessionFactoryConfig_withAnnotatedClasses extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new AnnotationSessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.setAnnotatedClasses(Foo.class, Foo.class)
.buildSessionFactory();
}
}
@Configuration
static class AnnotationSessionFactoryConfig_withConfigurationCallback extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new AnnotationSessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.doWithConfiguration(new HibernateConfigurationCallback<AnnotationConfiguration>() {
public void configure(AnnotationConfiguration configuration) throws Exception {
configuration.addAnnotatedClass(Foo.class);
}
})
.buildSessionFactory();
}
}
@Configuration
static class SessionFactoryConfig_withConfigurationCallback extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new SessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.doWithConfiguration(new HibernateConfigurationCallback<org.hibernate.cfg.Configuration>() {
public void configure(org.hibernate.cfg.Configuration configuration) throws Exception {
configuration.addFile(new File(this.getClass().getClassLoader().getResource("org/springframework/orm/hibernate3/scannable/FooMapping.hbm.xml").toURI()));
}
})
.buildSessionFactory();
}
}
@Configuration
static class SessionFactoryConfig_withCustomConfigurationClass extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
SessionFactoryBuilder sfb = new SessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.setConfigurationClass(CustomHibernateConfiguration.class)
.doWithConfiguration(new HibernateConfigurationCallback<org.hibernate.cfg.Configuration>() {
public void configure(org.hibernate.cfg.Configuration configuration) throws Exception {
configuration.addFile(new File(this.getClass().getClassLoader().getResource("org/springframework/orm/hibernate3/scannable/FooMapping.hbm.xml").toURI()));
}
});
assertThat(sfb.getConfiguration(), instanceOf(CustomHibernateConfiguration.class));
return sfb.buildSessionFactory();
}
}
@Configuration
static class SessionFactoryConfig_withLateCustomConfigurationClass extends DataConfig {
@Bean
SessionFactory sessionFactory() throws Exception {
return new SessionFactoryBuilder(dataSource())
.setSchemaUpdate(true)
.doWithConfiguration(new HibernateConfigurationCallback<org.hibernate.cfg.Configuration>() {
public void configure(org.hibernate.cfg.Configuration configuration) throws Exception {
configuration.addFile(new File(this.getClass().getClassLoader().getResource("org/springframework/orm/hibernate3/scannable/FooMapping.hbm.xml").toURI()));
}
})
.setConfigurationClass(CustomHibernateConfiguration.class)
.buildSessionFactory();
}
}
@SuppressWarnings("serial")
static class CustomHibernateConfiguration extends org.hibernate.cfg.Configuration {
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2011 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.hibernate3.scannable;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Foo {
@SuppressWarnings("unused")
@Id private int id;
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses" value="org.springframework.orm.hibernate3.scannable.Foo"/>
<property name="dataSource" ref="dataSource"/>
<property name="schemaUpdate" value="true"/>
</bean>
</beans>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!--<property name="annotatedClasses" value="org.springframework.orm.hibernate3.scannable.Foo"/>-->
<property name="mappingLocations" value="classpath:org/springframework/orm/hibernate3/scannable/FooMapping.hbm.xml"/>
<property name="dataSource" ref="dataSource"/>
<property name="schemaUpdate" value="true"/>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.springframework.orm.hibernate3.scannable.Foo" table="foo">
<id name="id" column="id" access="field"/>
<property name="name" column="name" />
</class>
</hibernate-mapping>