/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.orm.jpa.support;
import java.io.IOException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
Servlet Filter that binds a JPA EntityManager to the thread for the
entire processing of the request. Intended for the "Open EntityManager in
View" pattern, i.e. to allow for lazy loading in web views despite the
original transactions already being completed.
This filter makes JPA EntityManagers available via the current thread, which will be autodetected by transaction managers. It is suitable for service layer transactions via JpaTransactionManager
or JtaTransactionManager
as well as for non-transactional read-only execution.
Looks up the EntityManagerFactory in Spring's root web application context. Supports an "entityManagerFactoryBeanName" filter init-param in web.xml
; the default bean name is "entityManagerFactory". As an alternative, the "persistenceUnitName" init-param allows for retrieval by logical unit name (as specified in persistence.xml
).
Author: Juergen Hoeller See Also: Since: 2.0
/**
* Servlet Filter that binds a JPA EntityManager to the thread for the
* entire processing of the request. Intended for the "Open EntityManager in
* View" pattern, i.e. to allow for lazy loading in web views despite the
* original transactions already being completed.
*
* <p>This filter makes JPA EntityManagers available via the current thread,
* which will be autodetected by transaction managers. It is suitable for service
* layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
* or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
* as for non-transactional read-only execution.
*
* <p>Looks up the EntityManagerFactory in Spring's root web application context.
* Supports an "entityManagerFactoryBeanName" filter init-param in {@code web.xml};
* the default bean name is "entityManagerFactory". As an alternative, the
* "persistenceUnitName" init-param allows for retrieval by logical unit name
* (as specified in {@code persistence.xml}).
*
* @author Juergen Hoeller
* @since 2.0
* @see OpenEntityManagerInViewInterceptor
* @see org.springframework.orm.jpa.JpaTransactionManager
* @see org.springframework.orm.jpa.SharedEntityManagerCreator
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
Default EntityManagerFactory bean name: "entityManagerFactory".
Only applies when no "persistenceUnitName" param has been specified.
See Also: - setEntityManagerFactoryBeanName
- setPersistenceUnitName
/**
* Default EntityManagerFactory bean name: "entityManagerFactory".
* Only applies when no "persistenceUnitName" param has been specified.
* @see #setEntityManagerFactoryBeanName
* @see #setPersistenceUnitName
*/
public static final String DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";
@Nullable
private String entityManagerFactoryBeanName;
@Nullable
private String persistenceUnitName;
@Nullable
private volatile EntityManagerFactory entityManagerFactory;
Set the bean name of the EntityManagerFactory to fetch from Spring's
root application context.
Default is "entityManagerFactory". Note that this default only applies
when no "persistenceUnitName" param has been specified.
See Also: - setPersistenceUnitName
- DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
/**
* Set the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
* <p>Default is "entityManagerFactory". Note that this default only applies
* when no "persistenceUnitName" param has been specified.
* @see #setPersistenceUnitName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setEntityManagerFactoryBeanName(@Nullable String entityManagerFactoryBeanName) {
this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
}
Return the bean name of the EntityManagerFactory to fetch from Spring's
root application context.
/**
* Return the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
*/
@Nullable
protected String getEntityManagerFactoryBeanName() {
return this.entityManagerFactoryBeanName;
}
Set the name of the persistence unit to access the EntityManagerFactory for.
This is an alternative to specifying the EntityManagerFactory by bean name,
resolving it by its persistence unit name instead. If no bean name and no persistence
unit name have been specified, we'll check whether a bean exists for the default
bean name "entityManagerFactory"; if not, a default EntityManagerFactory will
be retrieved through finding a single unique bean of type EntityManagerFactory.
See Also: - setEntityManagerFactoryBeanName
- DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
/**
* Set the name of the persistence unit to access the EntityManagerFactory for.
* <p>This is an alternative to specifying the EntityManagerFactory by bean name,
* resolving it by its persistence unit name instead. If no bean name and no persistence
* unit name have been specified, we'll check whether a bean exists for the default
* bean name "entityManagerFactory"; if not, a default EntityManagerFactory will
* be retrieved through finding a single unique bean of type EntityManagerFactory.
* @see #setEntityManagerFactoryBeanName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setPersistenceUnitName(@Nullable String persistenceUnitName) {
this.persistenceUnitName = persistenceUnitName;
}
Return the name of the persistence unit to access the EntityManagerFactory for, if any.
/**
* Return the name of the persistence unit to access the EntityManagerFactory for, if any.
*/
@Nullable
protected String getPersistenceUnitName() {
return this.persistenceUnitName;
}
Returns "false" so that the filter may re-bind the opened EntityManager
to each asynchronously dispatched thread and postpone closing it until the very last asynchronous dispatch. /**
* Returns "false" so that the filter may re-bind the opened {@code EntityManager}
* to each asynchronously dispatched thread and postpone closing it until the very
* last asynchronous dispatch.
*/
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
Returns "false" so that the filter may provide an EntityManager
to each error dispatches. /**
* Returns "false" so that the filter may provide an {@code EntityManager}
* to each error dispatches.
*/
@Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
EntityManagerFactory emf = lookupEntityManagerFactory(request);
boolean participate = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
String key = getAlreadyFilteredAttributeName();
if (TransactionSynchronizationManager.hasResource(emf)) {
// Do not modify the EntityManager: just set the participate flag.
participate = true;
}
else {
boolean isFirstRequest = !isAsyncDispatch(request);
if (isFirstRequest || !applyEntityManagerBindingInterceptor(asyncManager, key)) {
logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
try {
EntityManager em = createEntityManager(emf);
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
asyncManager.registerCallableInterceptor(key, interceptor);
asyncManager.registerDeferredResultInterceptor(key, interceptor);
}
catch (PersistenceException ex) {
throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
}
}
}
try {
filterChain.doFilter(request, response);
}
finally {
if (!participate) {
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(emf);
if (!isAsyncStarted(request)) {
logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
}
}
Look up the EntityManagerFactory that this filter should use,
taking the current HTTP request as argument.
The default implementation delegates to the lookupEntityManagerFactory
without arguments, caching the EntityManagerFactory reference once obtained.
See Also: Returns: the EntityManagerFactory to use
/**
* Look up the EntityManagerFactory that this filter should use,
* taking the current HTTP request as argument.
* <p>The default implementation delegates to the {@code lookupEntityManagerFactory}
* without arguments, caching the EntityManagerFactory reference once obtained.
* @return the EntityManagerFactory to use
* @see #lookupEntityManagerFactory()
*/
protected EntityManagerFactory lookupEntityManagerFactory(HttpServletRequest request) {
EntityManagerFactory emf = this.entityManagerFactory;
if (emf == null) {
emf = lookupEntityManagerFactory();
this.entityManagerFactory = emf;
}
return emf;
}
Look up the EntityManagerFactory that this filter should use.
The default implementation looks for a bean with the specified name
in Spring's root application context.
See Also: Returns: the EntityManagerFactory to use
/**
* Look up the EntityManagerFactory that this filter should use.
* <p>The default implementation looks for a bean with the specified name
* in Spring's root application context.
* @return the EntityManagerFactory to use
* @see #getEntityManagerFactoryBeanName
*/
protected EntityManagerFactory lookupEntityManagerFactory() {
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
String emfBeanName = getEntityManagerFactoryBeanName();
String puName = getPersistenceUnitName();
if (StringUtils.hasLength(emfBeanName)) {
return wac.getBean(emfBeanName, EntityManagerFactory.class);
}
else if (!StringUtils.hasLength(puName) && wac.containsBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) {
return wac.getBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class);
}
else {
// Includes fallback search for single EntityManagerFactory bean by type.
return EntityManagerFactoryUtils.findEntityManagerFactory(wac, puName);
}
}
Create a JPA EntityManager to be bound to a request.
Can be overridden in subclasses.
Params: - emf – the EntityManagerFactory to use
See Also:
/**
* Create a JPA EntityManager to be bound to a request.
* <p>Can be overridden in subclasses.
* @param emf the EntityManagerFactory to use
* @see javax.persistence.EntityManagerFactory#createEntityManager()
*/
protected EntityManager createEntityManager(EntityManagerFactory emf) {
return emf.createEntityManager();
}
private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) {
CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key);
if (cpi == null) {
return false;
}
((AsyncRequestInterceptor) cpi).bindEntityManager();
return true;
}
}