Talk about OpenSessionInView of spring data jpa

Talk about OpenSessionInView of spring data jpa

sequence

This article mainly studies the OpenSessionInView of spring data jpa

Open Session In View

  • Open Session In View is abbreviated as OSIV, which is to solve the LazyInitializationException thrown by the session when the lazy load property of hibernate is used in the mvc controller; for hibernate, the ToMany relationship defaults to lazy loading, while the ToOne relationship defaults to immediate load

JpaProperties

spring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java

@ConfigurationProperties(prefix = "spring.jpa")
public class JpaProperties {

	/**
	 * Additional native properties to set on the JPA provider.
	 */
	private Map<String, String> properties = new HashMap<>();

	/**
	 * Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
	 */
	private final List<String> mappingResources = new ArrayList<>();

	/**
	 * Name of the target database to operate on, auto-detected by default. Can be
	 * alternatively set using the "Database" enum.
	 */
	private String databasePlatform;

	/**
	 * Target database to operate on, auto-detected by default. Can be alternatively set
	 * using the "databasePlatform" property.
	 */
	private Database database;

	/**
	 * Whether to initialize the schema on startup.
	 */
	private boolean generateDdl = false;

	/**
	 * Whether to enable logging of SQL statements.
	 */
	private boolean showSql = false;

	/**
	 * Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the
	 * thread for the entire processing of the request.
	 */
	private Boolean openInView;

	//......
}
 
  • JpaProperties has a configuration item of openInView( true), which is used to determine whether to register OpenEntityManagerInViewInterceptor, which binds a JPA EntityManager to a request thread

JpaBaseConfiguration

spring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java

@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
	//......

	@Configuration
	@ConditionalOnWebApplication(type = Type.SERVLET)
	@ConditionalOnClass(WebMvcConfigurer.class)
	@ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class,
			OpenEntityManagerInViewFilter.class })
	@ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
	@ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view",
			havingValue = "true", matchIfMissing = true)
	protected static class JpaWebConfiguration {

		//Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when
		//not on the classpath
		@Configuration
		protected static class JpaWebMvcConfiguration implements WebMvcConfigurer {

			private static final Log logger = LogFactory
					.getLog(JpaWebMvcConfiguration.class);

			private final JpaProperties jpaProperties;

			protected JpaWebMvcConfiguration(JpaProperties jpaProperties) {
				this.jpaProperties = jpaProperties;
			}

			@Bean
			public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
				if (this.jpaProperties.getOpenInView() == null) {
					logger.warn("spring.jpa.open-in-view is enabled by default. "
							+ "Therefore, database queries may be performed during view "
							+ "rendering. Explicitly configure "
							+ "spring.jpa.open-in-view to disable this warning");
				}
				return new OpenEntityManagerInViewInterceptor();
			}

			@Override
			public void addInterceptors(InterceptorRegistry registry) {
				registry.addWebRequestInterceptor(openEntityManagerInViewInterceptor());
			}

		}

	}

	//......
}
 
  • There is a JpaWebMvcConfiguration configuration in JpaBaseConfiguration. When the web application type is Type.SERVLET and spring.jpa.open-in-view is not false, register OpenEntityManagerInViewInterceptor, and then add it to mvc's webRequestInterceptor

OpenEntityManagerInViewInterceptor

spring-orm-5.1.6.RELEASE-sources.jar!/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java

public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor {

	/**
	 * Suffix that gets appended to the EntityManagerFactory toString
	 * representation for the "participate in existing entity manager
	 * handling" request attribute.
	 * @see #getParticipateAttributeName
	 */
	public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";


	@Override
	public void preHandle(WebRequest request) throws DataAccessException {
		String key = getParticipateAttributeName();
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) {
			return;
		}

		EntityManagerFactory emf = obtainEntityManagerFactory();
		if (TransactionSynchronizationManager.hasResource(emf)) {
			//Do not modify the EntityManager: just mark the request accordingly.
			Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST);
			int newCount = (count != null ? count + 1 : 1);
			request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
		}
		else {
			logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
			try {
				EntityManager em = createEntityManager();
				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);
			}
		}
	}

	@Override
	public void postHandle(WebRequest request, @Nullable ModelMap model) {
	}

	@Override
	public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException {
		if (!decrementParticipateCount(request)) {
			EntityManagerHolder emHolder = (EntityManagerHolder)
					TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory());
			logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
			EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
		}
	}

	private boolean decrementParticipateCount(WebRequest request) {
		String participateAttributeName = getParticipateAttributeName();
		Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
		if (count == null) {
			return false;
		}
		//Do not modify the Session: just clear the marker.
		if (count > 1) {
			request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
		}
		else {
			request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
		}
		return true;
	}

	@Override
	public void afterConcurrentHandlingStarted(WebRequest request) {
		if (!decrementParticipateCount(request)) {
			TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory());
		}
	}

	/**
	 * Return the name of the request attribute that identifies that a request is
	 * already filtered. Default implementation takes the toString representation
	 * of the EntityManagerFactory instance and appends ".FILTERED".
	 * @see #PARTICIPATE_SUFFIX
	 */
	protected String getParticipateAttributeName() {
		return obtainEntityManagerFactory().toString() + PARTICIPATE_SUFFIX;
	}


	private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) {
		CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key);
		if (cpi == null) {
			return false;
		}
		((AsyncRequestInterceptor) cpi).bindEntityManager();
		return true;
	}

}
 
  • OpenEntityManagerInViewInterceptor inherits the abstract class EntityManagerFactoryAccessor and implements the AsyncWebRequestInterceptor interface ( afterConcurrentHandlingStarted ); AsyncWebRequestInterceptor inherits the WebRequestInterceptor ( preHandle postHandle afterCompletion )
  • The preHandle method will determine whether the current thread has an EntityManagerFactory, if so, it will maintain the count in the attribute of the request; if not, it will create an EntityManager( openSession), and then use TransactionSynchronizationManager.bindResource for binding
  • The afterCompletion method will first decrement the count in the request attribute ( ), and remove the attribute when the count is 0; if the request does not have a count, use TransactionSynchronizationManager.unbindResource to unbind, and then close the EntityManager; the asynchronous afterConcurrentHandlingStarted method is similar, Mainly for unbind operation

summary

  • For hibernate, the ToMany relationship is lazy loading by default, and the ToOne relationship is loaded immediately by default; and the permanent contenxt is detached from the mvc controller, so the entity becomes detached. At this time, it will be used when the lazy loading property is used. Throws LazyInitializationException exception, and Open Session In View refers to solving this problem
  • There is a JpaWebMvcConfiguration configuration in JpaBaseConfiguration. When the web application type is Type.SERVLET and spring.jpa.open-in-view is not false, register OpenEntityManagerInViewInterceptor, and then add it to mvc's webRequestInterceptor
  • The preHandle method of OpenEntityManagerInViewInterceptor will determine whether the current thread has an EntityManagerFactory, if not, it will create an EntityManager( openSession), and then use TransactionSynchronizationManager.bindResource to bind to the current thread; the afterCompletion method will use TransactionSynchronizationManager.unbindResource to unbind and then close the EntityManager

Solving the LazyInitialization problem through OSIV technology will cause the open session life cycle to be too long, it runs through the entire request, and the session can be closed to release the database connection after the view is rendered; in addition, OSIV exposes the technical details of the service layer to the controller layer, causing certain Therefore, it is not recommended to enable it. The corresponding solution is to use dto in the controller layer instead of the entity in the detached state. The required data no longer depends on delayed loading, and it is explicitly queried as needed when assembling dto.

doc