001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.orm.hibernate5;
018
019import java.io.File;
020import java.io.IOException;
021import java.util.Properties;
022import javax.sql.DataSource;
023
024import org.hibernate.Interceptor;
025import org.hibernate.SessionFactory;
026import org.hibernate.boot.MetadataSources;
027import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
028import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
029import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
030import org.hibernate.cfg.Configuration;
031import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
032import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
033
034import org.springframework.beans.factory.DisposableBean;
035import org.springframework.beans.factory.FactoryBean;
036import org.springframework.beans.factory.InitializingBean;
037import org.springframework.context.ResourceLoaderAware;
038import org.springframework.core.io.ClassPathResource;
039import org.springframework.core.io.Resource;
040import org.springframework.core.io.ResourceLoader;
041import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
042import org.springframework.core.io.support.ResourcePatternResolver;
043import org.springframework.core.io.support.ResourcePatternUtils;
044import org.springframework.core.task.AsyncTaskExecutor;
045import org.springframework.core.type.filter.TypeFilter;
046import org.springframework.util.Assert;
047
048/**
049 * {@link FactoryBean} that creates a Hibernate
050 * {@link SessionFactory}. This is the usual way to set up a shared
051 * Hibernate SessionFactory in a Spring application context; the SessionFactory can
052 * then be passed to Hibernate-based data access objects via dependency injection.
053 *
054 * <p>Compatible with Hibernate 5.0/5.1 as well as 5.2, as of Spring 4.3.
055 *
056 * @author Juergen Hoeller
057 * @since 4.2
058 * @see #setDataSource
059 * @see #setPackagesToScan
060 * @see LocalSessionFactoryBuilder
061 */
062public class LocalSessionFactoryBean extends HibernateExceptionTranslator
063                implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
064
065        private DataSource dataSource;
066
067        private Resource[] configLocations;
068
069        private String[] mappingResources;
070
071        private Resource[] mappingLocations;
072
073        private Resource[] cacheableMappingLocations;
074
075        private Resource[] mappingJarLocations;
076
077        private Resource[] mappingDirectoryLocations;
078
079        private Interceptor entityInterceptor;
080
081        private ImplicitNamingStrategy implicitNamingStrategy;
082
083        private PhysicalNamingStrategy physicalNamingStrategy;
084
085        private Object jtaTransactionManager;
086
087        private MultiTenantConnectionProvider multiTenantConnectionProvider;
088
089        private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
090
091        private TypeFilter[] entityTypeFilters;
092
093        private Properties hibernateProperties;
094
095        private Class<?>[] annotatedClasses;
096
097        private String[] annotatedPackages;
098
099        private String[] packagesToScan;
100
101        private AsyncTaskExecutor bootstrapExecutor;
102
103        private boolean metadataSourcesAccessed = false;
104
105        private MetadataSources metadataSources;
106
107        private ResourcePatternResolver resourcePatternResolver;
108
109        private Configuration configuration;
110
111        private SessionFactory sessionFactory;
112
113
114        /**
115         * Set the DataSource to be used by the SessionFactory.
116         * If set, this will override corresponding settings in Hibernate properties.
117         * <p>If this is set, the Hibernate settings should not define
118         * a connection provider to avoid meaningless double configuration.
119         */
120        public void setDataSource(DataSource dataSource) {
121                this.dataSource = dataSource;
122        }
123
124        /**
125         * Set the location of a single Hibernate XML config file, for example as
126         * classpath resource "classpath:hibernate.cfg.xml".
127         * <p>Note: Can be omitted when all necessary properties and mapping
128         * resources are specified locally via this bean.
129         * @see Configuration#configure(java.net.URL)
130         */
131        public void setConfigLocation(Resource configLocation) {
132                this.configLocations = new Resource[] {configLocation};
133        }
134
135        /**
136         * Set the locations of multiple Hibernate XML config files, for example as
137         * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
138         * <p>Note: Can be omitted when all necessary properties and mapping
139         * resources are specified locally via this bean.
140         * @see Configuration#configure(java.net.URL)
141         */
142        public void setConfigLocations(Resource... configLocations) {
143                this.configLocations = configLocations;
144        }
145
146        /**
147         * Set Hibernate mapping resources to be found in the class path,
148         * like "example.hbm.xml" or "mypackage/example.hbm.xml".
149         * Analogous to mapping entries in a Hibernate XML config file.
150         * Alternative to the more generic setMappingLocations method.
151         * <p>Can be used to add to mappings from a Hibernate XML config file,
152         * or to specify all mappings locally.
153         * @see #setMappingLocations
154         * @see Configuration#addResource
155         */
156        public void setMappingResources(String... mappingResources) {
157                this.mappingResources = mappingResources;
158        }
159
160        /**
161         * Set locations of Hibernate mapping files, for example as classpath
162         * resource "classpath:example.hbm.xml". Supports any resource location
163         * via Spring's resource abstraction, for example relative paths like
164         * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
165         * <p>Can be used to add to mappings from a Hibernate XML config file,
166         * or to specify all mappings locally.
167         * @see Configuration#addInputStream
168         */
169        public void setMappingLocations(Resource... mappingLocations) {
170                this.mappingLocations = mappingLocations;
171        }
172
173        /**
174         * Set locations of cacheable Hibernate mapping files, for example as web app
175         * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
176         * via Spring's resource abstraction, as long as the resource can be resolved
177         * in the file system.
178         * <p>Can be used to add to mappings from a Hibernate XML config file,
179         * or to specify all mappings locally.
180         * @see Configuration#addCacheableFile(File)
181         */
182        public void setCacheableMappingLocations(Resource... cacheableMappingLocations) {
183                this.cacheableMappingLocations = cacheableMappingLocations;
184        }
185
186        /**
187         * Set locations of jar files that contain Hibernate mapping resources,
188         * like "WEB-INF/lib/example.hbm.jar".
189         * <p>Can be used to add to mappings from a Hibernate XML config file,
190         * or to specify all mappings locally.
191         * @see Configuration#addJar(File)
192         */
193        public void setMappingJarLocations(Resource... mappingJarLocations) {
194                this.mappingJarLocations = mappingJarLocations;
195        }
196
197        /**
198         * Set locations of directories that contain Hibernate mapping resources,
199         * like "WEB-INF/mappings".
200         * <p>Can be used to add to mappings from a Hibernate XML config file,
201         * or to specify all mappings locally.
202         * @see Configuration#addDirectory(File)
203         */
204        public void setMappingDirectoryLocations(Resource... mappingDirectoryLocations) {
205                this.mappingDirectoryLocations = mappingDirectoryLocations;
206        }
207
208        /**
209         * Set a Hibernate entity interceptor that allows to inspect and change
210         * property values before writing to and reading from the database.
211         * Will get applied to any new Session created by this factory.
212         * @see Configuration#setInterceptor
213         */
214        public void setEntityInterceptor(Interceptor entityInterceptor) {
215                this.entityInterceptor = entityInterceptor;
216        }
217
218        /**
219         * Set a Hibernate 5.0 ImplicitNamingStrategy for the SessionFactory.
220         * @see Configuration#setImplicitNamingStrategy
221         */
222        public void setImplicitNamingStrategy(ImplicitNamingStrategy implicitNamingStrategy) {
223                this.implicitNamingStrategy = implicitNamingStrategy;
224        }
225
226        /**
227         * Set a Hibernate 5.0 PhysicalNamingStrategy for the SessionFactory.
228         * @see Configuration#setPhysicalNamingStrategy
229         */
230        public void setPhysicalNamingStrategy(PhysicalNamingStrategy physicalNamingStrategy) {
231                this.physicalNamingStrategy = physicalNamingStrategy;
232        }
233
234        /**
235         * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager}
236         * or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate,
237         * if any. Implicitly sets up {@code JtaPlatform}.
238         * @see LocalSessionFactoryBuilder#setJtaTransactionManager
239         */
240        public void setJtaTransactionManager(Object jtaTransactionManager) {
241                this.jtaTransactionManager = jtaTransactionManager;
242        }
243
244        /**
245         * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory.
246         * @since 4.3
247         * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider
248         */
249        public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) {
250                this.multiTenantConnectionProvider = multiTenantConnectionProvider;
251        }
252
253        /**
254         * Set a {@link CurrentTenantIdentifierResolver} to be passed on to the SessionFactory.
255         * @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver
256         */
257        public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
258                this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
259        }
260
261        /**
262         * Specify custom type filters for Spring-based scanning for entity classes.
263         * <p>Default is to search all specified packages for classes annotated with
264         * {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
265         * or {@code @javax.persistence.MappedSuperclass}.
266         * @see #setPackagesToScan
267         */
268        public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
269                this.entityTypeFilters = entityTypeFilters;
270        }
271
272        /**
273         * Set Hibernate properties, such as "hibernate.dialect".
274         * <p>Note: Do not specify a transaction provider here when using
275         * Spring-driven transactions. It is also advisable to omit connection
276         * provider settings and use a Spring-set DataSource instead.
277         * @see #setDataSource
278         */
279        public void setHibernateProperties(Properties hibernateProperties) {
280                this.hibernateProperties = hibernateProperties;
281        }
282
283        /**
284         * Return the Hibernate properties, if any. Mainly available for
285         * configuration through property paths that specify individual keys.
286         */
287        public Properties getHibernateProperties() {
288                if (this.hibernateProperties == null) {
289                        this.hibernateProperties = new Properties();
290                }
291                return this.hibernateProperties;
292        }
293
294        /**
295         * Specify annotated entity classes to register with this Hibernate SessionFactory.
296         * @see Configuration#addAnnotatedClass(Class)
297         */
298        public void setAnnotatedClasses(Class<?>... annotatedClasses) {
299                this.annotatedClasses = annotatedClasses;
300        }
301
302        /**
303         * Specify the names of annotated packages, for which package-level
304         * annotation metadata will be read.
305         * @see Configuration#addPackage(String)
306         */
307        public void setAnnotatedPackages(String... annotatedPackages) {
308                this.annotatedPackages = annotatedPackages;
309        }
310
311        /**
312         * Specify packages to search for autodetection of your entity classes in the
313         * classpath. This is analogous to Spring's component-scan feature
314         * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
315         */
316        public void setPackagesToScan(String... packagesToScan) {
317                this.packagesToScan = packagesToScan;
318        }
319
320        /**
321         * Specify an asynchronous executor for background bootstrapping,
322         * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
323         * <p>{@code SessionFactory} initialization will then switch into background
324         * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for
325         * injection purposes instead of waiting for Hibernate's bootstrapping to complete.
326         * However, note that the first actual call to a {@code SessionFactory} method will
327         * then block until Hibernate's bootstrapping completed, if not ready by then.
328         * For maximum benefit, make sure to avoid early {@code SessionFactory} calls
329         * in init methods of related beans, even for metadata introspection purposes.
330         * @see LocalSessionFactoryBuilder#buildSessionFactory(AsyncTaskExecutor)
331         * @since 4.3
332         */
333        public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
334                this.bootstrapExecutor = bootstrapExecutor;
335        }
336
337        /**
338         * Specify a Hibernate {@link MetadataSources} service to use (e.g. reusing an
339         * existing one), potentially populated with a custom Hibernate bootstrap
340         * {@link org.hibernate.service.ServiceRegistry} as well.
341         * @since 4.3
342         */
343        public void setMetadataSources(MetadataSources metadataSources) {
344                Assert.notNull(metadataSources, "MetadataSources must not be null");
345                this.metadataSourcesAccessed = true;
346                this.metadataSources = metadataSources;
347        }
348
349        /**
350         * Determine the Hibernate {@link MetadataSources} to use.
351         * <p>Can also be externally called to initialize and pre-populate a {@link MetadataSources}
352         * instance which is then going to be used for {@link SessionFactory} building.
353         * @return the MetadataSources to use (never {@code null})
354         * @since 4.3
355         * @see LocalSessionFactoryBuilder#LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources)
356         */
357        public MetadataSources getMetadataSources() {
358                this.metadataSourcesAccessed = true;
359                if (this.metadataSources == null) {
360                        BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder();
361                        if (this.resourcePatternResolver != null) {
362                                builder = builder.applyClassLoader(this.resourcePatternResolver.getClassLoader());
363                        }
364                        this.metadataSources = new MetadataSources(builder.build());
365                }
366                return this.metadataSources;
367        }
368
369        /**
370         * Specify a Spring {@link ResourceLoader} to use for Hibernate metadata.
371         * @param resourceLoader the ResourceLoader to use (never {@code null})
372         */
373        @Override
374        public void setResourceLoader(ResourceLoader resourceLoader) {
375                this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
376        }
377
378        /**
379         * Determine the Spring {@link ResourceLoader} to use for Hibernate metadata.
380         * @return the ResourceLoader to use (never {@code null})
381         * @since 4.3
382         */
383        public ResourceLoader getResourceLoader() {
384                if (this.resourcePatternResolver == null) {
385                        this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
386                }
387                return this.resourcePatternResolver;
388        }
389
390
391        @Override
392        public void afterPropertiesSet() throws IOException {
393                if (this.metadataSources != null && !this.metadataSourcesAccessed) {
394                        // Repeated initialization with no user-customized MetadataSources -> clear it.
395                        this.metadataSources = null;
396                }
397
398                LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(
399                                this.dataSource, getResourceLoader(), getMetadataSources());
400
401                if (this.configLocations != null) {
402                        for (Resource resource : this.configLocations) {
403                                // Load Hibernate configuration from given location.
404                                sfb.configure(resource.getURL());
405                        }
406                }
407
408                if (this.mappingResources != null) {
409                        // Register given Hibernate mapping definitions, contained in resource files.
410                        for (String mapping : this.mappingResources) {
411                                Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
412                                sfb.addInputStream(mr.getInputStream());
413                        }
414                }
415
416                if (this.mappingLocations != null) {
417                        // Register given Hibernate mapping definitions, contained in resource files.
418                        for (Resource resource : this.mappingLocations) {
419                                sfb.addInputStream(resource.getInputStream());
420                        }
421                }
422
423                if (this.cacheableMappingLocations != null) {
424                        // Register given cacheable Hibernate mapping definitions, read from the file system.
425                        for (Resource resource : this.cacheableMappingLocations) {
426                                sfb.addCacheableFile(resource.getFile());
427                        }
428                }
429
430                if (this.mappingJarLocations != null) {
431                        // Register given Hibernate mapping definitions, contained in jar files.
432                        for (Resource resource : this.mappingJarLocations) {
433                                sfb.addJar(resource.getFile());
434                        }
435                }
436
437                if (this.mappingDirectoryLocations != null) {
438                        // Register all Hibernate mapping definitions in the given directories.
439                        for (Resource resource : this.mappingDirectoryLocations) {
440                                File file = resource.getFile();
441                                if (!file.isDirectory()) {
442                                        throw new IllegalArgumentException(
443                                                        "Mapping directory location [" + resource + "] does not denote a directory");
444                                }
445                                sfb.addDirectory(file);
446                        }
447                }
448
449                if (this.entityInterceptor != null) {
450                        sfb.setInterceptor(this.entityInterceptor);
451                }
452
453                if (this.implicitNamingStrategy != null) {
454                        sfb.setImplicitNamingStrategy(this.implicitNamingStrategy);
455                }
456
457                if (this.physicalNamingStrategy != null) {
458                        sfb.setPhysicalNamingStrategy(this.physicalNamingStrategy);
459                }
460
461                if (this.jtaTransactionManager != null) {
462                        sfb.setJtaTransactionManager(this.jtaTransactionManager);
463                }
464
465                if (this.multiTenantConnectionProvider != null) {
466                        sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
467                }
468
469                if (this.currentTenantIdentifierResolver != null) {
470                        sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
471                }
472
473                if (this.entityTypeFilters != null) {
474                        sfb.setEntityTypeFilters(this.entityTypeFilters);
475                }
476
477                if (this.hibernateProperties != null) {
478                        sfb.addProperties(this.hibernateProperties);
479                }
480
481                if (this.annotatedClasses != null) {
482                        sfb.addAnnotatedClasses(this.annotatedClasses);
483                }
484
485                if (this.annotatedPackages != null) {
486                        sfb.addPackages(this.annotatedPackages);
487                }
488
489                if (this.packagesToScan != null) {
490                        sfb.scanPackages(this.packagesToScan);
491                }
492
493                // Build SessionFactory instance.
494                this.configuration = sfb;
495                this.sessionFactory = buildSessionFactory(sfb);
496        }
497
498        /**
499         * Subclasses can override this method to perform custom initialization
500         * of the SessionFactory instance, creating it via the given Configuration
501         * object that got prepared by this LocalSessionFactoryBean.
502         * <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
503         * A custom implementation could prepare the instance in a specific way (e.g. applying
504         * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
505         * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
506         * @return the SessionFactory instance
507         * @see LocalSessionFactoryBuilder#buildSessionFactory
508         */
509        protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
510                return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
511                                sfb.buildSessionFactory());
512        }
513
514        /**
515         * Return the Hibernate Configuration object used to build the SessionFactory.
516         * Allows for access to configuration metadata stored there (rarely needed).
517         * @throws IllegalStateException if the Configuration object has not been initialized yet
518         */
519        public final Configuration getConfiguration() {
520                if (this.configuration == null) {
521                        throw new IllegalStateException("Configuration not initialized yet");
522                }
523                return this.configuration;
524        }
525
526
527        @Override
528        public SessionFactory getObject() {
529                return this.sessionFactory;
530        }
531
532        @Override
533        public Class<?> getObjectType() {
534                return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
535        }
536
537        @Override
538        public boolean isSingleton() {
539                return true;
540        }
541
542
543        @Override
544        public void destroy() {
545                this.sessionFactory.close();
546        }
547
548}