Close the database connection in the open session in view mode in advance

background:

   Open session in view provides lazy loading the function of dynamically accessing late-loading data in the control layer or freemarker page. But the disadvantage is also obvious, that is, the database connection can only be closed after all pages are written to the browser, which is unbearable for a scarce resource such as a database. So can the database connection be closed immediately after the view layer such as freemarker obtains the late-loaded data?

 

plan:

    After the freemarker template is written once, the parameters in the template have been loaded, and the session is closed in advance.

Specific steps:

    1) Change the ftl class in struts-plugin.xml to:
<bean type="com.skyon.webframe.core.struts2.ResultProxy" name="ftl" class="com.skyon.opensessioniv.FreemarkerResultProxy" />
 This new FreemarkerResultProxy returns a new Result, which writes the template empty again:

 

						nullWrite(template, model);
						template.process(model, parentCharArrayWriter);

 Then close the connection: 

 

	// Write the template again and close the session of OpenSessionInViewAnyCloseFilter in advance. In this way, the parameters in the template will be accessed again to ensure that the parameters inside are available.
	private void nullWrite(Template template, TemplateModel model) throws TemplateException, IOException {

		template.process(model, NullWriter.NULL_WRITER);
		LOG.debug("nullWrite template ok.");
		OpenSessionInViewAnyCloseFilter instance = OpenSessionInViewAnyCloseFilter.getInstance();
		if(instance!=null && instance.isInFilter()) {
			instance.closeSession();
			LOG.debug("OpenSessionInViewAnyCloseFilter.close() ok.");
		}else {
			LOG.debug("OpenSessionInViewAnyCloseFilter.instance is null.");
		}
	}

 

 

 

2) Change the filter section of web.xml to:
 

<filter>
   <filter-name>opensession</filter-name>
   <filter-class>com.skyon.opensessioniv.OpenSessionInViewAnyCloseFilter</filter-class>
   <init-param>
    <param-name>singleSession</param-name>
    <param-value>false</param-value>
   </init-param> 
  </filter>   
  <filter>
      other filters
  </filter>
  
  <filter-mapping>
   <filter-name>opensession</filter-name>
   <url-pattern>/params/*</url-pattern>
  </filter-mapping>
  <!-- Multiple url-patterns of opensession write multiple <filter-mapping> -->
  <!-- <filter-mapping>
   <filter-name>opensession</filter-name>
   <url-pattern>/params/dict/*</url-pattern>
  </filter-mapping> -->

  

  Note: 1) opensessioninview must be placed at the top
             2) url-pattern must be added, otherwise each request (such as js, image request) must switch session.
 And this new opensessioninview filer provides a method closeSession() to close the database connection in advance:

/*
 * Copyright 2002-2009 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 com.skyon.opensessioniv;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the
 * entire processing of the request. Intended for the "Open Session in View"
 * pattern, i.e. to allow for lazy loading in web views despite the original
 * transactions already being completed.
 *
 * <p>
 * This filter makes Hibernate Sessions available via the current thread, which
 * will be autodetected by transaction managers. It is suitable for service
 * layer transactions via
 * {@link org.springframework.orm.hibernate3.HibernateTransactionManager} or
 * {@link org.springframework.transaction.jta.JtaTransactionManager} as well as
 * for non-transactional execution (if configured appropriately).
 *
 * <p>
 * <b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate
 * Session, with the flush mode set to <code>FlushMode.NEVER</code>. It assumes
 * to be used in combination with service layer transactions that care for the
 * flushing: The active transaction manager will temporarily change the flush
 * mode to <code>FlushMode.AUTO</code> during a read-write transaction, with the
 * flush mode reset to <code>FlushMode.NEVER</code> at the end of each
 * transaction. If you intend to use this filter without transactions, consider
 * changing the default flush mode (through the "flushMode" property).
 *
 * <p>
 * <b>WARNING:</b> Applying this filter to existing logic can cause issues that
 * have not appeared before, through the use of a single Hibernate Session for
 * the processing of an entire request. In particular, the reassociation of
 * persistent objects with a Hibernate Session has to occur at the very
 * beginning of request processing, to avoid clashes with already loaded
 * instances of the same objects.
 *
 * <p>
 * Alternatively, turn this filter into deferred close mode, by specifying
 * "singleSession"="false": It will not use a single session per request then,
 * but rather let each data access operation or transaction use its own session
 * (like without Open Session in View). Each of those sessions will be
 * registered for deferred close, though, actually processed at request
 * completion.
 *
 * <p>
 * A single session per request allows for most efficient first-level caching,
 * but can cause side effects, for example on <code>saveOrUpdate</code> or when
 * continuing after a rolled-back transaction. The deferred close strategy is as
 * safe as no Open Session in View in that respect, while still allowing for
 * lazy loading in views (but not providing a first-level cache for the entire
 * request).
 *
 * <p>
 * Looks up the SessionFactory in Spring's root web application context.
 * Supports a "sessionFactoryBeanName" filter init-param in
 * <code>web.xml</code>; the default bean name is "sessionFactory". Looks up the
 * SessionFactory on each request, to avoid initialization order issues (when
 * using ContextLoaderServlet, the root application context will get initialized
 * <i>after</i> this filter).
 * <p>
 * On the basis of the original, the function of closing the session in advance has been added. For example, in the case of using a template, the template parameters are ready, and the session is closed when the entire page is not output to the browser.
 *
 * @author Juergen Hoeller
 * @author xinglj
 *
 * @since 1.2
 * @see #setSingleSession
 * @see #setFlushMode
 * @see #lookupSessionFactory
 * @see OpenSessionInViewInterceptor
 * @see org.springframework.orm.hibernate3.HibernateInterceptor
 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
 * @see org.springframework.transaction.support.TransactionSynchronizationManager
 */
