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.jpa; 018 019import java.io.Serializable; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Proxy; 024import java.util.LinkedHashSet; 025import java.util.Map; 026import java.util.Set; 027 028import javax.persistence.EntityManager; 029import javax.persistence.EntityManagerFactory; 030import javax.persistence.EntityTransaction; 031import javax.persistence.TransactionRequiredException; 032import javax.persistence.spi.PersistenceUnitInfo; 033import javax.persistence.spi.PersistenceUnitTransactionType; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037 038import org.springframework.core.Ordered; 039import org.springframework.dao.DataAccessException; 040import org.springframework.dao.support.PersistenceExceptionTranslator; 041import org.springframework.lang.Nullable; 042import org.springframework.transaction.support.ResourceHolderSynchronization; 043import org.springframework.transaction.support.TransactionSynchronizationManager; 044import org.springframework.util.Assert; 045import org.springframework.util.ClassUtils; 046import org.springframework.util.CollectionUtils; 047import org.springframework.util.ConcurrentReferenceHashMap; 048 049/** 050 * Delegate for creating a variety of {@link javax.persistence.EntityManager} 051 * proxies that follow the JPA spec's semantics for "extended" EntityManagers. 052 * 053 * <p>Supports several different variants of "extended" EntityManagers: 054 * in particular, an "application-managed extended EntityManager", as defined 055 * by {@link javax.persistence.EntityManagerFactory#createEntityManager()}, 056 * as well as a "container-managed extended EntityManager", as defined by 057 * {@link javax.persistence.PersistenceContextType#EXTENDED}. 058 * 059 * <p>The original difference between "application-managed" and "container-managed" 060 * was the need for explicit joining of an externally managed transaction through 061 * the {@link EntityManager#joinTransaction()} method in the "application" case 062 * versus the automatic joining on each user-level EntityManager operation in the 063 * "container" case. As of JPA 2.1, both join modes are available with both kinds of 064 * EntityManagers, so the difference between "application-" and "container-managed" 065 * is now primarily in the join mode default and in the restricted lifecycle of a 066 * container-managed EntityManager (i.e. tied to the object that it is injected into). 067 * 068 * @author Juergen Hoeller 069 * @author Rod Johnson 070 * @author Mark Paluch 071 * @since 2.0 072 * @see javax.persistence.EntityManagerFactory#createEntityManager() 073 * @see javax.persistence.PersistenceContextType#EXTENDED 074 * @see javax.persistence.EntityManager#joinTransaction() 075 * @see SharedEntityManagerCreator 076 */ 077public abstract class ExtendedEntityManagerCreator { 078 079 private static final Map<Class<?>, Class<?>[]> cachedEntityManagerInterfaces = new ConcurrentReferenceHashMap<>(4); 080 081 082 /** 083 * Create an application-managed extended EntityManager proxy. 084 * @param rawEntityManager the raw EntityManager to decorate 085 * @param emfInfo the EntityManagerFactoryInfo to obtain the JpaDialect 086 * and PersistenceUnitInfo from 087 * @return an application-managed EntityManager that can join transactions 088 * but does not participate in them automatically 089 */ 090 public static EntityManager createApplicationManagedEntityManager( 091 EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) { 092 093 return createProxy(rawEntityManager, emfInfo, false, false); 094 } 095 096 /** 097 * Create an application-managed extended EntityManager proxy. 098 * @param rawEntityManager the raw EntityManager to decorate 099 * @param emfInfo the EntityManagerFactoryInfo to obtain the JpaDialect 100 * and PersistenceUnitInfo from 101 * @param synchronizedWithTransaction whether to automatically join ongoing 102 * transactions (according to the JPA 2.1 SynchronizationType rules) 103 * @return an application-managed EntityManager that can join transactions 104 * but does not participate in them automatically 105 * @since 4.0 106 */ 107 public static EntityManager createApplicationManagedEntityManager( 108 EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo, boolean synchronizedWithTransaction) { 109 110 return createProxy(rawEntityManager, emfInfo, false, synchronizedWithTransaction); 111 } 112 113 /** 114 * Create a container-managed extended EntityManager proxy. 115 * @param rawEntityManager the raw EntityManager to decorate 116 * @param emfInfo the EntityManagerFactoryInfo to obtain the JpaDialect 117 * and PersistenceUnitInfo from 118 * @return a container-managed EntityManager that will automatically participate 119 * in any managed transaction 120 */ 121 public static EntityManager createContainerManagedEntityManager( 122 EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo) { 123 124 return createProxy(rawEntityManager, emfInfo, true, true); 125 } 126 127 /** 128 * Create a container-managed extended EntityManager proxy. 129 * @param emf the EntityManagerFactory to create the EntityManager with. 130 * If this implements the EntityManagerFactoryInfo interface, the corresponding 131 * JpaDialect and PersistenceUnitInfo will be detected accordingly. 132 * @return a container-managed EntityManager that will automatically participate 133 * in any managed transaction 134 * @see javax.persistence.EntityManagerFactory#createEntityManager() 135 */ 136 public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf) { 137 return createContainerManagedEntityManager(emf, null, true); 138 } 139 140 /** 141 * Create a container-managed extended EntityManager proxy. 142 * @param emf the EntityManagerFactory to create the EntityManager with. 143 * If this implements the EntityManagerFactoryInfo interface, the corresponding 144 * JpaDialect and PersistenceUnitInfo will be detected accordingly. 145 * @param properties the properties to be passed into the {@code createEntityManager} 146 * call (may be {@code null}) 147 * @return a container-managed EntityManager that will automatically participate 148 * in any managed transaction 149 * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map) 150 */ 151 public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties) { 152 return createContainerManagedEntityManager(emf, properties, true); 153 } 154 155 /** 156 * Create a container-managed extended EntityManager proxy. 157 * @param emf the EntityManagerFactory to create the EntityManager with. 158 * If this implements the EntityManagerFactoryInfo interface, the corresponding 159 * JpaDialect and PersistenceUnitInfo will be detected accordingly. 160 * @param properties the properties to be passed into the {@code createEntityManager} 161 * call (may be {@code null}) 162 * @param synchronizedWithTransaction whether to automatically join ongoing 163 * transactions (according to the JPA 2.1 SynchronizationType rules) 164 * @return a container-managed EntityManager that expects container-driven lifecycle 165 * management but may opt out of automatic transaction synchronization 166 * @since 4.0 167 * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map) 168 */ 169 public static EntityManager createContainerManagedEntityManager( 170 EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) { 171 172 Assert.notNull(emf, "EntityManagerFactory must not be null"); 173 if (emf instanceof EntityManagerFactoryInfo) { 174 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; 175 EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory(); 176 EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? 177 nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager()); 178 return createProxy(rawEntityManager, emfInfo, true, synchronizedWithTransaction); 179 } 180 else { 181 EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? 182 emf.createEntityManager(properties) : emf.createEntityManager()); 183 return createProxy(rawEntityManager, null, null, null, null, true, synchronizedWithTransaction); 184 } 185 } 186 187 188 /** 189 * Actually create the EntityManager proxy. 190 * @param rawEntityManager raw EntityManager 191 * @param emfInfo the EntityManagerFactoryInfo to obtain the JpaDialect 192 * and PersistenceUnitInfo from 193 * @param containerManaged whether to follow container-managed EntityManager 194 * or application-managed EntityManager semantics 195 * @param synchronizedWithTransaction whether to automatically join ongoing 196 * transactions (according to the JPA 2.1 SynchronizationType rules) 197 * @return the EntityManager proxy 198 */ 199 private static EntityManager createProxy(EntityManager rawEntityManager, 200 EntityManagerFactoryInfo emfInfo, boolean containerManaged, boolean synchronizedWithTransaction) { 201 202 Assert.notNull(emfInfo, "EntityManagerFactoryInfo must not be null"); 203 JpaDialect jpaDialect = emfInfo.getJpaDialect(); 204 PersistenceUnitInfo pui = emfInfo.getPersistenceUnitInfo(); 205 Boolean jta = (pui != null ? pui.getTransactionType() == PersistenceUnitTransactionType.JTA : null); 206 return createProxy(rawEntityManager, emfInfo.getEntityManagerInterface(), 207 emfInfo.getBeanClassLoader(), jpaDialect, jta, containerManaged, synchronizedWithTransaction); 208 } 209 210 /** 211 * Actually create the EntityManager proxy. 212 * @param rawEm raw EntityManager 213 * @param emIfc the (potentially vendor-specific) EntityManager 214 * interface to proxy, or {@code null} for default detection of all interfaces 215 * @param cl the ClassLoader to use for proxy creation (maybe {@code null}) 216 * @param exceptionTranslator the PersistenceException translator to use 217 * @param jta whether to create a JTA-aware EntityManager 218 * (or {@code null} if not known in advance) 219 * @param containerManaged whether to follow container-managed EntityManager 220 * or application-managed EntityManager semantics 221 * @param synchronizedWithTransaction whether to automatically join ongoing 222 * transactions (according to the JPA 2.1 SynchronizationType rules) 223 * @return the EntityManager proxy 224 */ 225 private static EntityManager createProxy( 226 EntityManager rawEm, @Nullable Class<? extends EntityManager> emIfc, @Nullable ClassLoader cl, 227 @Nullable PersistenceExceptionTranslator exceptionTranslator, @Nullable Boolean jta, 228 boolean containerManaged, boolean synchronizedWithTransaction) { 229 230 Assert.notNull(rawEm, "EntityManager must not be null"); 231 Class<?>[] interfaces; 232 233 if (emIfc != null) { 234 interfaces = cachedEntityManagerInterfaces.computeIfAbsent(emIfc, key -> { 235 Set<Class<?>> ifcs = new LinkedHashSet<>(4); 236 ifcs.add(key); 237 ifcs.add(EntityManagerProxy.class); 238 return ClassUtils.toClassArray(ifcs); 239 }); 240 } 241 else { 242 interfaces = cachedEntityManagerInterfaces.computeIfAbsent(rawEm.getClass(), key -> { 243 Set<Class<?>> ifcs = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(key, cl)); 244 ifcs.add(EntityManagerProxy.class); 245 return ClassUtils.toClassArray(ifcs); 246 }); 247 } 248 249 return (EntityManager) Proxy.newProxyInstance( 250 (cl != null ? cl : ExtendedEntityManagerCreator.class.getClassLoader()), 251 interfaces, 252 new ExtendedEntityManagerInvocationHandler( 253 rawEm, exceptionTranslator, jta, containerManaged, synchronizedWithTransaction)); 254 } 255 256 257 /** 258 * InvocationHandler for extended EntityManagers as defined in the JPA spec. 259 */ 260 @SuppressWarnings("serial") 261 private static final class ExtendedEntityManagerInvocationHandler implements InvocationHandler, Serializable { 262 263 private static final Log logger = LogFactory.getLog(ExtendedEntityManagerInvocationHandler.class); 264 265 private final EntityManager target; 266 267 @Nullable 268 private final PersistenceExceptionTranslator exceptionTranslator; 269 270 private final boolean jta; 271 272 private final boolean containerManaged; 273 274 private final boolean synchronizedWithTransaction; 275 276 private ExtendedEntityManagerInvocationHandler(EntityManager target, 277 @Nullable PersistenceExceptionTranslator exceptionTranslator, @Nullable Boolean jta, 278 boolean containerManaged, boolean synchronizedWithTransaction) { 279 280 this.target = target; 281 this.exceptionTranslator = exceptionTranslator; 282 this.jta = (jta != null ? jta : isJtaEntityManager()); 283 this.containerManaged = containerManaged; 284 this.synchronizedWithTransaction = synchronizedWithTransaction; 285 } 286 287 private boolean isJtaEntityManager() { 288 try { 289 this.target.getTransaction(); 290 return false; 291 } 292 catch (IllegalStateException ex) { 293 logger.debug("Cannot access EntityTransaction handle - assuming we're in a JTA environment"); 294 return true; 295 } 296 } 297 298 @Override 299 @Nullable 300 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 301 // Invocation on EntityManager interface coming in... 302 303 if (method.getName().equals("equals")) { 304 // Only consider equal when proxies are identical. 305 return (proxy == args[0]); 306 } 307 else if (method.getName().equals("hashCode")) { 308 // Use hashCode of EntityManager proxy. 309 return hashCode(); 310 } 311 else if (method.getName().equals("getTargetEntityManager")) { 312 // Handle EntityManagerProxy interface. 313 return this.target; 314 } 315 else if (method.getName().equals("unwrap")) { 316 // Handle JPA 2.0 unwrap method - could be a proxy match. 317 Class<?> targetClass = (Class<?>) args[0]; 318 if (targetClass == null) { 319 return this.target; 320 } 321 else if (targetClass.isInstance(proxy)) { 322 return proxy; 323 } 324 } 325 else if (method.getName().equals("isOpen")) { 326 if (this.containerManaged) { 327 return true; 328 } 329 } 330 else if (method.getName().equals("close")) { 331 if (this.containerManaged) { 332 throw new IllegalStateException("Invalid usage: Cannot close a container-managed EntityManager"); 333 } 334 ExtendedEntityManagerSynchronization synch = (ExtendedEntityManagerSynchronization) 335 TransactionSynchronizationManager.getResource(this.target); 336 if (synch != null) { 337 // Local transaction joined - don't actually call close() before transaction completion 338 synch.closeOnCompletion = true; 339 return null; 340 } 341 } 342 else if (method.getName().equals("getTransaction")) { 343 if (this.synchronizedWithTransaction) { 344 throw new IllegalStateException( 345 "Cannot obtain local EntityTransaction from a transaction-synchronized EntityManager"); 346 } 347 } 348 else if (method.getName().equals("joinTransaction")) { 349 doJoinTransaction(true); 350 return null; 351 } 352 else if (method.getName().equals("isJoinedToTransaction")) { 353 // Handle JPA 2.1 isJoinedToTransaction method for the non-JTA case. 354 if (!this.jta) { 355 return TransactionSynchronizationManager.hasResource(this.target); 356 } 357 } 358 359 // Do automatic joining if required. Excludes toString, equals, hashCode calls. 360 if (this.synchronizedWithTransaction && method.getDeclaringClass().isInterface()) { 361 doJoinTransaction(false); 362 } 363 364 // Invoke method on current EntityManager. 365 try { 366 return method.invoke(this.target, args); 367 } 368 catch (InvocationTargetException ex) { 369 throw ex.getTargetException(); 370 } 371 } 372 373 /** 374 * Join an existing transaction, if not already joined. 375 * @param enforce whether to enforce the transaction 376 * (i.e. whether failure to join is considered fatal) 377 */ 378 private void doJoinTransaction(boolean enforce) { 379 if (this.jta) { 380 // Let's try whether we're in a JTA transaction. 381 try { 382 this.target.joinTransaction(); 383 logger.debug("Joined JTA transaction"); 384 } 385 catch (TransactionRequiredException ex) { 386 if (!enforce) { 387 logger.debug("No JTA transaction to join: " + ex); 388 } 389 else { 390 throw ex; 391 } 392 } 393 } 394 else { 395 if (TransactionSynchronizationManager.isSynchronizationActive()) { 396 if (!TransactionSynchronizationManager.hasResource(this.target) && 397 !this.target.getTransaction().isActive()) { 398 enlistInCurrentTransaction(); 399 } 400 logger.debug("Joined local transaction"); 401 } 402 else { 403 if (!enforce) { 404 logger.debug("No local transaction to join"); 405 } 406 else { 407 throw new TransactionRequiredException("No local transaction to join"); 408 } 409 } 410 } 411 } 412 413 /** 414 * Enlist this application-managed EntityManager in the current transaction. 415 */ 416 private void enlistInCurrentTransaction() { 417 // Resource local transaction, need to acquire the EntityTransaction, 418 // start a transaction now and enlist a synchronization for commit or rollback later. 419 EntityTransaction et = this.target.getTransaction(); 420 et.begin(); 421 if (logger.isDebugEnabled()) { 422 logger.debug("Starting resource-local transaction on application-managed " + 423 "EntityManager [" + this.target + "]"); 424 } 425 ExtendedEntityManagerSynchronization extendedEntityManagerSynchronization = 426 new ExtendedEntityManagerSynchronization(this.target, this.exceptionTranslator); 427 TransactionSynchronizationManager.bindResource(this.target, extendedEntityManagerSynchronization); 428 TransactionSynchronizationManager.registerSynchronization(extendedEntityManagerSynchronization); 429 } 430 } 431 432 433 /** 434 * TransactionSynchronization enlisting an extended EntityManager 435 * with a current Spring transaction. 436 */ 437 private static class ExtendedEntityManagerSynchronization 438 extends ResourceHolderSynchronization<EntityManagerHolder, EntityManager> 439 implements Ordered { 440 441 private final EntityManager entityManager; 442 443 @Nullable 444 private final PersistenceExceptionTranslator exceptionTranslator; 445 446 public volatile boolean closeOnCompletion = false; 447 448 public ExtendedEntityManagerSynchronization( 449 EntityManager em, @Nullable PersistenceExceptionTranslator exceptionTranslator) { 450 451 super(new EntityManagerHolder(em), em); 452 this.entityManager = em; 453 this.exceptionTranslator = exceptionTranslator; 454 } 455 456 @Override 457 public int getOrder() { 458 return EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER - 1; 459 } 460 461 @Override 462 protected void flushResource(EntityManagerHolder resourceHolder) { 463 try { 464 this.entityManager.flush(); 465 } 466 catch (RuntimeException ex) { 467 throw convertException(ex); 468 } 469 } 470 471 @Override 472 protected boolean shouldReleaseBeforeCompletion() { 473 return false; 474 } 475 476 @Override 477 public void afterCommit() { 478 super.afterCommit(); 479 // Trigger commit here to let exceptions propagate to the caller. 480 try { 481 this.entityManager.getTransaction().commit(); 482 } 483 catch (RuntimeException ex) { 484 throw convertException(ex); 485 } 486 } 487 488 @Override 489 public void afterCompletion(int status) { 490 try { 491 super.afterCompletion(status); 492 if (status != STATUS_COMMITTED) { 493 // Haven't had an afterCommit call: trigger a rollback. 494 try { 495 this.entityManager.getTransaction().rollback(); 496 } 497 catch (RuntimeException ex) { 498 throw convertException(ex); 499 } 500 } 501 } 502 finally { 503 if (this.closeOnCompletion) { 504 EntityManagerFactoryUtils.closeEntityManager(this.entityManager); 505 } 506 } 507 } 508 509 private RuntimeException convertException(RuntimeException ex) { 510 DataAccessException dae = (this.exceptionTranslator != null) ? 511 this.exceptionTranslator.translateExceptionIfPossible(ex) : 512 EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex); 513 return (dae != null ? dae : ex); 514 } 515 } 516 517}