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