001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.orm.jpa;
018:
019: import javax.persistence.EntityManager;
020:
021: import org.aopalliance.intercept.MethodInterceptor;
022: import org.aopalliance.intercept.MethodInvocation;
023:
024: import org.springframework.transaction.support.TransactionSynchronizationManager;
025:
026: /**
027: * This interceptor binds a new JPA EntityManager to the thread before a method
028: * call, closing and removing it afterwards in case of any method outcome.
029: * If there already is a pre-bound EntityManager (e.g. from JpaTransactionManager,
030: * or from a surrounding JPA-intercepted method), the interceptor simply participates in it.
031: *
032: * <p>Application code must retrieve a JPA EntityManager via the
033: * <code>EntityManagerFactoryUtils.getEntityManager</code> method or - preferably -
034: * via a shared <code>EntityManager</code> reference, to be able to detect a
035: * thread-bound EntityManager. Typically, the code will look like as follows:
036: *
037: * <pre>
038: * public void doSomeDataAccessAction() {
039: * this.entityManager...
040: * }</pre>
041: *
042: * <p>Note that this interceptor automatically translates PersistenceExceptions,
043: * via delegating to the <code>EntityManagerFactoryUtils.convertJpaAccessException</code>
044: * method that converts them to exceptions that are compatible with the
045: * <code>org.springframework.dao</code> exception hierarchy (like JpaTemplate does).
046: *
047: * <p>This class can be considered a declarative alternative to JpaTemplate's
048: * callback approach. The advantages are:
049: * <ul>
050: * <li>no anonymous classes necessary for callback implementations;
051: * <li>the possibility to throw any application exceptions from within data access code.
052: * </ul>
053: *
054: * <p>The drawback is the dependency on interceptor configuration. However, note
055: * that this interceptor is usually <i>not</i> necessary in scenarios where the
056: * data access code always executes within transactions. A transaction will always
057: * have a thread-bound EntityManager in the first place, so adding this interceptor
058: * to the configuration just adds value when fine-tuning EntityManager settings
059: * like the flush mode - or when relying on exception translation.
060: *
061: * @author Juergen Hoeller
062: * @since 2.0
063: * @see JpaTransactionManager
064: * @see JpaTemplate
065: */
066: public class JpaInterceptor extends JpaAccessor implements
067: MethodInterceptor {
068:
069: private boolean exceptionConversionEnabled = true;
070:
071: /**
072: * Set whether to convert any PersistenceException raised to a Spring DataAccessException,
073: * compatible with the <code>org.springframework.dao</code> exception hierarchy.
074: * <p>Default is "true". Turn this flag off to let the caller receive raw exceptions
075: * as-is, without any wrapping.
076: * @see org.springframework.dao.DataAccessException
077: */
078: public void setExceptionConversionEnabled(
079: boolean exceptionConversionEnabled) {
080: this .exceptionConversionEnabled = exceptionConversionEnabled;
081: }
082:
083: public Object invoke(MethodInvocation methodInvocation)
084: throws Throwable {
085: // Determine current EntityManager: either the transactional one
086: // managed by the factory or a temporary one for the given invocation.
087: EntityManager em = getTransactionalEntityManager();
088: boolean isNewEm = false;
089: if (em == null) {
090: logger
091: .debug("Creating new EntityManager for JpaInterceptor invocation");
092: em = createEntityManager();
093: isNewEm = true;
094: TransactionSynchronizationManager.bindResource(
095: getEntityManagerFactory(), new EntityManagerHolder(
096: em));
097: }
098:
099: try {
100: Object retVal = methodInvocation.proceed();
101: flushIfNecessary(em, !isNewEm);
102: return retVal;
103: } catch (RuntimeException rawException) {
104: if (this .exceptionConversionEnabled) {
105: // Translation enabled. Translate if we understand the exception.
106: throw translateIfNecessary(rawException);
107: } else {
108: // Translation not enabled. Don't try to translate.
109: throw rawException;
110: }
111: } finally {
112: if (isNewEm) {
113: TransactionSynchronizationManager
114: .unbindResource(getEntityManagerFactory());
115: logger
116: .debug("Closing new EntityManager after JpaInterceptor invocation");
117: em.close();
118: }
119: }
120: }
121:
122: }
|