- /*
- * Copyright 2002-2004 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.hibernate;
- import java.io.File;
- import java.io.IOException;
- import java.sql.Connection;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.util.Properties;
- import javax.sql.DataSource;
- import javax.transaction.TransactionManager;
- import net.sf.hibernate.HibernateException;
- import net.sf.hibernate.Interceptor;
- import net.sf.hibernate.Session;
- import net.sf.hibernate.SessionFactory;
- import net.sf.hibernate.cfg.Configuration;
- import net.sf.hibernate.cfg.Environment;
- import net.sf.hibernate.cfg.NamingStrategy;
- import net.sf.hibernate.dialect.Dialect;
- import net.sf.hibernate.tool.hbm2ddl.DatabaseMetadata;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.beans.factory.DisposableBean;
- import org.springframework.beans.factory.FactoryBean;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.core.io.ClassPathResource;
- import org.springframework.core.io.Resource;
- import org.springframework.dao.DataAccessException;
- import org.springframework.jdbc.support.JdbcUtils;
- import org.springframework.jdbc.support.lob.LobHandler;
- /**
- * FactoryBean that creates a local Hibernate SessionFactory instance.
- * Behaves like a SessionFactory instance when used as bean reference, e.g.
- * for HibernateTemplate's "sessionFactory" property. Note that switching
- * to JndiObjectFactoryBean is just a matter of configuration!
- *
- * <p>The typical usage will be to register this as singleton factory
- * (for a certain underlying JDBC DataSource) in an application context,
- * and give bean references to application services that need it.
- *
- * <p>Configuration settings can either be read from a Hibernate XML file,
- * specified as "configLocation", or completely via this class. A typical
- * local configuration consists of one or more "mappingResources", various
- * "hibernateProperties" (not strictly necessary), and a "dataSource" that the
- * SessionFactory should use. The latter can also be specified via Hibernate
- * properties, but "dataSource" supports any Spring-configured DataSource,
- * instead of relying on Hibernate's own connection providers.
- *
- * <p>This SessionFactory handling strategy is appropriate for most types of
- * applications, from Hibernate-only single database apps to ones that need
- * distributed transactions. Either HibernateTransactionManager or
- * JtaTransactionManager can be used for transaction demarcation, the latter
- * only being necessary for transactions that span multiple databases.
- *
- * <p>Registering a SessionFactory with JNDI is only advisable when using
- * Hibernate's JCA Connector, i.e. when the application server cares for
- * initialization. Else, portability is rather limited: Manual JNDI binding
- * isn't supported by some application servers (e.g. Tomcat). Unfortunately,
- * JCA has drawbacks too: Its setup is container-specific and can be tedious.
- *
- * <p>Note that the JCA Connector's sole major strength is its seamless
- * cooperation with EJB containers and JTA services. If you do not use EJB
- * and initiate your JTA transactions via Spring's JtaTransactionManager,
- * you can get all benefits including distributed transactions and proper
- * transactional JVM-level caching with local SessionFactory setup too -
- * without any configuration hassle like container-specific setup.
- *
- * <p>Note: Spring's Hibernate support requires Hibernate 2.1 (as of Spring 1.0).
- *
- * @author Juergen Hoeller
- * @since 05.05.2003
- * @see HibernateTemplate#setSessionFactory
- * @see HibernateTransactionManager#setSessionFactory
- * @see org.springframework.jndi.JndiObjectFactoryBean
- */
- public class LocalSessionFactoryBean implements FactoryBean, InitializingBean, DisposableBean {
- private static ThreadLocal configTimeLobHandlerHolder = new ThreadLocal();
- /**
- * Return the LobHandler for the currently configured Hibernate SessionFactory,
- * to be used by Type implementations like ClobStringType.
- * <p>This instance will be set before initialization of the corresponding
- * SessionFactory, and reset immediately afterwards. It is thus only available
- * in constructors of UserType implementations.
- * @see #setLobHandler
- * @see org.springframework.orm.hibernate.support.ClobStringType
- * @see net.sf.hibernate.type.Type
- */
- public static LobHandler getConfigTimeLobHandler() {
- return (LobHandler) configTimeLobHandlerHolder.get();
- }
- protected final Log logger = LogFactory.getLog(getClass());
- private Resource configLocation;
- private Resource[] mappingLocations;
- private Resource[] mappingJarLocations;
- private Resource[] mappingDirectoryLocations;
- private Properties hibernateProperties;
- private DataSource dataSource;
- private TransactionManager jtaTransactionManager;
- private LobHandler lobHandler;
- private Interceptor entityInterceptor;
- private NamingStrategy namingStrategy;
- private boolean schemaUpdate = false;
- private Configuration configuration;
- private SessionFactory sessionFactory;
- /**
- * Set the location of the Hibernate XML config file, for example as
- * classpath resource "classpath:hibernate.cfg.xml".
- * <p>Note: Can be omitted when all necessary properties and mapping
- * resources are specified locally via this bean.
- * @see net.sf.hibernate.cfg.Configuration#configure(java.net.URL)
- */
- public void setConfigLocation(Resource configLocation) {
- this.configLocation = configLocation;
- }
- /**
- * Set Hibernate mapping resources to be found in the class path,
- * like "example.hbm.xml" or "mypackage/example.hbm.xml".
- * Analogous to mapping entries in a Hibernate XML config file.
- * Alternative to the more generic setMappingLocations method.
- * <p>Can be used to add to mappings from a Hibernate XML config file,
- * or to specify all mappings locally.
- * @see #setMappingLocations
- * @see net.sf.hibernate.cfg.Configuration#addResource
- */
- public void setMappingResources(String[] mappingResources) {
- this.mappingLocations = new Resource[mappingResources.length];
- for (int i = 0; i < mappingResources.length; i++) {
- this.mappingLocations[i] = new ClassPathResource(mappingResources[i]);
- }
- }
- /**
- * Set locations of Hibernate mapping files, for example as classpath
- * resource "classpath:example.hbm.xml". Supports any resource location
- * via Spring's resource abstraction, for example relative paths like
- * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
- * <p>Can be used to add to mappings from a Hibernate XML config file,
- * or to specify all mappings locally.
- * @see net.sf.hibernate.cfg.Configuration#addInputStream
- */
- public void setMappingLocations(Resource[] mappingLocations) {
- this.mappingLocations = mappingLocations;
- }
- /**
- * Set locations of jar files that contain Hibernate mapping resources,
- * like "WEB-INF/lib/example.hbm.jar".
- * <p>Can be used to add to mappings from a Hibernate XML config file,
- * or to specify all mappings locally.
- * @see net.sf.hibernate.cfg.Configuration#addJar(java.io.File)
- */
- public void setMappingJarLocations(Resource[] mappingJarLocations) {
- this.mappingJarLocations = mappingJarLocations;
- }
- /**
- * Set locations of directories that contain Hibernate mapping resources,
- * like "WEB-INF/mappings".
- * <p>Can be used to add to mappings from a Hibernate XML config file,
- * or to specify all mappings locally.
- * @see net.sf.hibernate.cfg.Configuration#addDirectory(java.io.File)
- */
- public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) {
- this.mappingDirectoryLocations = mappingDirectoryLocations;
- }
- /**
- * Set Hibernate properties, like "hibernate.dialect".
- * <p>Can be used to override values in a Hibernate XML config file,
- * or to specify all necessary properties locally.
- * <p>Note: Do not specify a transaction provider here when using
- * Spring-driven transactions. It is also advisable to omit connection
- * provider settings and use a Spring-set DataSource instead.
- * @see #setDataSource
- */
- public void setHibernateProperties(Properties hibernateProperties) {
- this.hibernateProperties = hibernateProperties;
- }
- /**
- * Set the DataSource to be used by the SessionFactory.
- * If set, this will override corresponding settings in Hibernate properties.
- * <p>Note: If this is set, the Hibernate settings should not define
- * a connection provider to avoid meaningless double configuration.
- * @see LocalDataSourceConnectionProvider
- */
- public void setDataSource(DataSource dataSource) {
- this.dataSource = dataSource;
- }
- /**
- * Set the JTA TransactionManager to be used for Hibernate's
- * TransactionManagerLookup. If set, this will override corresponding
- * settings in Hibernate properties. Allows to use a Spring-managed
- * JTA TransactionManager for Hibernate's cache synchronization.
- * <p>Note: If this is set, the Hibernate settings should not define a
- * transaction manager lookup to avoid meaningless double configuration.
- * @see LocalTransactionManagerLookup
- */
- public void setJtaTransactionManager(TransactionManager jtaTransactionManager) {
- this.jtaTransactionManager = jtaTransactionManager;
- }
- /**
- * Set the LobHandler to be used by the SessionFactory.
- * Will be exposed at config time for Type implementations.
- * @see #getConfigTimeLobHandler
- * @see org.springframework.orm.hibernate.support.ClobStringType
- * @see net.sf.hibernate.type.Type
- */
- public void setLobHandler(LobHandler lobHandler) {
- this.lobHandler = lobHandler;
- }
- /**
- * Set a Hibernate entity interceptor that allows to inspect and change
- * property values before writing to and reading from the database.
- * Will get applied to any new Session created by this factory.
- * <p>Such an interceptor can either be set at the SessionFactory level, i.e. on
- * LocalSessionFactoryBean, or at the Session level, i.e. on HibernateTemplate,
- * HibernateInterceptor, and HibernateTransactionManager. It's preferable to set
- * it on LocalSessionFactoryBean or HibernateTransactionManager to avoid repeated
- * configuration and guarantee consistent behavior in transactions.
- * @see HibernateTemplate#setEntityInterceptor
- * @see HibernateInterceptor#setEntityInterceptor
- * @see HibernateTransactionManager#setEntityInterceptor
- * @see net.sf.hibernate.cfg.Configuration#setInterceptor
- */
- public void setEntityInterceptor(Interceptor entityInterceptor) {
- this.entityInterceptor = entityInterceptor;
- }
- /**
- * Set a Hibernate NamingStrategy for the SessionFactory, determining the
- * physical column and table names given the info in the mapping document.
- * @see net.sf.hibernate.cfg.Configuration#setNamingStrategy
- */
- public void setNamingStrategy(NamingStrategy namingStrategy) {
- this.namingStrategy = namingStrategy;
- }
- /**
- * Set whether to execute a schema update after SessionFactory initialization.
- * <p>For details on how to make schema update scripts work, see the Hibernate
- * documentation, as this class leverages the same schema update script support
- * in net.sf.hibernate.cfg.Configuration as Hibernate's own SchemaUpdate tool.
- * @see net.sf.hibernate.cfg.Configuration#generateSchemaUpdateScript
- * @see net.sf.hibernate.tool.hbm2ddl.SchemaUpdate
- */
- public void setSchemaUpdate(boolean schemaUpdate) {
- this.schemaUpdate = schemaUpdate;
- }
- /**
- * Initialize the SessionFactory for the given or the default location.
- * @throws IllegalArgumentException in case of illegal property values
- * @throws HibernateException in case of Hibernate initialization errors
- */
- public void afterPropertiesSet() throws IllegalArgumentException, HibernateException, IOException {
- // create Configuration instance
- Configuration config = newConfiguration();
- if (this.lobHandler != null) {
- // make given LobHandler available for SessionFactory configuration
- // do early because because mapping resource might refer to custom types
- configTimeLobHandlerHolder.set(this.lobHandler);
- }
- if (this.namingStrategy != null) {
- // pass given naming strategy to Hibernate Configuration
- config.setNamingStrategy(this.namingStrategy);
- }
- if (this.configLocation != null) {
- // load Hibernate configuration from given location
- config.configure(this.configLocation.getURL());
- }
- if (this.mappingLocations != null) {
- // register given Hibernate mapping definitions, contained in resource files
- for (int i = 0; i < this.mappingLocations.length; i++) {
- config.addInputStream(this.mappingLocations[i].getInputStream());
- }
- }
- if (this.mappingJarLocations != null) {
- // register given Hibernate mapping definitions, contained in jar files
- for (int i = 0; i < this.mappingJarLocations.length; i++) {
- Resource resource = this.mappingJarLocations[i];
- config.addJar(resource.getFile());
- }
- }
- if (this.mappingDirectoryLocations != null) {
- // register all Hibernate mapping definitions in the given directories
- for (int i = 0; i < this.mappingDirectoryLocations.length; i++) {
- File file = this.mappingDirectoryLocations[i].getFile();
- if (!file.isDirectory()) {
- throw new IllegalArgumentException("Mapping directory location [" + this.mappingDirectoryLocations[i] +
- "] does not denote a directory");
- }
- config.addDirectory(file);
- }
- }
- if (this.hibernateProperties != null) {
- // add given Hibernate properties
- config.addProperties(this.hibernateProperties);
- }
- if (this.dataSource != null) {
- // make given DataSource available for SessionFactory configuration
- config.setProperty(Environment.CONNECTION_PROVIDER, LocalDataSourceConnectionProvider.class.getName());
- LocalDataSourceConnectionProvider.configTimeDataSourceHolder.set(this.dataSource);
- }
- if (this.jtaTransactionManager != null) {
- // set Spring-provided JTA TransactionManager for Hibernate cache callbacks
- config.setProperty(Environment.TRANSACTION_MANAGER_STRATEGY, LocalTransactionManagerLookup.class.getName());
- LocalTransactionManagerLookup.configTimeTransactionManagerHolder.set(this.jtaTransactionManager);
- }
- if (this.entityInterceptor != null) {
- // set given entity interceptor at SessionFactory level
- config.setInterceptor(this.entityInterceptor);
- }
- // perform custom post-processing in subclasses
- postProcessConfiguration(config);
- // build SessionFactory instance
- logger.info("Building new Hibernate SessionFactory");
- this.configuration = config;
- this.sessionFactory = newSessionFactory(config);
- if (this.jtaTransactionManager != null) {
- // reset TransactionManager holder
- LocalTransactionManagerLookup.configTimeTransactionManagerHolder.set(null);
- }
- if (this.dataSource != null) {
- // reset DataSource holder
- LocalDataSourceConnectionProvider.configTimeDataSourceHolder.set(null);
- }
- if (this.lobHandler != null) {
- // reset LobHandler holder
- configTimeLobHandlerHolder.set(null);
- }
- // execute schema update if requested
- if (this.schemaUpdate) {
- updateDatabaseSchema();
- }
- }
- /**
- * Subclasses can override this method to perform custom initialization
- * of the Configuration instance used for SessionFactory creation.
- * The properties of this LocalSessionFactoryBean will be applied to
- * the Configuration object that gets returned here.
- * <p>The default implementation creates a new Configuration instance.
- * A custom implementation could prepare the instance in a specific way,
- * or use a custom Configuration subclass.
- * @return the Configuration instance
- * @throws HibernateException in case of Hibernate initialization errors
- * @see net.sf.hibernate.cfg.Configuration#Configuration()
- */
- protected Configuration newConfiguration() throws HibernateException {
- return new Configuration();
- }
- /**
- * To be implemented by subclasses that want to to perform custom
- * post-processing of the Configuration object after this FactoryBean
- * performed its default initialization.
- * @param config the current Configuration object
- * @throws HibernateException in case of Hibernate initialization errors
- */
- protected void postProcessConfiguration(Configuration config) throws HibernateException {
- }
- /**
- * Subclasses can override this method to perform custom initialization
- * of the SessionFactory instance, creating it via the given Configuration
- * object that got prepared by this LocalSessionFactoryBean.
- * <p>The default implementation invokes Configuration's buildSessionFactory.
- * A custom implementation could prepare the instance in a specific way,
- * or use a custom SessionFactoryImpl subclass.
- * @param config Configuration prepared by this LocalSessionFactoryBean
- * @return the SessionFactory instance
- * @throws HibernateException in case of Hibernate initialization errors
- * @see net.sf.hibernate.cfg.Configuration#buildSessionFactory
- */
- protected SessionFactory newSessionFactory(Configuration config) throws HibernateException {
- return config.buildSessionFactory();
- }
- /**
- * Execute schema drop script, determined by the Configuration object
- * used for creating the SessionFactory. A replacement for Hibernate's
- * SchemaExport class, to be invoked on application setup.
- * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
- * SessionFactory to be able to invoke this method, e.g. via
- * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
- * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
- * connection to perform the script.
- * @throws DataAccessException in case of script execution errors
- * @see net.sf.hibernate.cfg.Configuration#generateDropSchemaScript
- * @see net.sf.hibernate.tool.hbm2ddl.SchemaExport#drop
- */
- public void dropDatabaseSchema() throws DataAccessException {
- logger.info("Dropping database schema for Hibernate SessionFactory");
- HibernateTemplate hibernateTemplate = new HibernateTemplate(this.sessionFactory);
- hibernateTemplate.execute(
- new HibernateCallback() {
- public Object doInHibernate(Session session) throws HibernateException, SQLException {
- Connection con = session.connection();
- Dialect dialect = Dialect.getDialect(configuration.getProperties());
- String[] sql = configuration.generateDropSchemaScript(dialect);
- executeSchemaScript(con, sql);
- return null;
- }
- }
- );
- }
- /**
- * Execute schema creation script, determined by the Configuration object
- * used for creating the SessionFactory. A replacement for Hibernate's
- * SchemaExport class, to be invoked on application setup.
- * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
- * SessionFactory to be able to invoke this method, e.g. via
- * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
- * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
- * connection to perform the script.
- * @throws DataAccessException in case of script execution errors
- * @see net.sf.hibernate.cfg.Configuration#generateSchemaCreationScript
- * @see net.sf.hibernate.tool.hbm2ddl.SchemaExport#create
- */
- public void createDatabaseSchema() throws DataAccessException {
- logger.info("Creating database schema for Hibernate SessionFactory");
- HibernateTemplate hibernateTemplate = new HibernateTemplate(this.sessionFactory);
- hibernateTemplate.execute(
- new HibernateCallback() {
- public Object doInHibernate(Session session) throws HibernateException, SQLException {
- Connection con = session.connection();
- final Dialect dialect = Dialect.getDialect(configuration.getProperties());
- String[] sql = configuration.generateSchemaCreationScript(dialect);
- executeSchemaScript(con, sql);
- return null;
- }
- }
- );
- }
- /**
- * Execute schema update script, determined by the Configuration object
- * used for creating the SessionFactory. A replacement for Hibernate's
- * SchemaUpdate class, for automatically executing schema update scripts
- * on application startup. Can also be invoked manually.
- * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
- * SessionFactory to be able to invoke this method, e.g. via
- * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
- * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
- * connection to perform the script.
- * @throws HibernateException in case of Hibernate initialization errors
- * @see #setSchemaUpdate
- * @see net.sf.hibernate.cfg.Configuration#generateSchemaUpdateScript
- * @see net.sf.hibernate.tool.hbm2ddl.SchemaUpdate
- */
- public void updateDatabaseSchema() throws HibernateException {
- logger.info("Updating database schema for Hibernate SessionFactory");
- HibernateTemplate hibernateTemplate = new HibernateTemplate(this.sessionFactory);
- hibernateTemplate.setFlushMode(HibernateTemplate.FLUSH_NEVER);
- hibernateTemplate.execute(
- new HibernateCallback() {
- public Object doInHibernate(Session session) throws HibernateException, SQLException {
- Connection con = session.connection();
- final Dialect dialect = Dialect.getDialect(configuration.getProperties());
- DatabaseMetadata metadata = new DatabaseMetadata(con, dialect);
- String[] sql = configuration.generateSchemaUpdateScript(dialect, metadata);
- executeSchemaScript(con, sql);
- return null;
- }
- }
- );
- }
- /**
- * Execute the given schema script on the given JDBC Connection.
- * Will log unsuccessful statements and continue to execute.
- * @param con the JDBC Connection to execute the script on
- * @param sql the SQL statements to execute
- * @throws SQLException if thrown by JDBC methods
- */
- protected void executeSchemaScript(Connection con, String[] sql) throws SQLException {
- if (sql != null && sql.length > 0) {
- boolean oldAutoCommit = con.getAutoCommit();
- con.setAutoCommit(false);
- try {
- Statement stmt = con.createStatement();
- try {
- for (int i = 0; i < sql.length; i++) {
- logger.debug("Executing schema statement: " + sql[i]);
- try {
- stmt.executeUpdate(sql[i]);
- }
- catch (SQLException ex) {
- logger.warn("Unsuccessful schema statement: " + sql[i], ex);
- }
- }
- }
- finally {
- JdbcUtils.closeStatement(stmt);
- }
- con.commit();
- }
- finally {
- con.setAutoCommit(oldAutoCommit);
- }
- }
- }
- /**
- * Return the Configuration object used to build the SessionFactory.
- * Allows access to configuration metadata stored there (rarely needed).
- */
- public Configuration getConfiguration() {
- return configuration;
- }
- /**
- * Return the singleton SessionFactory.
- */
- public Object getObject() {
- return this.sessionFactory;
- }
- public Class getObjectType() {
- return (this.sessionFactory != null) ? this.sessionFactory.getClass() : SessionFactory.class;
- }
- public boolean isSingleton() {
- return true;
- }
- /**
- * Close the SessionFactory on bean factory shutdown.
- */
- public void destroy() throws HibernateException {
- logger.info("Closing Hibernate SessionFactory");
- this.sessionFactory.close();
- }
- }