001/* 002 * Copyright 2002-2019 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.IOException; 020import java.io.ObjectInputStream; 021import java.io.Serializable; 022import java.lang.reflect.InvocationHandler; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.lang.reflect.Proxy; 026import java.util.HashSet; 027import java.util.LinkedHashMap; 028import java.util.Map; 029import java.util.Set; 030 031import javax.persistence.EntityManager; 032import javax.persistence.EntityManagerFactory; 033import javax.persistence.ParameterMode; 034import javax.persistence.Query; 035import javax.persistence.StoredProcedureQuery; 036import javax.persistence.TransactionRequiredException; 037 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040 041import org.springframework.lang.Nullable; 042import org.springframework.transaction.support.TransactionSynchronizationManager; 043import org.springframework.util.ClassUtils; 044import org.springframework.util.CollectionUtils; 045import org.springframework.util.ConcurrentReferenceHashMap; 046 047/** 048 * Delegate for creating a shareable JPA {@link javax.persistence.EntityManager} 049 * reference for a given {@link javax.persistence.EntityManagerFactory}. 050 * 051 * <p>A shared EntityManager will behave just like an EntityManager fetched from 052 * an application server's JNDI environment, as defined by the JPA specification. 053 * It will delegate all calls to the current transactional EntityManager, if any; 054 * otherwise it will fall back to a newly created EntityManager per operation. 055 * 056 * <p>For a behavioral definition of such a shared transactional EntityManager, 057 * see {@link javax.persistence.PersistenceContextType#TRANSACTION} and its 058 * discussion in the JPA spec document. This is also the default being used 059 * for the annotation-based {@link javax.persistence.PersistenceContext#type()}. 060 * 061 * @author Juergen Hoeller 062 * @author Rod Johnson 063 * @author Oliver Gierke 064 * @author Mark Paluch 065 * @since 2.0 066 * @see javax.persistence.PersistenceContext 067 * @see javax.persistence.PersistenceContextType#TRANSACTION 068 * @see org.springframework.orm.jpa.JpaTransactionManager 069 * @see ExtendedEntityManagerCreator 070 */ 071public abstract class SharedEntityManagerCreator { 072 073 private static final Class<?>[] NO_ENTITY_MANAGER_INTERFACES = new Class<?>[0]; 074 075 private static final Map<Class<?>, Class<?>[]> cachedQueryInterfaces = new ConcurrentReferenceHashMap<>(4); 076 077 private static final Set<String> transactionRequiringMethods = new HashSet<>(8); 078 079 private static final Set<String> queryTerminatingMethods = new HashSet<>(8); 080 081 static { 082 transactionRequiringMethods.add("joinTransaction"); 083 transactionRequiringMethods.add("flush"); 084 transactionRequiringMethods.add("persist"); 085 transactionRequiringMethods.add("merge"); 086 transactionRequiringMethods.add("remove"); 087 transactionRequiringMethods.add("refresh"); 088 089 queryTerminatingMethods.add("execute"); // JPA 2.1 StoredProcedureQuery 090 queryTerminatingMethods.add("executeUpdate"); 091 queryTerminatingMethods.add("getSingleResult"); 092 queryTerminatingMethods.add("getResultStream"); 093 queryTerminatingMethods.add("getResultList"); 094 queryTerminatingMethods.add("list"); // Hibernate Query.list() method 095 } 096 097 098 /** 099 * Create a transactional EntityManager proxy for the given EntityManagerFactory. 100 * @param emf the EntityManagerFactory to delegate to. 101 * @return a shareable transaction EntityManager proxy 102 */ 103 public static EntityManager createSharedEntityManager(EntityManagerFactory emf) { 104 return createSharedEntityManager(emf, null, true); 105 } 106 107 /** 108 * Create a transactional EntityManager proxy for the given EntityManagerFactory. 109 * @param emf the EntityManagerFactory to delegate to. 110 * @param properties the properties to be passed into the 111 * {@code createEntityManager} call (may be {@code null}) 112 * @return a shareable transaction EntityManager proxy 113 */ 114 public static EntityManager createSharedEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties) { 115 return createSharedEntityManager(emf, properties, true); 116 } 117 118 /** 119 * Create a transactional EntityManager proxy for the given EntityManagerFactory. 120 * @param emf the EntityManagerFactory to delegate to. 121 * @param properties the properties to be passed into the 122 * {@code createEntityManager} call (may be {@code null}) 123 * @param synchronizedWithTransaction whether to automatically join ongoing 124 * transactions (according to the JPA 2.1 SynchronizationType rules) 125 * @return a shareable transaction EntityManager proxy 126 * @since 4.0 127 */ 128 public static EntityManager createSharedEntityManager( 129 EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) { 130 131 Class<?> emIfc = (emf instanceof EntityManagerFactoryInfo ? 132 ((EntityManagerFactoryInfo) emf).getEntityManagerInterface() : EntityManager.class); 133 return createSharedEntityManager(emf, properties, synchronizedWithTransaction, 134 (emIfc == null ? NO_ENTITY_MANAGER_INTERFACES : new Class<?>[] {emIfc})); 135 } 136 137 /** 138 * Create a transactional EntityManager proxy for the given EntityManagerFactory. 139 * @param emf the EntityManagerFactory to obtain EntityManagers from as needed 140 * @param properties the properties to be passed into the 141 * {@code createEntityManager} call (may be {@code null}) 142 * @param entityManagerInterfaces the interfaces to be implemented by the 143 * EntityManager. Allows the addition or specification of proprietary interfaces. 144 * @return a shareable transactional EntityManager proxy 145 */ 146 public static EntityManager createSharedEntityManager( 147 EntityManagerFactory emf, @Nullable Map<?, ?> properties, Class<?>... entityManagerInterfaces) { 148 149 return createSharedEntityManager(emf, properties, true, entityManagerInterfaces); 150 } 151 152 /** 153 * Create a transactional EntityManager proxy for the given EntityManagerFactory. 154 * @param emf the EntityManagerFactory to obtain EntityManagers from as needed 155 * @param properties the properties to be passed into the 156 * {@code createEntityManager} call (may be {@code null}) 157 * @param synchronizedWithTransaction whether to automatically join ongoing 158 * transactions (according to the JPA 2.1 SynchronizationType rules) 159 * @param entityManagerInterfaces the interfaces to be implemented by the 160 * EntityManager. Allows the addition or specification of proprietary interfaces. 161 * @return a shareable transactional EntityManager proxy 162 * @since 4.0 163 */ 164 public static EntityManager createSharedEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties, 165 boolean synchronizedWithTransaction, Class<?>... entityManagerInterfaces) { 166 167 ClassLoader cl = null; 168 if (emf instanceof EntityManagerFactoryInfo) { 169 cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader(); 170 } 171 Class<?>[] ifcs = new Class<?>[entityManagerInterfaces.length + 1]; 172 System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length); 173 ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class; 174 return (EntityManager) Proxy.newProxyInstance( 175 (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()), 176 ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction)); 177 } 178 179 180 /** 181 * Invocation handler that delegates all calls to the current 182 * transactional EntityManager, if any; else, it will fall back 183 * to a newly created EntityManager per operation. 184 */ 185 @SuppressWarnings("serial") 186 private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable { 187 188 private final Log logger = LogFactory.getLog(getClass()); 189 190 private final EntityManagerFactory targetFactory; 191 192 @Nullable 193 private final Map<?, ?> properties; 194 195 private final boolean synchronizedWithTransaction; 196 197 @Nullable 198 private transient volatile ClassLoader proxyClassLoader; 199 200 public SharedEntityManagerInvocationHandler( 201 EntityManagerFactory target, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) { 202 203 this.targetFactory = target; 204 this.properties = properties; 205 this.synchronizedWithTransaction = synchronizedWithTransaction; 206 initProxyClassLoader(); 207 } 208 209 private void initProxyClassLoader() { 210 if (this.targetFactory instanceof EntityManagerFactoryInfo) { 211 this.proxyClassLoader = ((EntityManagerFactoryInfo) this.targetFactory).getBeanClassLoader(); 212 } 213 else { 214 this.proxyClassLoader = this.targetFactory.getClass().getClassLoader(); 215 } 216 } 217 218 @Override 219 @Nullable 220 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 221 // Invocation on EntityManager interface coming in... 222 223 if (method.getName().equals("equals")) { 224 // Only consider equal when proxies are identical. 225 return (proxy == args[0]); 226 } 227 else if (method.getName().equals("hashCode")) { 228 // Use hashCode of EntityManager proxy. 229 return hashCode(); 230 } 231 else if (method.getName().equals("toString")) { 232 // Deliver toString without touching a target EntityManager. 233 return "Shared EntityManager proxy for target factory [" + this.targetFactory + "]"; 234 } 235 else if (method.getName().equals("getEntityManagerFactory")) { 236 // JPA 2.0: return EntityManagerFactory without creating an EntityManager. 237 return this.targetFactory; 238 } 239 else if (method.getName().equals("getCriteriaBuilder") || method.getName().equals("getMetamodel")) { 240 // JPA 2.0: return EntityManagerFactory's CriteriaBuilder/Metamodel (avoid creation of EntityManager) 241 try { 242 return EntityManagerFactory.class.getMethod(method.getName()).invoke(this.targetFactory); 243 } 244 catch (InvocationTargetException ex) { 245 throw ex.getTargetException(); 246 } 247 } 248 else if (method.getName().equals("unwrap")) { 249 // JPA 2.0: handle unwrap method - could be a proxy match. 250 Class<?> targetClass = (Class<?>) args[0]; 251 if (targetClass != null && targetClass.isInstance(proxy)) { 252 return proxy; 253 } 254 } 255 else if (method.getName().equals("isOpen")) { 256 // Handle isOpen method: always return true. 257 return true; 258 } 259 else if (method.getName().equals("close")) { 260 // Handle close method: suppress, not valid. 261 return null; 262 } 263 else if (method.getName().equals("getTransaction")) { 264 throw new IllegalStateException( 265 "Not allowed to create transaction on shared EntityManager - " + 266 "use Spring transactions or EJB CMT instead"); 267 } 268 269 // Determine current EntityManager: either the transactional one 270 // managed by the factory or a temporary one for the given invocation. 271 EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager( 272 this.targetFactory, this.properties, this.synchronizedWithTransaction); 273 274 if (method.getName().equals("getTargetEntityManager")) { 275 // Handle EntityManagerProxy interface. 276 if (target == null) { 277 throw new IllegalStateException("No transactional EntityManager available"); 278 } 279 return target; 280 } 281 else if (method.getName().equals("unwrap")) { 282 Class<?> targetClass = (Class<?>) args[0]; 283 if (targetClass == null) { 284 return (target != null ? target : proxy); 285 } 286 // We need a transactional target now. 287 if (target == null) { 288 throw new IllegalStateException("No transactional EntityManager available"); 289 } 290 // Still perform unwrap call on target EntityManager. 291 } 292 else if (transactionRequiringMethods.contains(method.getName())) { 293 // We need a transactional target now, according to the JPA spec. 294 // Otherwise, the operation would get accepted but remain unflushed... 295 if (target == null || (!TransactionSynchronizationManager.isActualTransactionActive() && 296 !target.getTransaction().isActive())) { 297 throw new TransactionRequiredException("No EntityManager with actual transaction available " + 298 "for current thread - cannot reliably process '" + method.getName() + "' call"); 299 } 300 } 301 302 // Regular EntityManager operations. 303 boolean isNewEm = false; 304 if (target == null) { 305 logger.debug("Creating new EntityManager for shared EntityManager invocation"); 306 target = (!CollectionUtils.isEmpty(this.properties) ? 307 this.targetFactory.createEntityManager(this.properties) : 308 this.targetFactory.createEntityManager()); 309 isNewEm = true; 310 } 311 312 // Invoke method on current EntityManager. 313 try { 314 Object result = method.invoke(target, args); 315 if (result instanceof Query) { 316 Query query = (Query) result; 317 if (isNewEm) { 318 Class<?>[] ifcs = cachedQueryInterfaces.computeIfAbsent(query.getClass(), key -> 319 ClassUtils.getAllInterfacesForClass(key, this.proxyClassLoader)); 320 result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs, 321 new DeferredQueryInvocationHandler(query, target)); 322 isNewEm = false; 323 } 324 else { 325 EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory); 326 } 327 } 328 return result; 329 } 330 catch (InvocationTargetException ex) { 331 throw ex.getTargetException(); 332 } 333 finally { 334 if (isNewEm) { 335 EntityManagerFactoryUtils.closeEntityManager(target); 336 } 337 } 338 } 339 340 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 341 // Rely on default serialization, just initialize state after deserialization. 342 ois.defaultReadObject(); 343 // Initialize transient fields. 344 initProxyClassLoader(); 345 } 346 } 347 348 349 /** 350 * Invocation handler that handles deferred Query objects created by 351 * non-transactional createQuery invocations on a shared EntityManager. 352 * <p>Includes deferred output parameter access for JPA 2.1 StoredProcedureQuery, 353 * retrieving the corresponding values for all registered parameters on query 354 * termination and returning the locally cached values for subsequent access. 355 */ 356 private static class DeferredQueryInvocationHandler implements InvocationHandler { 357 358 private final Query target; 359 360 @Nullable 361 private EntityManager entityManager; 362 363 @Nullable 364 private Map<Object, Object> outputParameters; 365 366 public DeferredQueryInvocationHandler(Query target, EntityManager entityManager) { 367 this.target = target; 368 this.entityManager = entityManager; 369 } 370 371 @Override 372 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 373 // Invocation on Query interface coming in... 374 375 if (method.getName().equals("equals")) { 376 // Only consider equal when proxies are identical. 377 return (proxy == args[0]); 378 } 379 else if (method.getName().equals("hashCode")) { 380 // Use hashCode of EntityManager proxy. 381 return hashCode(); 382 } 383 else if (method.getName().equals("unwrap")) { 384 // Handle JPA 2.0 unwrap method - could be a proxy match. 385 Class<?> targetClass = (Class<?>) args[0]; 386 if (targetClass == null) { 387 return this.target; 388 } 389 else if (targetClass.isInstance(proxy)) { 390 return proxy; 391 } 392 } 393 else if (method.getName().equals("getOutputParameterValue")) { 394 if (this.entityManager == null) { 395 Object key = args[0]; 396 if (this.outputParameters == null || !this.outputParameters.containsKey(key)) { 397 throw new IllegalArgumentException("OUT/INOUT parameter not available: " + key); 398 } 399 Object value = this.outputParameters.get(key); 400 if (value instanceof IllegalArgumentException) { 401 throw (IllegalArgumentException) value; 402 } 403 return value; 404 } 405 } 406 407 // Invoke method on actual Query object. 408 try { 409 Object retVal = method.invoke(this.target, args); 410 if (method.getName().equals("registerStoredProcedureParameter") && args.length == 3 && 411 (args[2] == ParameterMode.OUT || args[2] == ParameterMode.INOUT)) { 412 if (this.outputParameters == null) { 413 this.outputParameters = new LinkedHashMap<>(); 414 } 415 this.outputParameters.put(args[0], null); 416 } 417 return (retVal == this.target ? proxy : retVal); 418 } 419 catch (InvocationTargetException ex) { 420 throw ex.getTargetException(); 421 } 422 finally { 423 if (queryTerminatingMethods.contains(method.getName())) { 424 // Actual execution of the query: close the EntityManager right 425 // afterwards, since that was the only reason we kept it open. 426 if (this.outputParameters != null && this.target instanceof StoredProcedureQuery) { 427 StoredProcedureQuery storedProc = (StoredProcedureQuery) this.target; 428 for (Map.Entry<Object, Object> entry : this.outputParameters.entrySet()) { 429 try { 430 Object key = entry.getKey(); 431 if (key instanceof Integer) { 432 entry.setValue(storedProc.getOutputParameterValue((Integer) key)); 433 } 434 else { 435 entry.setValue(storedProc.getOutputParameterValue(key.toString())); 436 } 437 } 438 catch (IllegalArgumentException ex) { 439 entry.setValue(ex); 440 } 441 } 442 } 443 EntityManagerFactoryUtils.closeEntityManager(this.entityManager); 444 this.entityManager = null; 445 } 446 } 447 } 448 } 449 450}