open session in view

2017/02/12 iteye

摘自:http://community.jboss.org/wiki/OpenSessioninView  

问题描述

A common issue in a typical (web-)application is the rendering of the view, after the main logic of the action has been completed, and therefore, the Hibernate Session has already been closed and the database transaction has ended. If you access detached objects that have been loaded in the Session inside your JSP (or any other view rendering mechanism), you might hit an unloaded collection or a proxy that isn't initialized. The exception you get is: LazyInitializationException: Session has been closed (or a very similar message).

比如页面是学生信息显示,学生实体对象包含一个课程实体列表,并且采用惰性装载。那么页面显示学生的课程信息时可能Session已关闭,事务已提交,hibernate会抛出上面的异常。基本上问题出现在一次request 与 response 之间layz loading的对象上。

open session in view解决方案也是应用在相应的scope内。如果是用JPA,那么对应Session的是EntityManager,相应的是open entity manager in view. 如果渲染发生在servlet中,在servlet中打开和关闭Session(Session per Request), 这样是没问题的。但如果渲染是用JSP,那么就有这里所说的问题,这时就需要Open Session in View的filter方式。Filter后处理发生在 “渲染JSP模板生成Html” 后, 这时候才关闭Session或者EntityManager  

解决方案

  1. 为detached objects 重新开一个session  
  2. open session in view in most applications you need the following: when an HTTP request has to be handled, a new Session and database transaction will begin. Right before the response is send to the client, and after all the work has been done, the transaction will be committed, and the Session will be closed. A good standard interceptor in a servlet container is a ServletFilter .
public class HibernateSessionRequestFilter implements Filter {
 
    private static Log log = LogFactory.getLog(HibernateSessionRequestFilter.class);
 
    private SessionFactory sf;
 
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {
 
        try {
            log.debug("Starting a database transaction");
            sf.getCurrentSession().beginTransaction();
 
            // Call the next filter (continue request processing)
            chain.doFilter(request, response);
 
            // Commit and cleanup
            log.debug("Committing the database transaction");
            sf.getCurrentSession().getTransaction().commit();
 
        } catch (StaleObjectStateException staleEx) {
            log.error("This interceptor does not implement optimistic concurrency control!");
            log.error("Your application will not work until you add compensation actions!");
            // Rollback, close everything, possibly compensate for any permanent changes
            // during the conversation, and finally restart business conversation. Maybe
            // give the user of the application a chance to merge some of his work with
            // fresh data... what you do here depends on your applications design.
            throw staleEx;
        } catch (Throwable ex) {
            // Rollback only
            ex.printStackTrace();
            try {
                if (sf.getCurrentSession().getTransaction().isActive()) {
                    log.debug("Trying to rollback database transaction after exception");
                    sf.getCurrentSession().getTransaction().rollback();
                }
            } catch (Throwable rbEx) {
                log.error("Could not rollback transaction after exception!", rbEx);
            }
 
            // Let others handle it... maybe another interceptor for exceptions?
            throw new ServletException(ex);
        }
    }
 
    public void init(FilterConfig filterConfig) throws ServletException {
        log.debug("Initializing filter...");
        log.debug("Obtaining SessionFactory from static HibernateUtil singleton");
        sf = HibernateUtil.getSessionFactory();
    }
 
    public void destroy() {}
 
}
    <filter>
        <filter-name>HibernateFilter</filter-name>
        <filter-class>my.package.HibernateSessionRequestFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>HibernateFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>   
public class ItemDAO {
 
    Session currentSession;
 
    public ItemDAO() {
        currentSession = HibernateUtil.getSessionFactory().getCurrentSession();
    }
 
    public Item getItemById(Long itemId) {
        return (Item) currentSession.load(Item.class, itemId);
    }
}
public String execute(HttpRequest request) {
 
    Long itemId = request.getParameter(ITEM_ID);
 
    ItemDAO dao = new ItemDAO();
 
    request.setAttribute( RESULT, dao.getItemById(itemId) );
  
    return "success";
}

filter for long conversation 长事务 

If you'd like to use a single Session for several database transactions, you either have to implement it completely yourself, or you have to use the built-in "application managed" strategy:      

