001/* 002 * Copyright 2002-2020 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.IOException; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Proxy; 024import java.util.Collections; 025import java.util.Set; 026import java.util.TreeSet; 027import java.util.concurrent.Callable; 028import java.util.concurrent.ExecutionException; 029import java.util.concurrent.Future; 030 031import javax.persistence.AttributeConverter; 032import javax.persistence.Converter; 033import javax.persistence.Embeddable; 034import javax.persistence.Entity; 035import javax.persistence.MappedSuperclass; 036import javax.sql.DataSource; 037import javax.transaction.TransactionManager; 038 039import org.hibernate.HibernateException; 040import org.hibernate.MappingException; 041import org.hibernate.SessionFactory; 042import org.hibernate.boot.MetadataSources; 043import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; 044import org.hibernate.cache.spi.RegionFactory; 045import org.hibernate.cfg.AvailableSettings; 046import org.hibernate.cfg.Configuration; 047import org.hibernate.context.spi.CurrentTenantIdentifierResolver; 048import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; 049import org.hibernate.engine.spi.SessionFactoryImplementor; 050 051import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 052import org.springframework.core.InfrastructureProxy; 053import org.springframework.core.io.Resource; 054import org.springframework.core.io.ResourceLoader; 055import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 056import org.springframework.core.io.support.ResourcePatternResolver; 057import org.springframework.core.io.support.ResourcePatternUtils; 058import org.springframework.core.task.AsyncTaskExecutor; 059import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 060import org.springframework.core.type.classreading.MetadataReader; 061import org.springframework.core.type.classreading.MetadataReaderFactory; 062import org.springframework.core.type.filter.AnnotationTypeFilter; 063import org.springframework.core.type.filter.TypeFilter; 064import org.springframework.lang.Nullable; 065import org.springframework.transaction.jta.JtaTransactionManager; 066import org.springframework.util.Assert; 067import org.springframework.util.ClassUtils; 068 069/** 070 * A Spring-provided extension of the standard Hibernate {@link Configuration} class, 071 * adding {@link SpringSessionContext} as a default and providing convenient ways 072 * to specify a JDBC {@link DataSource} and an application class loader. 073 * 074 * <p>This is designed for programmatic use, e.g. in {@code @Bean} factory methods; 075 * consider using {@link LocalSessionFactoryBean} for XML bean definition files. 076 * Typically combined with {@link HibernateTransactionManager} for declarative 077 * transactions against the {@code SessionFactory} and its JDBC {@code DataSource}. 078 * 079 * <p>Compatible with Hibernate 5.0/5.1 as well as 5.2/5.3/5.4, as of Spring 5.2. 080 * Set up with Hibernate 5.2+, this builder is also a convenient way to set up 081 * a JPA {@code EntityManagerFactory} since the Hibernate {@code SessionFactory} 082 * natively exposes the JPA {@code EntityManagerFactory} interface as well now. 083 * 084 * <p>This builder supports Hibernate 5.3/5.4 {@code BeanContainer} integration, 085 * {@link MetadataSources} from custom {@link BootstrapServiceRegistryBuilder} 086 * setup, as well as other advanced Hibernate configuration options beyond the 087 * standard JPA bootstrap contract. 088 * 089 * @author Juergen Hoeller 090 * @since 4.2 091 * @see HibernateTransactionManager 092 * @see LocalSessionFactoryBean 093 * @see #setBeanContainer 094 * @see #LocalSessionFactoryBuilder(DataSource, ResourceLoader, MetadataSources) 095 * @see BootstrapServiceRegistryBuilder 096 */ 097@SuppressWarnings("serial") 098public class LocalSessionFactoryBuilder extends Configuration { 099 100 private static final String RESOURCE_PATTERN = "/**/*.class"; 101 102 private static final String PACKAGE_INFO_SUFFIX = ".package-info"; 103 104 private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] { 105 new AnnotationTypeFilter(Entity.class, false), 106 new AnnotationTypeFilter(Embeddable.class, false), 107 new AnnotationTypeFilter(MappedSuperclass.class, false)}; 108 109 private static final TypeFilter CONVERTER_TYPE_FILTER = new AnnotationTypeFilter(Converter.class, false); 110 111 112 private final ResourcePatternResolver resourcePatternResolver; 113 114 @Nullable 115 private TypeFilter[] entityTypeFilters = DEFAULT_ENTITY_TYPE_FILTERS; 116 117 118 /** 119 * Create a new LocalSessionFactoryBuilder for the given DataSource. 120 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using 121 * (may be {@code null}) 122 */ 123 public LocalSessionFactoryBuilder(@Nullable DataSource dataSource) { 124 this(dataSource, new PathMatchingResourcePatternResolver()); 125 } 126 127 /** 128 * Create a new LocalSessionFactoryBuilder for the given DataSource. 129 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using 130 * (may be {@code null}) 131 * @param classLoader the ClassLoader to load application classes from 132 */ 133 public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ClassLoader classLoader) { 134 this(dataSource, new PathMatchingResourcePatternResolver(classLoader)); 135 } 136 137 /** 138 * Create a new LocalSessionFactoryBuilder for the given DataSource. 139 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using 140 * (may be {@code null}) 141 * @param resourceLoader the ResourceLoader to load application classes from 142 */ 143 public LocalSessionFactoryBuilder(@Nullable DataSource dataSource, ResourceLoader resourceLoader) { 144 this(dataSource, resourceLoader, new MetadataSources( 145 new BootstrapServiceRegistryBuilder().applyClassLoader(resourceLoader.getClassLoader()).build())); 146 } 147 148 /** 149 * Create a new LocalSessionFactoryBuilder for the given DataSource. 150 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using 151 * (may be {@code null}) 152 * @param resourceLoader the ResourceLoader to load application classes from 153 * @param metadataSources the Hibernate MetadataSources service to use (e.g. reusing an existing one) 154 * @since 4.3 155 */ 156 public LocalSessionFactoryBuilder( 157 @Nullable DataSource dataSource, ResourceLoader resourceLoader, MetadataSources metadataSources) { 158 159 super(metadataSources); 160 161 getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); 162 if (dataSource != null) { 163 getProperties().put(AvailableSettings.DATASOURCE, dataSource); 164 } 165 166 // Hibernate 5.1/5.2: manually enforce connection release mode ON_CLOSE (the former default) 167 try { 168 // Try Hibernate 5.2 169 AvailableSettings.class.getField("CONNECTION_HANDLING"); 170 getProperties().put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_HOLD"); 171 } 172 catch (NoSuchFieldException ex) { 173 // Try Hibernate 5.1 174 try { 175 AvailableSettings.class.getField("ACQUIRE_CONNECTIONS"); 176 getProperties().put("hibernate.connection.release_mode", "ON_CLOSE"); 177 } 178 catch (NoSuchFieldException ex2) { 179 // on Hibernate 5.0.x or lower - no need to change the default there 180 } 181 } 182 183 getProperties().put(AvailableSettings.CLASSLOADERS, Collections.singleton(resourceLoader.getClassLoader())); 184 this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); 185 } 186 187 188 /** 189 * Set the Spring {@link JtaTransactionManager} or the JTA {@link TransactionManager} 190 * to be used with Hibernate, if any. Allows for using a Spring-managed transaction 191 * manager for Hibernate 5's session and cache synchronization, with the 192 * "hibernate.transaction.jta.platform" automatically set to it. 193 * <p>A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA 194 * {@link TransactionManager} reference to be usable here, except for the WebSphere 195 * case where we'll automatically set {@code WebSphereExtendedJtaPlatform} accordingly. 196 * <p>Note: If this is set, the Hibernate settings should not contain a JTA platform 197 * setting to avoid meaningless double configuration. 198 */ 199 public LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) { 200 Assert.notNull(jtaTransactionManager, "Transaction manager reference must not be null"); 201 202 if (jtaTransactionManager instanceof JtaTransactionManager) { 203 boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader()); 204 if (webspherePresent) { 205 getProperties().put(AvailableSettings.JTA_PLATFORM, 206 "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform"); 207 } 208 else { 209 JtaTransactionManager jtaTm = (JtaTransactionManager) jtaTransactionManager; 210 if (jtaTm.getTransactionManager() == null) { 211 throw new IllegalArgumentException( 212 "Can only apply JtaTransactionManager which has a TransactionManager reference set"); 213 } 214 getProperties().put(AvailableSettings.JTA_PLATFORM, 215 new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction(), 216 jtaTm.getTransactionSynchronizationRegistry())); 217 } 218 } 219 else if (jtaTransactionManager instanceof TransactionManager) { 220 getProperties().put(AvailableSettings.JTA_PLATFORM, 221 new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null, null)); 222 } 223 else { 224 throw new IllegalArgumentException( 225 "Unknown transaction manager type: " + jtaTransactionManager.getClass().getName()); 226 } 227 228 getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jta"); 229 230 // Hibernate 5.1/5.2: manually enforce connection release mode AFTER_STATEMENT (the JTA default) 231 try { 232 // Try Hibernate 5.2 233 AvailableSettings.class.getField("CONNECTION_HANDLING"); 234 getProperties().put("hibernate.connection.handling_mode", "DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT"); 235 } 236 catch (NoSuchFieldException ex) { 237 // Try Hibernate 5.1 238 try { 239 AvailableSettings.class.getField("ACQUIRE_CONNECTIONS"); 240 getProperties().put("hibernate.connection.release_mode", "AFTER_STATEMENT"); 241 } 242 catch (NoSuchFieldException ex2) { 243 // on Hibernate 5.0.x or lower - no need to change the default there 244 } 245 } 246 247 return this; 248 } 249 250 /** 251 * Set a Hibernate {@link org.hibernate.resource.beans.container.spi.BeanContainer} 252 * for the given Spring {@link ConfigurableListableBeanFactory}. 253 * <p>Note: Bean container integration requires Hibernate 5.3 or higher. 254 * It enables autowiring of Hibernate attribute converters and entity listeners. 255 * @since 5.1 256 * @see SpringBeanContainer 257 * @see AvailableSettings#BEAN_CONTAINER 258 */ 259 public LocalSessionFactoryBuilder setBeanContainer(ConfigurableListableBeanFactory beanFactory) { 260 getProperties().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)); 261 return this; 262 } 263 264 /** 265 * Set the Hibernate {@link RegionFactory} to use for the SessionFactory. 266 * Allows for using a Spring-managed {@code RegionFactory} instance. 267 * <p>Note: If this is set, the Hibernate settings should not define a 268 * cache provider to avoid meaningless double configuration. 269 * @since 5.1 270 * @see AvailableSettings#CACHE_REGION_FACTORY 271 */ 272 public LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegionFactory) { 273 getProperties().put(AvailableSettings.CACHE_REGION_FACTORY, cacheRegionFactory); 274 return this; 275 } 276 277 /** 278 * Set a {@link MultiTenantConnectionProvider} to be passed on to the SessionFactory. 279 * @since 4.3 280 * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER 281 */ 282 public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) { 283 getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider); 284 return this; 285 } 286 287 /** 288 * Overridden to reliably pass a {@link CurrentTenantIdentifierResolver} to the SessionFactory. 289 * @since 4.3.2 290 * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER 291 */ 292 @Override 293 public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { 294 getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver); 295 super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver); 296 } 297 298 /** 299 * Specify custom type filters for Spring-based scanning for entity classes. 300 * <p>Default is to search all specified packages for classes annotated with 301 * {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable} 302 * or {@code @javax.persistence.MappedSuperclass}. 303 * @see #scanPackages 304 */ 305 public LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) { 306 this.entityTypeFilters = entityTypeFilters; 307 return this; 308 } 309 310 /** 311 * Add the given annotated classes in a batch. 312 * @see #addAnnotatedClass 313 * @see #scanPackages 314 */ 315 public LocalSessionFactoryBuilder addAnnotatedClasses(Class<?>... annotatedClasses) { 316 for (Class<?> annotatedClass : annotatedClasses) { 317 addAnnotatedClass(annotatedClass); 318 } 319 return this; 320 } 321 322 /** 323 * Add the given annotated packages in a batch. 324 * @see #addPackage 325 * @see #scanPackages 326 */ 327 public LocalSessionFactoryBuilder addPackages(String... annotatedPackages) { 328 for (String annotatedPackage : annotatedPackages) { 329 addPackage(annotatedPackage); 330 } 331 return this; 332 } 333 334 /** 335 * Perform Spring-based scanning for entity classes, registering them 336 * as annotated classes with this {@code Configuration}. 337 * @param packagesToScan one or more Java package names 338 * @throws HibernateException if scanning fails for any reason 339 */ 340 @SuppressWarnings("unchecked") 341 public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws HibernateException { 342 Set<String> entityClassNames = new TreeSet<>(); 343 Set<String> converterClassNames = new TreeSet<>(); 344 Set<String> packageNames = new TreeSet<>(); 345 try { 346 for (String pkg : packagesToScan) { 347 String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 348 ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN; 349 Resource[] resources = this.resourcePatternResolver.getResources(pattern); 350 MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); 351 for (Resource resource : resources) { 352 if (resource.isReadable()) { 353 MetadataReader reader = readerFactory.getMetadataReader(resource); 354 String className = reader.getClassMetadata().getClassName(); 355 if (matchesEntityTypeFilter(reader, readerFactory)) { 356 entityClassNames.add(className); 357 } 358 else if (CONVERTER_TYPE_FILTER.match(reader, readerFactory)) { 359 converterClassNames.add(className); 360 } 361 else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { 362 packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); 363 } 364 } 365 } 366 } 367 } 368 catch (IOException ex) { 369 throw new MappingException("Failed to scan classpath for unlisted classes", ex); 370 } 371 try { 372 ClassLoader cl = this.resourcePatternResolver.getClassLoader(); 373 for (String className : entityClassNames) { 374 addAnnotatedClass(ClassUtils.forName(className, cl)); 375 } 376 for (String className : converterClassNames) { 377 addAttributeConverter((Class<? extends AttributeConverter<?, ?>>) ClassUtils.forName(className, cl)); 378 } 379 for (String packageName : packageNames) { 380 addPackage(packageName); 381 } 382 } 383 catch (ClassNotFoundException ex) { 384 throw new MappingException("Failed to load annotated classes from classpath", ex); 385 } 386 return this; 387 } 388 389 /** 390 * Check whether any of the configured entity type filters matches 391 * the current class descriptor contained in the metadata reader. 392 */ 393 private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { 394 if (this.entityTypeFilters != null) { 395 for (TypeFilter filter : this.entityTypeFilters) { 396 if (filter.match(reader, readerFactory)) { 397 return true; 398 } 399 } 400 } 401 return false; 402 } 403 404 /** 405 * Build the Hibernate {@code SessionFactory} through background bootstrapping, 406 * using the given executor for a parallel initialization phase 407 * (e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}). 408 * <p>{@code SessionFactory} initialization will then switch into background 409 * bootstrap mode, with a {@code SessionFactory} proxy immediately returned for 410 * injection purposes instead of waiting for Hibernate's bootstrapping to complete. 411 * However, note that the first actual call to a {@code SessionFactory} method will 412 * then block until Hibernate's bootstrapping completed, if not ready by then. 413 * For maximum benefit, make sure to avoid early {@code SessionFactory} calls 414 * in init methods of related beans, even for metadata introspection purposes. 415 * @since 4.3 416 * @see #buildSessionFactory() 417 */ 418 public SessionFactory buildSessionFactory(AsyncTaskExecutor bootstrapExecutor) { 419 Assert.notNull(bootstrapExecutor, "AsyncTaskExecutor must not be null"); 420 return (SessionFactory) Proxy.newProxyInstance(this.resourcePatternResolver.getClassLoader(), 421 new Class<?>[] {SessionFactoryImplementor.class, InfrastructureProxy.class}, 422 new BootstrapSessionFactoryInvocationHandler(bootstrapExecutor)); 423 } 424 425 426 /** 427 * Proxy invocation handler for background bootstrapping, only enforcing 428 * a fully initialized target {@code SessionFactory} when actually needed. 429 * @since 4.3 430 */ 431 private class BootstrapSessionFactoryInvocationHandler implements InvocationHandler { 432 433 private final Future<SessionFactory> sessionFactoryFuture; 434 435 public BootstrapSessionFactoryInvocationHandler(AsyncTaskExecutor bootstrapExecutor) { 436 this.sessionFactoryFuture = bootstrapExecutor.submit( 437 (Callable<SessionFactory>) LocalSessionFactoryBuilder.this::buildSessionFactory); 438 } 439 440 @Override 441 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 442 try { 443 if (method.getName().equals("equals")) { 444 // Only consider equal when proxies are identical. 445 return (proxy == args[0]); 446 } 447 else if (method.getName().equals("hashCode")) { 448 // Use hashCode of EntityManagerFactory proxy. 449 return System.identityHashCode(proxy); 450 } 451 else if (method.getName().equals("getProperties")) { 452 return getProperties(); 453 } 454 else if (method.getName().equals("getWrappedObject")) { 455 // Call coming in through InfrastructureProxy interface... 456 return getSessionFactory(); 457 } 458 // Regular delegation to the target SessionFactory, 459 // enforcing its full initialization... 460 return method.invoke(getSessionFactory(), args); 461 } 462 catch (InvocationTargetException ex) { 463 throw ex.getTargetException(); 464 } 465 } 466 467 private SessionFactory getSessionFactory() { 468 try { 469 return this.sessionFactoryFuture.get(); 470 } 471 catch (InterruptedException ex) { 472 Thread.currentThread().interrupt(); 473 throw new IllegalStateException("Interrupted during initialization of Hibernate SessionFactory", ex); 474 } 475 catch (ExecutionException ex) { 476 Throwable cause = ex.getCause(); 477 if (cause instanceof HibernateException) { 478 // Rethrow a provider configuration exception (possibly with a nested cause) directly 479 throw (HibernateException) cause; 480 } 481 throw new IllegalStateException("Failed to asynchronously initialize Hibernate SessionFactory: " + 482 ex.getMessage(), cause); 483 } 484 } 485 } 486 487}