public class OpenSessionInViewAnyCloseFilter extends OncePerRequestFilter {

	public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";

	private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;

	private boolean singleSession = true;

	private FlushMode flushMode = FlushMode.MANUAL;

	private static OpenSessionInViewAnyCloseFilter instance;

	public static OpenSessionInViewAnyCloseFilter getInstance() {
		if (instance == null)
			throw new RuntimeException("it's null,pls call it later!");
		else
			return instance;
	}

	// Whether this filter has passed. Handles the situation that the external actively calls close session, but there is no session.
	private ThreadLocal<Boolean> inFilter = new ThreadLocal<Boolean>() {
		protected Boolean initialValue() {
			return Boolean.FALSE;
		}
	};

	// Whether the session has been closed. Prevent repeated shutdowns
	private ThreadLocal<Boolean> sessionClosed = new ThreadLocal<Boolean>() {
		protected Boolean initialValue() {
			return Boolean.FALSE;
		}
	};

	public OpenSessionInViewAnyCloseFilter() {
		if (instance != null)
			throw new RuntimeException("pls new it only once!");
		else {
			instance = this;
		}
	}

	/**
	 * Set the bean name of the SessionFactory to fetch from Spring's root
	 * application context. Default is "sessionFactory".
	 *
	 * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
	 */
	public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
		this.sessionFactoryBeanName = sessionFactoryBeanName;
	}

	/**
	 * Return the bean name of the SessionFactory to fetch from Spring's root
	 * application context.
	 */
	protected String getSessionFactoryBeanName() {
		return this.sessionFactoryBeanName;
	}

	/**
	 * Set whether to use a single session for each request. Default is "true".
	 * <p>
	 * If set to "false", each data access operation or transaction will use its own
	 * session (like without Open Session in View). Each of those sessions will be
	 * registered for deferred close, though, actually processed at request
	 * completion.
	 *
	 * @see SessionFactoryUtils#initDeferredClose
	 * @see SessionFactoryUtils#processDeferredClose
	 */
	public void setSingleSession(boolean singleSession) {
		this.singleSession = singleSession;
	}

	/**
	 * Return whether to use a single session for each request.
	 */
	protected boolean isSingleSession() {
		return this.singleSession;
	}

	/**
	 * Specify the Hibernate FlushMode to apply to this filter's
	 * {@link org.hibernate.Session}. Only applied in single session mode.
	 * <p>
	 * Can be populated with the corresponding constant name in XML bean
	 * definitions: e.g. "AUTO".
	 * <p>
	 * The default is "MANUAL". Specify "AUTO" if you intend to use this filter
	 * without service layer transactions.
	 *
	 * @see org.hibernate.Session#setFlushMode
	 * @see org.hibernate.FlushMode#MANUAL
	 * @see org.hibernate.FlushMode#AUTO
	 */
	public void setFlushMode(FlushMode flushMode) {
		this.flushMode = flushMode;
	}

	/**
	 * Return the Hibernate FlushMode that this filter applies to its
	 * {@link org.hibernate.Session} (in single session mode).
	 */
	protected FlushMode getFlushMode() {
		return this.flushMode;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		inFilter.set(Boolean.TRUE);
		sessionClosed.set(Boolean.FALSE);
		
		SessionFactory sessionFactory = lookupSessionFactory(request);
		boolean participate = false;

		if (isSingleSession()) {
			// single session mode
			if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
				// Do not modify the Session: just set the participate flag.
				participate = true;
			} else {
				logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
				Session session = getSession(sessionFactory);
				TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
			}
		} else {
			// deferred close mode
			if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
				// Do not modify deferred close: just set the participate flag.
				participate = true;
			} else {
				SessionFactoryUtils.initDeferredClose(sessionFactory);
			}
		}

		try {
			filterChain.doFilter(request, response);
		}

		finally {
			if (!participate &&!sessionClosed.get()) {
				closeSessionIfNeccesary(sessionFactory);
			}
		}
	}
	/**
	 * Has passed this filter
	 * @return
	 */
	public Boolean isInFilter() {
		return inFilter.get();
	}
	/**
	 * close thread-bound session
	 */
	public void closeSession() {
		
			closeSessionIfNeccesary(lookupSessionFactory());
	}

	private void closeSessionIfNeccesary(SessionFactory sessionFactory) {
		if (!sessionClosed.get()) {
			logger.debug("close session...");
			if (isSingleSession()) {
				// single session mode
				SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
						.unbindResource(sessionFactory);
				logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
				closeSession(sessionHolder.getSession(), sessionFactory);
			} else {
				// deferred close mode
				SessionFactoryUtils.processDeferredClose(sessionFactory);
			}
			sessionClosed.set(Boolean.TRUE);
		} else {
			logger.debug("has closed session,do nothing.");
		}
	}

	/**
	 * Look up the SessionFactory that this filter should use, taking the current
	 * HTTP request as argument.
	 * <p>
	 * The default implementation delegates to the {@link #lookupSessionFactory()}
	 * variant without arguments.
	 *
	 * @param request
	 *            the current request
	 * @return the SessionFactory to use
	 */
	protected SessionFactory lookupSessionFactory(HttpServletRequest request) {
		return lookupSessionFactory();
	}

	/**
	 * Look up the SessionFactory 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 SessionFactory to use
	 * @see #getSessionFactoryBeanName
	 */
	protected SessionFactory lookupSessionFactory() {
		if (logger.isDebugEnabled()) {
			logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter");
		}
		WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
		return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
	}

	/**
	 * Get a Session for the SessionFactory that this filter uses. Note that this
	 * just applies in single session mode!
	 * <p>
	 * The default implementation delegates to the
	 * <code>SessionFactoryUtils.getSession</code> method and sets the
	 * <code>Session</code>'s flush mode to "MANUAL".
	 * <p>
	 * Can be overridden in subclasses for creating a Session with a custom entity
	 * interceptor or JDBC exception translator.
	 *
	 * @param sessionFactory
	 *            the SessionFactory that this filter uses
	 * @return the Session to use
	 * @throws DataAccessResourceFailureException
	 *             if the Session could not be created
	 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory,
	 *      boolean)
	 * @see org.hibernate.FlushMode#MANUAL
	 */
	protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
		FlushMode flushMode = getFlushMode();
		if (flushMode != null) {
			session.setFlushMode(flushMode);
		}
		return session;
	}

	/**
	 * Close the given Session. Note that this just applies in single session mode!
	 * <p>
	 * Can be overridden in subclasses, e.g. for flushing the Session before closing
	 * it. See class-level javadoc for a discussion of flush handling. Note that you
	 * should also override getSession accordingly, to set the flush mode to
	 * something else than NEVER.
	 *
	 * @param session
	 *            the Session used for filtering
	 * @param sessionFactory
	 *            the SessionFactory that this filter uses
	 */
	protected void closeSession(Session session, SessionFactory sessionFactory) {
		SessionFactoryUtils.closeSession(session);
	}

}

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326147370&siteId=291194637