To enable the application-managed "current" Session strategy, set your hibernate.current_session_context_class configuration property to org.hibernate.context.ManagedSessionContext (or simply "managed" in Hibernate 3.2). You can now bind and unbind the "current" Session with static methods, and control the FlushMode and flushing manually.  

public class HibernateSessionConversationFilter
        implements Filter {
 
    private static Log log = LogFactory.getLog(HibernateSessionConversationFilter.class);
 
    private SessionFactory sf;
 
    public static final String HIBERNATE_SESSION_KEY = "hibernateSession";
    public static final String END_OF_CONVERSATION_FLAG = "endOfConversation";
 
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {
 
        org.hibernate.classic.Session currentSession;
 
        // Try to get a Hibernate Session from the HttpSession
        HttpSession httpSession =
                ((HttpServletRequest) request).getSession();
        Session disconnectedSession =
                (Session) httpSession.getAttribute(HIBERNATE_SESSION_KEY);
 
        try {
 
            // Start a new conversation or in the middle?
            if (disconnectedSession == null) {
                log.debug(">>> New conversation");
                currentSession = sf.openSession();
                currentSession.setFlushMode(FlushMode.NEVER);
            } else {
                log.debug("< Continuing conversation");
                currentSession = (org.hibernate.classic.Session) disconnectedSession;
            }
 
            log.debug("Binding the current Session");
            ManagedSessionContext.bind(currentSession);
 
            log.debug("Starting a database transaction");
            currentSession.beginTransaction();
 
            log.debug("Processing the event");
            chain.doFilter(request, response);
 
            log.debug("Unbinding Session after processing");
            currentSession = ManagedSessionContext.unbind(sf);
 
            // End or continue the long-running conversation?
            if (request.getAttribute(END_OF_CONVERSATION_FLAG) != null ||
                request.getParameter(END_OF_CONVERSATION_FLAG) != null) {
 
                log.debug("Flushing Session");
                currentSession.flush();
 
                log.debug("Committing the database transaction");
                currentSession.getTransaction().commit();
 
                log.debug("Closing the Session");
                currentSession.close();
 
                log.debug("Cleaning Session from HttpSession");
                httpSession.setAttribute(HIBERNATE_SESSION_KEY, null);
 
                log.debug("<<< End of conversation");
 
            } else {
 
                log.debug("Committing database transaction");
                currentSession.getTransaction().commit();
 
                log.debug("Storing Session in the HttpSession");
                httpSession.setAttribute(HIBERNATE_SESSION_KEY, currentSession);
 
                log.debug("> Returning to user in conversation");
            }
 
        } catch (StaleObjectStateException staleEx) {
            log.error("This interceptor does not implement optimistic concurrency control!");
            log.error("Your application will not work until you add compensation actions!");
            // Rollback, close everything, possibly compensate for any permanent changes
            // during the conversation, and finally restart business conversation. Maybe
            // give the user of the application a chance to merge some of his work with
            // fresh data... what you do here depends on your applications design.
            throw staleEx;
        } catch (Throwable ex) {
            // Rollback only
            try {
                if (sf.getCurrentSession().getTransaction().isActive()) {
                    log.debug("Trying to rollback database transaction after exception");
                    sf.getCurrentSession().getTransaction().rollback();
                }
            } catch (Throwable rbEx) {
                log.error("Could not rollback transaction after exception!", rbEx);
            } finally {
                log.error("Cleanup after exception!");
 
                // Cleanup
                log.debug("Unbinding Session after exception");
                currentSession = ManagedSessionContext.unbind(sf);
 
                log.debug("Closing Session after exception");
                currentSession.close();
 
                log.debug("Removing Session from HttpSession");
                httpSession.setAttribute(HIBERNATE_SESSION_KEY, null);
 
            }
 
            // Let others handle it... maybe another interceptor for exceptions?
            throw new ServletException(ex);
        }
 
    }
 
    public void init(FilterConfig filterConfig) throws ServletException {
        log.debug("Initializing filter...");
        log.debug("Obtaining SessionFactory from static HibernateUtil singleton");
        sf = HibernateUtil.getSessionFactory();
    }
 
    public void destroy() {}
 
}

 

Search

    Table of Contents