1 /*
2 * Copyright 2002-2008 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.hibernate3;
18
19 import java.io.File;
20 import java.lang.reflect.Array;
21 import java.sql.Connection;
22 import java.sql.SQLException;
23 import java.sql.Statement;
24 import java.util.Collection;
25 import java.util.Enumeration;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Properties;
29
30 import javax.sql.DataSource;
31 import javax.transaction.TransactionManager;
32
33 import org.hibernate.HibernateException;
34 import org.hibernate.Interceptor;
35 import org.hibernate.Session;
36 import org.hibernate.SessionFactory;
37 import org.hibernate.cache.CacheProvider;
38 import org.hibernate.cfg.Configuration;
39 import org.hibernate.cfg.Environment;
40 import org.hibernate.cfg.Mappings;
41 import org.hibernate.cfg.NamingStrategy;
42 import org.hibernate.dialect.Dialect;
43 import org.hibernate.engine.FilterDefinition;
44 import org.hibernate.event.EventListeners;
45 import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
46 import org.hibernate.transaction.JTATransactionFactory;
47
48 import org.springframework.beans.BeanUtils;
49 import org.springframework.beans.factory.BeanClassLoaderAware;
50 import org.springframework.core.io.ClassPathResource;
51 import org.springframework.core.io.Resource;
52 import org.springframework.dao.DataAccessException;
53 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
54 import org.springframework.jdbc.support.JdbcUtils;
55 import org.springframework.jdbc.support.lob.LobHandler;
56 import org.springframework.util.Assert;
57 import org.springframework.util.ClassUtils;
58 import org.springframework.util.StringUtils;
59
60 /**
61 * {@link org.springframework.beans.factory.FactoryBean} that creates a
62 * Hibernate {@link org.hibernate.SessionFactory}. This is the usual way to
63 * set up a shared Hibernate SessionFactory in a Spring application context;
64 * the SessionFactory can then be passed to Hibernate-based DAOs via
65 * dependency injection.
66 *
67 * <p>Configuration settings can either be read from a Hibernate XML file,
68 * specified as "configLocation", or completely via this class. A typical
69 * local configuration consists of one or more "mappingResources", various
70 * "hibernateProperties" (not strictly necessary), and a "dataSource" that the
71 * SessionFactory should use. The latter can also be specified via Hibernate
72 * properties, but "dataSource" supports any Spring-configured DataSource,
73 * instead of relying on Hibernate's own connection providers.
74 *
75 * <p>This SessionFactory handling strategy is appropriate for most types of
76 * applications, from Hibernate-only single database apps to ones that need
77 * distributed transactions. Either {@link HibernateTransactionManager} or
78 * {@link org.springframework.transaction.jta.JtaTransactionManager} can be
79 * used for transaction demarcation, with the latter only necessary for
80 * transactions which span multiple databases.
81 *
82 * <p>This factory bean will by default expose a transaction-aware SessionFactory
83 * proxy, letting data access code work with the plain Hibernate SessionFactory
84 * and its <code>getCurrentSession()</code> method, while still being able to
85 * participate in current Spring-managed transactions: with any transaction
86 * management strategy, either local or JTA / EJB CMT, and any transaction
87 * synchronization mechanism, either Spring or JTA. Furthermore,
88 * <code>getCurrentSession()</code> will also seamlessly work with
89 * a request-scoped Session managed by
90 * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewFilter} /
91 * {@link org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor}.
92 *
93 * <p><b>Requires Hibernate 3.1 or later.</b> Note that this factory will use
94 * "on_close" as default Hibernate connection release mode, unless in the
95 * case of a "jtaTransactionManager" specified, for the reason that
96 * this is appropriate for most Spring-based applications (in particular when
97 * using Spring's HibernateTransactionManager). Hibernate 3.0 used "on_close"
98 * as its own default too; however, Hibernate 3.1 changed this to "auto"
99 * (i.e. "after_statement" or "after_transaction").
100 *
101 * @author Juergen Hoeller
102 * @since 1.2
103 * @see HibernateTemplate#setSessionFactory
104 * @see HibernateTransactionManager#setSessionFactory
105 * @see #setExposeTransactionAwareSessionFactory
106 * @see #setJtaTransactionManager
107 * @see org.hibernate.SessionFactory#getCurrentSession()
108 * @see HibernateTransactionManager
109 */
110 public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implements BeanClassLoaderAware {
111
112 private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal();
113
114 private static final ThreadLocal configTimeTransactionManagerHolder = new ThreadLocal();
115
116 private static final ThreadLocal configTimeCacheProviderHolder = new ThreadLocal();
117
118 private static final ThreadLocal configTimeLobHandlerHolder = new ThreadLocal();
119
120 /**
121 * Return the DataSource for the currently configured Hibernate SessionFactory,
122 * to be used by LocalDataSourceConnectionProvoder.
123 * <p>This instance will be set before initialization of the corresponding
124 * SessionFactory, and reset immediately afterwards. It is thus only available
125 * during configuration.
126 * @see #setDataSource
127 * @see LocalDataSourceConnectionProvider
128 */
129 public static DataSource getConfigTimeDataSource() {
130 return (DataSource) configTimeDataSourceHolder.get();
131 }
132
133 /**
134 * Return the JTA TransactionManager for the currently configured Hibernate
135 * SessionFactory, to be used by LocalTransactionManagerLookup.
136 * <p>This instance will be set before initialization of the corresponding
137 * SessionFactory, and reset immediately afterwards. It is thus only available
138 * during configuration.
139 * @see #setJtaTransactionManager
140 * @see LocalTransactionManagerLookup
141 */
142 public static TransactionManager getConfigTimeTransactionManager() {
143 return (TransactionManager) configTimeTransactionManagerHolder.get();
144 }
145
146 /**
147 * Return the CacheProvider for the currently configured Hibernate SessionFactory,
148 * to be used by LocalCacheProviderProxy.
149 * <p>This instance will be set before initialization of the corresponding
150 * SessionFactory, and reset immediately afterwards. It is thus only available
151 * during configuration.
152 * @see #setCacheProvider
153 */
154 public static CacheProvider getConfigTimeCacheProvider() {
155 return (CacheProvider) configTimeCacheProviderHolder.get();
156 }
157
158 /**
159 * Return the LobHandler for the currently configured Hibernate SessionFactory,
160 * to be used by UserType implementations like ClobStringType.
161 * <p>This instance will be set before initialization of the corresponding
162 * SessionFactory, and reset immediately afterwards. It is thus only available
163 * during configuration.
164 * @see #setLobHandler
165 * @see org.springframework.orm.hibernate3.support.ClobStringType
166 * @see org.springframework.orm.hibernate3.support.BlobByteArrayType
167 * @see org.springframework.orm.hibernate3.support.BlobSerializableType
168 */
169 public static LobHandler getConfigTimeLobHandler() {
170 return (LobHandler) configTimeLobHandlerHolder.get();
171 }
172
173
174 private Class configurationClass = Configuration.class;
175
176 private Resource[] configLocations;
177
178 private String[] mappingResources;
179
180 private Resource[] mappingLocations;
181
182 private Resource[] cacheableMappingLocations;
183
184 private Resource[] mappingJarLocations;
185
186 private Resource[] mappingDirectoryLocations;
187
188 private Properties hibernateProperties;
189
190 private TransactionManager jtaTransactionManager;
191
192 private CacheProvider cacheProvider;
193
194 private LobHandler lobHandler;
195
196 private Interceptor entityInterceptor;
197
198 private NamingStrategy namingStrategy;
199
200 private TypeDefinitionBean[] typeDefinitions;
201
202 private FilterDefinition[] filterDefinitions;
203
204 private Properties entityCacheStrategies;
205
206 private Properties collectionCacheStrategies;
207
208 private Map eventListeners;
209
210 private boolean schemaUpdate = false;
211
212 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
213
214 private Configuration configuration;
215
216
217 /**
218 * Specify the Hibernate Configuration class to use.
219 * Default is "org.hibernate.cfg.Configuration"; any subclass of
220 * this default Hibernate Configuration class can be specified.
221 * <p>Can be set to "org.hibernate.cfg.AnnotationConfiguration" for
222 * using Hibernate3 annotation support (initially only available as
223 * alpha download separate from the main Hibernate3 distribution).
224 * <p>Annotated packages and annotated classes can be specified via the
225 * corresponding tags in "hibernate.cfg.xml" then, so this will usually
226 * be combined with a "configLocation" property that points at such a
227 * standard Hibernate configuration file.
228 * @see #setConfigLocation
229 * @see org.hibernate.cfg.Configuration
230 * @see org.hibernate.cfg.AnnotationConfiguration
231 */
232 public void setConfigurationClass(Class configurationClass) {
233 if (configurationClass == null || !Configuration.class.isAssignableFrom(configurationClass)) {
234 throw new IllegalArgumentException(
235 "configurationClass must be assignable to [org.hibernate.cfg.Configuration]");
236 }
237 this.configurationClass = configurationClass;
238 }
239
240 /**
241 * Set the location of a single Hibernate XML config file, for example as
242 * classpath resource "classpath:hibernate.cfg.xml".
243 * <p>Note: Can be omitted when all necessary properties and mapping
244 * resources are specified locally via this bean.
245 * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
246 */
247 public void setConfigLocation(Resource configLocation) {
248 this.configLocations = new Resource[] {configLocation};
249 }
250
251 /**
252 * Set the locations of multiple Hibernate XML config files, for example as
253 * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
254 * <p>Note: Can be omitted when all necessary properties and mapping
255 * resources are specified locally via this bean.
256 * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
257 */
258 public void setConfigLocations(Resource[] configLocations) {
259 this.configLocations = configLocations;
260 }
261
262 /**
263 * Set Hibernate mapping resources to be found in the class path,
264 * like "example.hbm.xml" or "mypackage/example.hbm.xml".
265 * Analogous to mapping entries in a Hibernate XML config file.
266 * Alternative to the more generic setMappingLocations method.
267 * <p>Can be used to add to mappings from a Hibernate XML config file,
268 * or to specify all mappings locally.
269 * @see #setMappingLocations
270 * @see org.hibernate.cfg.Configuration#addResource
271 */
272 public void setMappingResources(String[] mappingResources) {
273 this.mappingResources = mappingResources;
274 }
275
276 /**
277 * Set locations of Hibernate mapping files, for example as classpath
278 * resource "classpath:example.hbm.xml". Supports any resource location
279 * via Spring's resource abstraction, for example relative paths like
280 * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
281 * <p>Can be used to add to mappings from a Hibernate XML config file,
282 * or to specify all mappings locally.
283 * @see org.hibernate.cfg.Configuration#addInputStream
284 */
285 public void setMappingLocations(Resource[] mappingLocations) {
286 this.mappingLocations = mappingLocations;
287 }
288
289 /**
290 * Set locations of cacheable Hibernate mapping files, for example as web app
291 * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
292 * via Spring's resource abstraction, as long as the resource can be resolved
293 * in the file system.
294 * <p>Can be used to add to mappings from a Hibernate XML config file,
295 * or to specify all mappings locally.
296 * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File)
297 */
298 public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) {
299 this.cacheableMappingLocations = cacheableMappingLocations;
300 }
301
302 /**
303 * Set locations of jar files that contain Hibernate mapping resources,
304 * like "WEB-INF/lib/example.hbm.jar".
305 * <p>Can be used to add to mappings from a Hibernate XML config file,
306 * or to specify all mappings locally.
307 * @see org.hibernate.cfg.Configuration#addJar(java.io.File)
308 */
309 public void setMappingJarLocations(Resource[] mappingJarLocations) {
310 this.mappingJarLocations = mappingJarLocations;
311 }
312
313 /**
314 * Set locations of directories that contain Hibernate mapping resources,
315 * like "WEB-INF/mappings".
316 * <p>Can be used to add to mappings from a Hibernate XML config file,
317 * or to specify all mappings locally.
318 * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File)
319 */
320 public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) {
321 this.mappingDirectoryLocations = mappingDirectoryLocations;
322 }
323
324 /**
325 * Set Hibernate properties, such as "hibernate.dialect".
326 * <p>Can be used to override values in a Hibernate XML config file,
327 * or to specify all necessary properties locally.
328 * <p>Note: Do not specify a transaction provider here when using
329 * Spring-driven transactions. It is also advisable to omit connection
330 * provider settings and use a Spring-set DataSource instead.
331 * @see #setDataSource
332 */
333 public void setHibernateProperties(Properties hibernateProperties) {
334 this.hibernateProperties = hibernateProperties;
335 }
336
337 /**
338 * Return the Hibernate properties, if any. Mainly available for
339 * configuration through property paths that specify individual keys.
340 */
341 public Properties getHibernateProperties() {
342 if (this.hibernateProperties == null) {
343 this.hibernateProperties = new Properties();
344 }
345 return this.hibernateProperties;
346 }
347
348 /**
349 * Set the JTA TransactionManager to be used for Hibernate's
350 * TransactionManagerLookup. Allows for using a Spring-managed
351 * JTA TransactionManager for Hibernate's cache synchronization.
352 * <p>Note: If this is set, the Hibernate settings should not define a
353 * transaction manager lookup to avoid meaningless double configuration.
354 * @see LocalTransactionManagerLookup
355 */
356 public void setJtaTransactionManager(TransactionManager jtaTransactionManager) {
357 this.jtaTransactionManager = jtaTransactionManager;
358 }
359
360 /**
361 * Set the Hibernate CacheProvider to use for the SessionFactory.
362 * Allows for using a Spring-managed CacheProvider instance.
363 * <p>Note: If this is set, the Hibernate settings should not define a
364 * cache provider to avoid meaningless double configuration.
365 * @see LocalCacheProviderProxy
366 */
367 public void setCacheProvider(CacheProvider cacheProvider) {
368 this.cacheProvider = cacheProvider;
369 }
370
371 /**
372 * Set the LobHandler to be used by the SessionFactory.
373 * Will be exposed at config time for UserType implementations.
374 * @see #getConfigTimeLobHandler
375 * @see org.hibernate.usertype.UserType
376 * @see org.springframework.orm.hibernate3.support.ClobStringType
377 * @see org.springframework.orm.hibernate3.support.BlobByteArrayType
378 * @see org.springframework.orm.hibernate3.support.BlobSerializableType
379 */
380 public void setLobHandler(LobHandler lobHandler) {
381 this.lobHandler = lobHandler;
382 }
383
384 /**
385 * Set a Hibernate entity interceptor that allows to inspect and change
386 * property values before writing to and reading from the database.
387 * Will get applied to any new Session created by this factory.
388 * <p>Such an interceptor can either be set at the SessionFactory level, i.e. on
389 * LocalSessionFactoryBean, or at the Session level, i.e. on HibernateTemplate,
390 * HibernateInterceptor, and HibernateTransactionManager. It's preferable to set
391 * it on LocalSessionFactoryBean or HibernateTransactionManager to avoid repeated
392 * configuration and guarantee consistent behavior in transactions.
393 * @see HibernateTemplate#setEntityInterceptor
394 * @see HibernateInterceptor#setEntityInterceptor
395 * @see HibernateTransactionManager#setEntityInterceptor
396 * @see org.hibernate.cfg.Configuration#setInterceptor
397 */
398 public void setEntityInterceptor(Interceptor entityInterceptor) {
399 this.entityInterceptor = entityInterceptor;
400 }
401
402 /**
403 * Set a Hibernate NamingStrategy for the SessionFactory, determining the
404 * physical column and table names given the info in the mapping document.
405 * @see org.hibernate.cfg.Configuration#setNamingStrategy
406 */
407 public void setNamingStrategy(NamingStrategy namingStrategy) {
408 this.namingStrategy = namingStrategy;
409 }
410
411 /**
412 * Specify the Hibernate type definitions to register with the SessionFactory,
413 * as Spring TypeDefinitionBean instances. This is an alternative to specifying
414 * <<typedef> elements in Hibernate mapping files.
415 * <p>Unfortunately, Hibernate itself does not define a complete object that
416 * represents a type definition, hence the need for Spring's TypeDefinitionBean.
417 * @see TypeDefinitionBean
418 * @see org.hibernate.cfg.Mappings#addTypeDef(String, String, java.util.Properties)
419 */
420 public void setTypeDefinitions(TypeDefinitionBean[] typeDefinitions) {
421 this.typeDefinitions = typeDefinitions;
422 }
423
424 /**
425 * Specify the Hibernate FilterDefinitions to register with the SessionFactory.
426 * This is an alternative to specifying <<filter-def> elements in
427 * Hibernate mapping files.
428 * <p>Typically, the passed-in FilterDefinition objects will have been defined
429 * as Spring FilterDefinitionFactoryBeans, probably as inner beans within the
430 * LocalSessionFactoryBean definition.
431 * @see FilterDefinitionFactoryBean
432 * @see org.hibernate.cfg.Configuration#addFilterDefinition
433 */
434 public void setFilterDefinitions(FilterDefinition[] filterDefinitions) {
435 this.filterDefinitions = filterDefinitions;
436 }
437
438 /**
439 * Specify the cache strategies for entities (persistent classes or named entities).
440 * This configuration setting corresponds to the <class-cache> entry
441 * in the "hibernate.cfg.xml" configuration format.
442 * <p>For example:
443 * <pre>
444 * <property name="entityCacheStrategies">
445 * <props>
446 * <prop key="com.mycompany.Customer">read-write</prop>
447 * <prop key="com.mycompany.Product">read-only,myRegion</prop>
448 * </props>
449 * </property></pre>
450 * Note that appending a cache region name (with a comma separator) is only
451 * supported on Hibernate 3.1, where this functionality is publically available.
452 * @param entityCacheStrategies properties that define entity cache strategies,
453 * with class names as keys and cache concurrency strategies as values
454 * @see org.hibernate.cfg.Configuration#setCacheConcurrencyStrategy(String, String)
455 */
456 public void setEntityCacheStrategies(Properties entityCacheStrategies) {
457 this.entityCacheStrategies = entityCacheStrategies;
458 }
459
460 /**
461 * Specify the cache strategies for persistent collections (with specific roles).
462 * This configuration setting corresponds to the <collection-cache> entry
463 * in the "hibernate.cfg.xml" configuration format.
464 * <p>For example:
465 * <pre>
466 * <property name="collectionCacheStrategies">
467 * <props>
468 * <prop key="com.mycompany.Order.items">read-write</prop>
469 * <prop key="com.mycompany.Product.categories">read-only,myRegion</prop>
470 * </props>
471 * </property></pre>
472 * Note that appending a cache region name (with a comma separator) is only
473 * supported on Hibernate 3.1, where this functionality is publically available.
474 * @param collectionCacheStrategies properties that define collection cache strategies,
475 * with collection roles as keys and cache concurrency strategies as values
476 * @see org.hibernate.cfg.Configuration#setCollectionCacheConcurrencyStrategy(String, String)
477 */
478 public void setCollectionCacheStrategies(Properties collectionCacheStrategies) {
479 this.collectionCacheStrategies = collectionCacheStrategies;
480 }
481
482 /**
483 * Specify the Hibernate event listeners to register, with listener types
484 * as keys and listener objects as values.
485 * <p>Instead of a single listener object, you can also pass in a list
486 * or set of listeners objects as value. However, this is only supported
487 * on Hibernate 3.1.
488 * <p>See the Hibernate documentation for further details on listener types
489 * and associated listener interfaces.
490 * @param eventListeners Map with listener type Strings as keys and
491 * listener objects as values
492 * @see org.hibernate.cfg.Configuration#setListener(String, Object)
493 */
494 public void setEventListeners(Map eventListeners) {
495 this.eventListeners = eventListeners;
496 }
497
498 /**
499 * Set whether to execute a schema update after SessionFactory initialization.
500 * <p>For details on how to make schema update scripts work, see the Hibernate
501 * documentation, as this class leverages the same schema update script support
502 * in org.hibernate.cfg.Configuration as Hibernate's own SchemaUpdate tool.
503 * @see org.hibernate.cfg.Configuration#generateSchemaUpdateScript
504 * @see org.hibernate.tool.hbm2ddl.SchemaUpdate
505 */
506 public void setSchemaUpdate(boolean schemaUpdate) {
507 this.schemaUpdate = schemaUpdate;
508 }
509
510 public void setBeanClassLoader(ClassLoader beanClassLoader) {
511 this.beanClassLoader = beanClassLoader;
512 }
513
514
515 protected SessionFactory buildSessionFactory() throws Exception {
516 // Create Configuration instance.
517 Configuration config = newConfiguration();
518
519 DataSource dataSource = getDataSource();
520 if (dataSource != null) {
521 // Make given DataSource available for SessionFactory configuration.
522 configTimeDataSourceHolder.set(dataSource);
523 }
524 if (this.jtaTransactionManager != null) {
525 // Make Spring-provided JTA TransactionManager available.
526 configTimeTransactionManagerHolder.set(this.jtaTransactionManager);
527 }
528 if (this.cacheProvider != null) {
529 // Make Spring-provided Hibernate CacheProvider available.
530 configTimeCacheProviderHolder.set(this.cacheProvider);
531 }
532 if (this.lobHandler != null) {
533 // Make given LobHandler available for SessionFactory configuration.
534 // Do early because because mapping resource might refer to custom types.
535 configTimeLobHandlerHolder.set(this.lobHandler);
536 }
537
538 // Analogous to Hibernate EntityManager's Ejb3Configuration:
539 // Hibernate doesn't allow setting the bean ClassLoader explicitly,
540 // so we need to expose it as thread context ClassLoader accordingly.
541 Thread currentThread = Thread.currentThread();
542 ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
543 boolean overrideClassLoader =
544 (this.beanClassLoader != null && !this.beanClassLoader.equals(threadContextClassLoader));
545 if (overrideClassLoader) {
546 currentThread.setContextClassLoader(this.beanClassLoader);
547 }
548
549 try {
550 if (isExposeTransactionAwareSessionFactory()) {
551 // Set Hibernate 3.1 CurrentSessionContext implementation,
552 // providing the Spring-managed Session as current Session.
553 // Can be overridden by a custom value for the corresponding Hibernate property.
554 config.setProperty(
555 Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());
556 }
557
558 if (this.jtaTransactionManager != null) {
559 // Set Spring-provided JTA TransactionManager as Hibernate property.
560 config.setProperty(
561 Environment.TRANSACTION_STRATEGY, JTATransactionFactory.class.getName());
562 config.setProperty(
563 Environment.TRANSACTION_MANAGER_STRATEGY, LocalTransactionManagerLookup.class.getName());
564 }
565 else {
566 // Makes the Hibernate Session aware of the presence of a Spring-managed transaction.
567 // Also sets connection release mode to ON_CLOSE by default.
568 config.setProperty(
569 Environment.TRANSACTION_STRATEGY, SpringTransactionFactory.class.getName());
570 }
571
572 if (this.entityInterceptor != null) {
573 // Set given entity interceptor at SessionFactory level.
574 config.setInterceptor(this.entityInterceptor);
575 }
576
577 if (this.namingStrategy != null) {
578 // Pass given naming strategy to Hibernate Configuration.
579 config.setNamingStrategy(this.namingStrategy);
580 }
581
582 if (this.typeDefinitions != null) {
583 // Register specified Hibernate type definitions.
584 Mappings mappings = config.createMappings();
585 for (int i = 0; i < this.typeDefinitions.length; i++) {
586 TypeDefinitionBean typeDef = this.typeDefinitions[i];
587 mappings.addTypeDef(typeDef.getTypeName(), typeDef.getTypeClass(), typeDef.getParameters());
588 }
589 }
590
591 if (this.filterDefinitions != null) {
592 // Register specified Hibernate FilterDefinitions.
593 for (int i = 0; i < this.filterDefinitions.length; i++) {
594 config.addFilterDefinition(this.filterDefinitions[i]);
595 }
596 }
597
598 if (this.configLocations != null) {
599 for (int i = 0; i < this.configLocations.length; i++) {
600 // Load Hibernate configuration from given location.
601 config.configure(this.configLocations[i].getURL());
602 }
603 }
604
605 if (this.hibernateProperties != null) {
606 // Add given Hibernate properties to Configuration.
607 config.addProperties(this.hibernateProperties);
608 }
609
610 if (dataSource != null) {
611 Class providerClass = LocalDataSourceConnectionProvider.class;
612 if (isUseTransactionAwareDataSource() || dataSource instanceof TransactionAwareDataSourceProxy) {
613 providerClass = TransactionAwareDataSourceConnectionProvider.class;
614 }
615 else if (config.getProperty(Environment.TRANSACTION_MANAGER_STRATEGY) != null) {
616 providerClass = LocalJtaDataSourceConnectionProvider.class;
617 }
618 // Set Spring-provided DataSource as Hibernate ConnectionProvider.
619 config.setProperty(Environment.CONNECTION_PROVIDER, providerClass.getName());
620 }
621
622 if (this.cacheProvider != null) {
623 // Expose Spring-provided Hibernate CacheProvider.
624 config.setProperty(Environment.CACHE_PROVIDER, LocalCacheProviderProxy.class.getName());
625 }
626
627 if (this.mappingResources != null) {
628 // Register given Hibernate mapping definitions, contained in resource files.
629 for (int i = 0; i < this.mappingResources.length; i++) {
630 Resource resource = new ClassPathResource(this.mappingResources[i].trim(), this.beanClassLoader);
631 config.addInputStream(resource.getInputStream());
632 }
633 }
634
635 if (this.mappingLocations != null) {
636 // Register given Hibernate mapping definitions, contained in resource files.
637 for (int i = 0; i < this.mappingLocations.length; i++) {
638 config.addInputStream(this.mappingLocations[i].getInputStream());
639 }
640 }
641
642 if (this.cacheableMappingLocations != null) {
643 // Register given cacheable Hibernate mapping definitions, read from the file system.
644 for (int i = 0; i < this.cacheableMappingLocations.length; i++) {
645 config.addCacheableFile(this.cacheableMappingLocations[i].getFile());
646 }
647 }
648
649 if (this.mappingJarLocations != null) {
650 // Register given Hibernate mapping definitions, contained in jar files.
651 for (int i = 0; i < this.mappingJarLocations.length; i++) {
652 Resource resource = this.mappingJarLocations[i];
653 config.addJar(resource.getFile());
654 }
655 }
656
657 if (this.mappingDirectoryLocations != null) {
658 // Register all Hibernate mapping definitions in the given directories.
659 for (int i = 0; i < this.mappingDirectoryLocations.length; i++) {
660 File file = this.mappingDirectoryLocations[i].getFile();
661 if (!file.isDirectory()) {
662 throw new IllegalArgumentException(
663 "Mapping directory location [" + this.mappingDirectoryLocations[i] +
664 "] does not denote a directory");
665 }
666 config.addDirectory(file);
667 }
668 }
669
670 // Tell Hibernate to eagerly compile the mappings that we registered,
671 // for availability of the mapping information in further processing.
672 postProcessMappings(config);
673 config.buildMappings();
674
675 if (this.entityCacheStrategies != null) {
676 // Register cache strategies for mapped entities.
677 for (Enumeration classNames = this.entityCacheStrategies.propertyNames(); classNames.hasMoreElements();) {
678 String className = (String) classNames.nextElement();
679 String[] strategyAndRegion =
680 StringUtils.commaDelimitedListToStringArray(this.entityCacheStrategies.getProperty(className));
681 if (strategyAndRegion.length > 1) {
682 config.setCacheConcurrencyStrategy(className, strategyAndRegion[0], strategyAndRegion[1]);
683 }
684 else if (strategyAndRegion.length > 0) {
685 config.setCacheConcurrencyStrategy(className, strategyAndRegion[0]);
686 }
687 }
688 }
689
690 if (this.collectionCacheStrategies != null) {
691 // Register cache strategies for mapped collections.
692 for (Enumeration collRoles = this.collectionCacheStrategies.propertyNames(); collRoles.hasMoreElements();) {
693 String collRole = (String) collRoles.nextElement();
694 String[] strategyAndRegion =
695 StringUtils.commaDelimitedListToStringArray(this.collectionCacheStrategies.getProperty(collRole));
696 if (strategyAndRegion.length > 1) {
697 config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0], strategyAndRegion[1]);
698 }
699 else if (strategyAndRegion.length > 0) {
700 config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0]);
701 }
702 }
703 }
704
705 if (this.eventListeners != null) {
706 // Register specified Hibernate event listeners.
707 for (Iterator it = this.eventListeners.entrySet().iterator(); it.hasNext();) {
708 Map.Entry entry = (Map.Entry) it.next();
709 Assert.isTrue(entry.getKey() instanceof String, "Event listener key needs to be of type String");
710 String listenerType = (String) entry.getKey();
711 Object listenerObject = entry.getValue();
712 if (listenerObject instanceof Collection) {
713 Collection listeners = (Collection) listenerObject;
714 EventListeners listenerRegistry = config.getEventListeners();
715 Object[] listenerArray =
716 (Object[]) Array.newInstance(listenerRegistry.getListenerClassFor(listenerType), listeners.size());
717 listenerArray = listeners.toArray(listenerArray);
718 config.setListeners(listenerType, listenerArray);
719 }
720 else {
721 config.setListener(listenerType, listenerObject);
722 }
723 }
724 }
725
726 // Perform custom post-processing in subclasses.
727 postProcessConfiguration(config);
728
729 // Build SessionFactory instance.
730 logger.info("Building new Hibernate SessionFactory");
731 this.configuration = config;
732 return newSessionFactory(config);
733 }
734
735 finally {
736 if (dataSource != null) {
737 // Reset DataSource holder.
738 configTimeDataSourceHolder.set(null);
739 }
740 if (this.jtaTransactionManager != null) {
741 // Reset TransactionManager holder.
742 configTimeTransactionManagerHolder.set(null);
743 }
744 if (this.cacheProvider != null) {
745 // Reset CacheProvider holder.
746 configTimeCacheProviderHolder.set(null);
747 }
748 if (this.lobHandler != null) {
749 // Reset LobHandler holder.
750 configTimeLobHandlerHolder.set(null);
751 }
752 if (overrideClassLoader) {
753 // Reset original thread context ClassLoader.
754 currentThread.setContextClassLoader(threadContextClassLoader);
755 }
756 }
757 }
758
759 /**
760 * Subclasses can override this method to perform custom initialization
761 * of the Configuration instance used for SessionFactory creation.
762 * The properties of this LocalSessionFactoryBean will be applied to
763 * the Configuration object that gets returned here.
764 * <p>The default implementation creates a new Configuration instance.
765 * A custom implementation could prepare the instance in a specific way,
766 * or use a custom Configuration subclass.
767 * @return the Configuration instance
768 * @throws HibernateException in case of Hibernate initialization errors
769 * @see org.hibernate.cfg.Configuration#Configuration()
770 */
771 protected Configuration newConfiguration() throws HibernateException {
772 return (Configuration) BeanUtils.instantiateClass(this.configurationClass);
773 }
774
775 /**
776 * To be implemented by subclasses that want to to register further mappings
777 * on the Configuration object after this FactoryBean registered its specified
778 * mappings.
779 * <p>Invoked <i>before</i> the <code>Configuration.buildMappings()</code> call,
780 * so that it can still extend and modify the mapping information.
781 * @param config the current Configuration object
782 * @throws HibernateException in case of Hibernate initialization errors
783 * @see org.hibernate.cfg.Configuration#buildMappings()
784 */
785 protected void postProcessMappings(Configuration config) throws HibernateException {
786 }
787
788 /**
789 * To be implemented by subclasses that want to to perform custom
790 * post-processing of the Configuration object after this FactoryBean
791 * performed its default initialization.
792 * <p>Invoked <i>after</i> the <code>Configuration.buildMappings()</code> call,
793 * so that it can operate on the completed and fully parsed mapping information.
794 * @param config the current Configuration object
795 * @throws HibernateException in case of Hibernate initialization errors
796 * @see org.hibernate.cfg.Configuration#buildMappings()
797 */
798 protected void postProcessConfiguration(Configuration config) throws HibernateException {
799 }
800
801 /**
802 * Subclasses can override this method to perform custom initialization
803 * of the SessionFactory instance, creating it via the given Configuration
804 * object that got prepared by this LocalSessionFactoryBean.
805 * <p>The default implementation invokes Configuration's buildSessionFactory.
806 * A custom implementation could prepare the instance in a specific way,
807 * or use a custom SessionFactoryImpl subclass.
808 * @param config Configuration prepared by this LocalSessionFactoryBean
809 * @return the SessionFactory instance
810 * @throws HibernateException in case of Hibernate initialization errors
811 * @see org.hibernate.cfg.Configuration#buildSessionFactory
812 */
813 protected SessionFactory newSessionFactory(Configuration config) throws HibernateException {
814 return config.buildSessionFactory();
815 }
816
817 /**
818 * Return the Configuration object used to build the SessionFactory.
819 * Allows access to configuration metadata stored there (rarely needed).
820 * @throws IllegalStateException if the Configuration object has not been initialized yet
821 */
822 public final Configuration getConfiguration() {
823 if (this.configuration == null) {
824 throw new IllegalStateException("Configuration not initialized yet");
825 }
826 return this.configuration;
827 }
828
829 /**
830 * Executes schema update if requested.
831 * @see #setSchemaUpdate
832 * @see #updateDatabaseSchema()
833 */
834 protected void afterSessionFactoryCreation() throws Exception {
835 if (this.schemaUpdate) {
836 DataSource dataSource = getDataSource();
837 if (dataSource != null) {
838 // Make given DataSource available for the schema update,
839 // which unfortunately reinstantiates a ConnectionProvider.
840 configTimeDataSourceHolder.set(dataSource);
841 }
842 try {
843 updateDatabaseSchema();
844 }
845 finally {
846 if (dataSource != null) {
847 // Reset DataSource holder.
848 configTimeDataSourceHolder.set(null);
849 }
850 }
851 }
852 }
853
854 /**
855 * Allows for schema export on shutdown.
856 */
857 public void destroy() throws HibernateException {
858 DataSource dataSource = getDataSource();
859 if (dataSource != null) {
860 // Make given DataSource available for potential SchemaExport,
861 // which unfortunately reinstantiates a ConnectionProvider.
862 configTimeDataSourceHolder.set(dataSource);
863 }
864 try {
865 super.destroy();
866 }
867 finally {
868 if (dataSource != null) {
869 // Reset DataSource holder.
870 configTimeDataSourceHolder.set(null);
871 }
872 }
873 }
874
875
876 /**
877 * Execute schema drop script, determined by the Configuration object
878 * used for creating the SessionFactory. A replacement for Hibernate's
879 * SchemaExport class, to be invoked on application setup.
880 * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
881 * SessionFactory to be able to invoke this method, e.g. via
882 * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
883 * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
884 * connection to perform the script.
885 * @throws org.springframework.dao.DataAccessException in case of script execution errors
886 * @see org.hibernate.cfg.Configuration#generateDropSchemaScript
887 * @see org.hibernate.tool.hbm2ddl.SchemaExport#drop
888 */
889 public void dropDatabaseSchema() throws DataAccessException {
890 logger.info("Dropping database schema for Hibernate SessionFactory");
891 HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory());
892 hibernateTemplate.execute(
893 new HibernateCallback() {
894 public Object doInHibernate(Session session) throws HibernateException, SQLException {
895 Connection con = session.connection();
896 Dialect dialect = Dialect.getDialect(getConfiguration().getProperties());
897 String[] sql = getConfiguration().generateDropSchemaScript(dialect);
898 executeSchemaScript(con, sql);
899 return null;
900 }
901 }
902 );
903 }
904
905 /**
906 * Execute schema creation script, determined by the Configuration object
907 * used for creating the SessionFactory. A replacement for Hibernate's
908 * SchemaExport class, to be invoked on application setup.
909 * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
910 * SessionFactory to be able to invoke this method, e.g. via
911 * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
912 * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
913 * connection to perform the script.
914 * @throws DataAccessException in case of script execution errors
915 * @see org.hibernate.cfg.Configuration#generateSchemaCreationScript
916 * @see org.hibernate.tool.hbm2ddl.SchemaExport#create
917 */
918 public void createDatabaseSchema() throws DataAccessException {
919 logger.info("Creating database schema for Hibernate SessionFactory");
920 HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory());
921 hibernateTemplate.execute(
922 new HibernateCallback() {
923 public Object doInHibernate(Session session) throws HibernateException, SQLException {
924 Connection con = session.connection();
925 Dialect dialect = Dialect.getDialect(getConfiguration().getProperties());
926 String[] sql = getConfiguration().generateSchemaCreationScript(dialect);
927 executeSchemaScript(con, sql);
928 return null;
929 }
930 }
931 );
932 }
933
934 /**
935 * Execute schema update script, determined by the Configuration object
936 * used for creating the SessionFactory. A replacement for Hibernate's
937 * SchemaUpdate class, for automatically executing schema update scripts
938 * on application startup. Can also be invoked manually.
939 * <p>Fetch the LocalSessionFactoryBean itself rather than the exposed
940 * SessionFactory to be able to invoke this method, e.g. via
941 * <code>LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean) ctx.getBean("&mySessionFactory");</code>.
942 * <p>Uses the SessionFactory that this bean generates for accessing a JDBC
943 * connection to perform the script.
944 * @throws DataAccessException in case of script execution errors
945 * @see #setSchemaUpdate
946 * @see org.hibernate.cfg.Configuration#generateSchemaUpdateScript
947 * @see org.hibernate.tool.hbm2ddl.SchemaUpdate
948 */
949 public void updateDatabaseSchema() throws DataAccessException {
950 logger.info("Updating database schema for Hibernate SessionFactory");
951 HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory());
952 hibernateTemplate.setFlushMode(HibernateTemplate.FLUSH_NEVER);
953 hibernateTemplate.execute(
954 new HibernateCallback() {
955 public Object doInHibernate(Session session) throws HibernateException, SQLException {
956 Connection con = session.connection();
957 Dialect dialect = Dialect.getDialect(getConfiguration().getProperties());
958 DatabaseMetadata metadata = new DatabaseMetadata(con, dialect);
959 String[] sql = getConfiguration().generateSchemaUpdateScript(dialect, metadata);
960 executeSchemaScript(con, sql);
961 return null;
962 }
963 }
964 );
965 }
966
967 /**
968 * Execute the given schema script on the given JDBC Connection.
969 * <p>Note that the default implementation will log unsuccessful statements
970 * and continue to execute. Override the <code>executeSchemaStatement</code>
971 * method to treat failures differently.
972 * @param con the JDBC Connection to execute the script on
973 * @param sql the SQL statements to execute
974 * @throws SQLException if thrown by JDBC methods
975 * @see #executeSchemaStatement
976 */
977 protected void executeSchemaScript(Connection con, String[] sql) throws SQLException {
978 if (sql != null && sql.length > 0) {
979 boolean oldAutoCommit = con.getAutoCommit();
980 if (!oldAutoCommit) {
981 con.setAutoCommit(true);
982 }
983 try {
984 Statement stmt = con.createStatement();
985 try {
986 for (int i = 0; i < sql.length; i++) {
987 executeSchemaStatement(stmt, sql[i]);
988 }
989 }
990 finally {
991 JdbcUtils.closeStatement(stmt);
992 }
993 }
994 finally {
995 if (!oldAutoCommit) {
996 con.setAutoCommit(false);
997 }
998 }
999 }
1000 }
1001
1002 /**
1003 * Execute the given schema SQL on the given JDBC Statement.
1004 * <p>Note that the default implementation will log unsuccessful statements
1005 * and continue to execute. Override this method to treat failures differently.
1006 * @param stmt the JDBC Statement to execute the SQL on
1007 * @param sql the SQL statement to execute
1008 * @throws SQLException if thrown by JDBC methods (and considered fatal)
1009 */
1010 protected void executeSchemaStatement(Statement stmt, String sql) throws SQLException {
1011 if (logger.isDebugEnabled()) {
1012 logger.debug("Executing schema statement: " + sql);
1013 }
1014 try {
1015 stmt.executeUpdate(sql);
1016 }
1017 catch (SQLException ex) {
1018 if (logger.isWarnEnabled()) {
1019 logger.warn("Unsuccessful schema statement: " + sql, ex);
1020 }
1021 }
1022 }
1023
1024 }