1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.orm.jpa;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.Serializable;
22 import java.lang.reflect.InvocationHandler;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Proxy;
26 import java.util.HashSet;
27 import java.util.Map;
28 import java.util.Set;
29 import javax.persistence.EntityManager;
30 import javax.persistence.EntityManagerFactory;
31 import javax.persistence.Query;
32 import javax.persistence.TransactionRequiredException;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import org.springframework.util.ClassUtils;
38 import org.springframework.util.CollectionUtils;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public abstract class SharedEntityManagerCreator {
64
65 private static final Class<?>[] NO_ENTITY_MANAGER_INTERFACES = new Class<?>[0];
66
67 private static final Set<String> transactionRequiringMethods = new HashSet<String>(6);
68
69 private static final Set<String> queryTerminationMethods = new HashSet<String>(3);
70
71 static {
72 transactionRequiringMethods.add("joinTransaction");
73 transactionRequiringMethods.add("flush");
74 transactionRequiringMethods.add("persist");
75 transactionRequiringMethods.add("merge");
76 transactionRequiringMethods.add("remove");
77 transactionRequiringMethods.add("refresh");
78
79 queryTerminationMethods.add("getResultList");
80 queryTerminationMethods.add("getSingleResult");
81 queryTerminationMethods.add("executeUpdate");
82 }
83
84
85
86
87
88
89
90 public static EntityManager createSharedEntityManager(EntityManagerFactory emf) {
91 return createSharedEntityManager(emf, null, true);
92 }
93
94
95
96
97
98
99
100
101 public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map<?, ?> properties) {
102 return createSharedEntityManager(emf, properties, true);
103 }
104
105
106
107
108
109
110
111
112
113
114
115 public static EntityManager createSharedEntityManager(
116 EntityManagerFactory emf, Map<?, ?> properties, boolean synchronizedWithTransaction) {
117 Class<?> entityManagerInterface = (emf instanceof EntityManagerFactoryInfo ?
118 ((EntityManagerFactoryInfo) emf).getEntityManagerInterface() : EntityManager.class);
119 return createSharedEntityManager(emf, properties, synchronizedWithTransaction,
120 (entityManagerInterface == null ? NO_ENTITY_MANAGER_INTERFACES :
121 new Class<?>[] { entityManagerInterface }));
122 }
123
124
125
126
127
128
129
130
131
132
133 public static EntityManager createSharedEntityManager(
134 EntityManagerFactory emf, Map<?, ?> properties, Class<?>... entityManagerInterfaces) {
135
136 return createSharedEntityManager(emf, properties, true, entityManagerInterfaces);
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151 public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map<?, ?> properties,
152 boolean synchronizedWithTransaction, Class<?>... entityManagerInterfaces) {
153
154 ClassLoader cl = null;
155 if (emf instanceof EntityManagerFactoryInfo) {
156 cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader();
157 }
158 Class<?>[] ifcs = new Class<?>[entityManagerInterfaces.length + 1];
159 System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length);
160 ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class;
161 return (EntityManager) Proxy.newProxyInstance(
162 (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
163 ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
164 }
165
166
167
168
169
170
171
172 @SuppressWarnings("serial")
173 private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
174
175 private final Log logger = LogFactory.getLog(getClass());
176
177 private final EntityManagerFactory targetFactory;
178
179 private final Map<?, ?> properties;
180
181 private final boolean synchronizedWithTransaction;
182
183 private transient volatile ClassLoader proxyClassLoader;
184
185 public SharedEntityManagerInvocationHandler(
186 EntityManagerFactory target, Map<?, ?> properties, boolean synchronizedWithTransaction) {
187 this.targetFactory = target;
188 this.properties = properties;
189 this.synchronizedWithTransaction = synchronizedWithTransaction;
190 initProxyClassLoader();
191 }
192
193 private void initProxyClassLoader() {
194 if (this.targetFactory instanceof EntityManagerFactoryInfo) {
195 this.proxyClassLoader = ((EntityManagerFactoryInfo) this.targetFactory).getBeanClassLoader();
196 }
197 else {
198 this.proxyClassLoader = this.targetFactory.getClass().getClassLoader();
199 }
200 }
201
202 @Override
203 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
204
205
206 if (method.getName().equals("equals")) {
207
208 return (proxy == args[0]);
209 }
210 else if (method.getName().equals("hashCode")) {
211
212 return hashCode();
213 }
214 else if (method.getName().equals("toString")) {
215
216 return "Shared EntityManager proxy for target factory [" + this.targetFactory + "]";
217 }
218 else if (method.getName().equals("getEntityManagerFactory")) {
219
220 return this.targetFactory;
221 }
222 else if (method.getName().equals("getCriteriaBuilder") || method.getName().equals("getMetamodel")) {
223
224 try {
225 return EntityManagerFactory.class.getMethod(method.getName()).invoke(this.targetFactory);
226 }
227 catch (InvocationTargetException ex) {
228 throw ex.getTargetException();
229 }
230 }
231 else if (method.getName().equals("unwrap")) {
232
233 Class<?> targetClass = (Class<?>) args[0];
234 if (targetClass == null || targetClass.isInstance(proxy)) {
235 return proxy;
236 }
237 }
238 else if (method.getName().equals("isOpen")) {
239
240 return true;
241 }
242 else if (method.getName().equals("close")) {
243
244 return null;
245 }
246 else if (method.getName().equals("getTransaction")) {
247 throw new IllegalStateException(
248 "Not allowed to create transaction on shared EntityManager - " +
249 "use Spring transactions or EJB CMT instead");
250 }
251
252
253
254 EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager(
255 this.targetFactory, this.properties, this.synchronizedWithTransaction);
256
257 if (method.getName().equals("getTargetEntityManager")) {
258
259 if (target == null) {
260 throw new IllegalStateException("No transactional EntityManager available");
261 }
262 return target;
263 }
264 else if (method.getName().equals("unwrap")) {
265
266 if (target == null) {
267 throw new IllegalStateException("No transactional EntityManager available");
268 }
269
270 }
271 else if (transactionRequiringMethods.contains(method.getName())) {
272
273
274 if (target == null) {
275 throw new TransactionRequiredException("No transactional EntityManager available");
276 }
277 }
278
279
280 boolean isNewEm = false;
281 if (target == null) {
282 logger.debug("Creating new EntityManager for shared EntityManager invocation");
283 target = (!CollectionUtils.isEmpty(this.properties) ?
284 this.targetFactory.createEntityManager(this.properties) :
285 this.targetFactory.createEntityManager());
286 isNewEm = true;
287 }
288
289
290 try {
291 Object result = method.invoke(target, args);
292 if (result instanceof Query) {
293 Query query = (Query) result;
294 if (isNewEm) {
295 Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(query.getClass(), this.proxyClassLoader);
296 result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
297 new DeferredQueryInvocationHandler(query, target));
298 isNewEm = false;
299 }
300 else {
301 EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
302 }
303 }
304 return result;
305 }
306 catch (InvocationTargetException ex) {
307 throw ex.getTargetException();
308 }
309 finally {
310 if (isNewEm) {
311 EntityManagerFactoryUtils.closeEntityManager(target);
312 }
313 }
314 }
315
316 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
317
318 ois.defaultReadObject();
319
320 initProxyClassLoader();
321 }
322 }
323
324
325
326
327
328
329 private static class DeferredQueryInvocationHandler implements InvocationHandler {
330
331 private final Query target;
332
333 private EntityManager em;
334
335 public DeferredQueryInvocationHandler(Query target, EntityManager em) {
336 this.target = target;
337 this.em = em;
338 }
339
340 @Override
341 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
342
343
344 if (method.getName().equals("equals")) {
345
346 return (proxy == args[0]);
347 }
348 else if (method.getName().equals("hashCode")) {
349
350 return hashCode();
351 }
352 else if (method.getName().equals("unwrap")) {
353
354 Class<?> targetClass = (Class<?>) args[0];
355 if (targetClass == null || targetClass.isInstance(proxy)) {
356 return proxy;
357 }
358 }
359
360
361 try {
362 Object retVal = method.invoke(this.target, args);
363 return (retVal == this.target ? proxy : retVal);
364 }
365 catch (InvocationTargetException ex) {
366 throw ex.getTargetException();
367 }
368 finally {
369 if (queryTerminationMethods.contains(method.getName())) {
370
371
372 EntityManagerFactoryUtils.closeEntityManager(this.em);
373 this.em = null;
374 }
375 }
376 }
377
378 @Override
379 protected void finalize() throws Throwable {
380
381
382
383
384 EntityManagerFactoryUtils.closeEntityManager(this.em);
385 super.finalize();
386 }
387 }
388
389 }