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}