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