`
SSailYang
  • 浏览: 311204 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
阅读更多
原文:http://www.hibernate.org/43.html

问题

在一个典型的(Web)应用中常见的一个问题是在主要的逻辑动作完成之后渲染页面,同时,因为逻辑动作的完成,Hibernate Session 已被关闭,数据库事务也已结束。如果你在你的 JSP 中(或者其它视图渲染机制)访问已被 Session 加载的 Detached Object 的话,你可能会遇到一个没有被初始化的未加载的 Collection 或 代理。这时,你将会得到一个 LazyInitializationException: Session has been closed,或类似的消息。当然,这是可预见的,毕竟,你已经结束了你的工作单元了。

一个解决方案是开启另一个工作单元去渲染视图。这很容易被实现,但这通常不是正确的方法。为一个完整的动作去渲染页面应当是在第一个单元的工作中,而不是在独立的单元中。这个解决方案,在一个动作的执行,通过 Session 进行数据访问,视图的渲染都是在一个虚拟机中完成的两层架构系统中,是通过保持 Session 的打开直至视图渲染完成来实现的。

使用拦截器

如果你为了自动化的 Hibernate Session Context 管理而使用 Hibernate 内建的支持(ThreadLocalSessionContext 和 JTASessionContext)来实现你的 Session 处理的话,请见 Sessions and transactions ,你已经有了一半的代码实现了。现在你只需要某种拦截器在试图渲染之后运行,然后去提交事务,关闭 Session。换句话说,在大多数应用中你需要做如下事情:当一个 Http 请求被处理的时候,一个新的 Session 和事务将开始。在响应被发回到客户端之前,所用的动作完成之后,事务被提交,Session 被关闭。

Servlet 容器中的标准拦截器是 ServletFilter。写一个自定义的 Filter 很平常,下面是 CaveatEmptor 的例子:
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 与自动的 Session Context 支持结合。DAO 代码将像下面这个简单平常:
public class ItemDAO {

    Session currentSession;

    public ItemDAO() {
        currentSession = HibernateUtil.getSessionFactory().getCurrentSession();
    }

    public Item getItemById(Long itemId) {
        return (Item) currentSession.load(Item.class, itemId);
    }
}


或者,DAO 可以有一个以 Session 作为参数的构造器,这样的话,设置当前 Session 的责任就将移到 DAO 的调用者上(或者是工厂)。关于 DAO 的更多信息,请看泛型 DAO

现在你的应用中的 Controller 可以使用 DAO 了,并且不被 Session 和事务所打扰。例如,在你的 Servlet 中:
public String execute(HttpRequest request) {

    Long itemId = request.getParameter(ITEM_ID);

    ItemDAO dao = new ItemDAO();

    request.setAttribute( RESULT, dao.getItemById(itemId) );
  
    return "success";
}


使 Filter 对所有的 Http 请求生效,在 web.xml 添加如下配置:
    <filter>
        <filter-name>HibernateFilter</filter-name>
        <filter-class>my.package.HibernateThreadFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>HibernateFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


如果你不想为每一个 Http 请求开启一个 Hibernate Session (和一个数据库事务,其从连接池中得到一个数据库连接),将 Filter 映射到合适的 Url 模式上(例如,仅需要数据库访问的 Url)。或者,为 Filter 增加一个开关,如果 Session 和事务是必须的,基于某种任意的条件(例如,请求参数)

附加说明:因为 Session 在视图渲染之后被 flush,数据库异常可能会在输出成功生成之后产生。如果你使用纯 JSP 和 Servlet,页面渲染之后的输出将被放在一个 buffer 中,仅有 8kb 大小。如果 buffer 满了,它将被 flush 到客户的浏览器中!所以,你的用户可能在发生异常的情况下看到成功的页面(200 OK)。为了避免这个问题,不要将输出渲染进 Servlet 的 buffer 中,或者增大 buffer 的尺寸到一个安全的值。多数 Web 框架不将渲染后输出放进标准 Servlet 的buffer 中,而是它们自带的 buffer,以避免这个问题。

关于三层环境

毫无疑问,这种模式仅当你渲染视图时使用本地的 Session 这种情况下有效。在一个三层的环境中,视图可能在展现层虚拟机被渲染,而不是在拥有商业逻辑和数据访问层的 Service 虚拟机中被渲染。因此,保持 Session 和事务的开启并不是一个选择。这种情况下,你不得不发送合适量的数据到展现层虚拟机中,这样视图可以在 Session 关闭的情况下被构建。至于你是选择 Detached Object,或是 DTO,亦或使用 Command Pattern 混合两者,就取决于你的架构了。这些在 Hibernate in Action 均有讨论。


关于为长对话(long-conversation)扩展 Session 的模式

如果你喜欢让一个 Hibernate Session 跨越多个数据库事务,那你也不得不自力更生了,或者使用(Hibernate)内建的“应用管理”策略(使用 ManagedSessionContext)。


为了启用“应用管理”当前 Session 策略,你要设置 hibernate.current_session_context_class 配置属性为 org.hibernate.context.ManagedSessionContext (or simply "managed" in Hibernate 3.2)。你现在可以 使用静态方法去 bind 和 unbind 当前的 Session,和控制 FlushMode 并手动 flush。前面见到的 Servlet filter 需要在 conversation 期间去控制 bind 和 flush。
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() {}

}

备注:1. org.hibernate.classic.Session 同普通的 Session 一样,只是多了一些 Hibernate 2 中的,现已不推荐的方法,用以代码移植。
2. 这里提到为 long-conversation 扩展 Session 的做法和 Seam 中的有很大的不同,不要将两者混淆。这里的做法也不是真正将 Hibernate Session 和 Conversation (比 Http Session 小,但比 Request 的 Context,详见 JBoss Seam 或 WebBeans 文档)整合。


这个 Filter 对于你的应用的其余部分是透明的,使用“当前” Session 的 DAO 和其它代码是不需要改变的。然而,在某一点 Conversation 是需要结束的,这时 Session 需要被 flush 和关闭。上面的例子使用了一个Request 范围的特殊标记。你可以在请求的处理过程中设置这个标记。可能你有其它的拦截器层应用在 Conversation。或者,是工作流引擎。

我不想写 Servlet Filter,它太老土了!

你是对的,Servlet Filter 不再是热门技术了。然而,它们在 Web 应用中作为 wrap-around 的拦截器工作的非常好(wrap-around,同 AOP 中的 around 含义相同),并且 Servlet Filter 本质上没有什么问题。就像已经提到的,如果你使用了自带拦截器的 Web 框架,你可能会发现它们更灵活。目前,很多 Web 容器也提供了自定义的拦截器。注意,如果这些 Web 容器没有实现 Java EE 规范的话,你的应用将不具有可移植性。最后,更灵活的方式是使用 AOP 拦截器。见 Session handling with AOP

如何处理异常?

毫无疑问,一旦异常发生,事务将会回滚。Hibernate 的另一条规则是,当前 Session 必须被关闭,并立即被抛弃,它不能被重新使用。因此,从 Hibernate 的角度看,这是你在处理异常时全部要做的。当然,如果失败的话你可能想重试一些工作,或者你可能想显示自定义的错误。所有这些已超出了 Hibernate 的范围,你可以按照自己喜欢的方式实现。

我能在渲染视图前提交事务吗?

显然地,虽然没有在 Hibernate 文档中提及,一些开发者使用这种模式的变种去保持 Session 的开启直到视图渲染,但在视图渲染之前提交事务。然后,在视图渲染期间,未被加载的代理或集合被访问,并进行初始化。因为 Session 依然开启,这表面上是可行的。甚至 Session 可以被调用,并做一些事情。Session 为一个单独的操作得到数据库连接,并执行准备好的语句。

然而,这种访问是非事务的。你需要为此在 Hibernate 配置中启用 auto-commit 模式。如果你在 Hibernate 中启用 auto-commit 模式,Hibernate 就会将从连接池中去到数据库连接设置为 auto-commit 模式,然后在通过调用 close() 方法使 JDBC Connection 返回连接池。

同样需要注意的是,如果你忘记了在 Hibernate 配置中启用 auto-commit,非事务的访问也可能会工作。如果你不在 Hibernate 中启用 auto-commit 模式,Connection 可能以任意的默认模式从连接池中获得,并在没有提交或者回滚的情况下返回连接池。这个行为是没有被定义的。绝对不要这样做,因为 JDBC 规范并没有说有潜在的未完成的事务时,调用 close() 将会发生什么(是的,当 Hibernate 从数据库连接池中得到连接时,数据库事务可能将隐式地开始)

事实上,任何非事务数据访问(没有 JTA/EJB)被认为是一个反模式,因为这无法从事务中得到性能、可伸缩性以及其它方面的好处。

许多依赖 Hibernate 的框架也依赖于这个反模式,要避免它们。总是用清晰的事务边界将你的工作划分为组。你可以考虑使用在一个 Session 中的两个事务,一个用于执行事件,另一个用于渲染视图。

我能在一个 Session 中使用两个事务吗?

是的,这事实上是这种模式(Open Session In View)的一个更好的实现。在一个请求事件中,一个数据库事务用于数据的读写。第二个数据库事务仅用于在渲染视图期间读数据。在这点上没有对对象的修改。因此,数据库锁早在第一个事务时就被释放了,这使得应用有更好的可伸缩性,第二个事务可以被优化。要使用两阶段的事务,你需要比 Servlet Filter 更强大的拦截器 - AOP 是个很好的选择。JBoss Seam 使用了这种模式。

为什么 Hibernate 不在需要时就加载 Object?

每个月很多人都会有这种想法,为什么 Hibernate 不能在有需要的就开启一个新的数据库连接(更有效率的是开启一个 Session),然后加载集合或是初始化代理,而是选择抛出一个 LazyInitializationException。当然,这种想法,第一眼看上去可能是明智之举。但这种做法有很多的缺点,只有当你考虑特别的事务访问时才会发现。

如果 Hibernate 可以进行任意的数据库连接和事务,这种操作是开发人员不可知,并且也是在任何事务边界之外的,那还要事务边界做什么。当 Hibernate 开启了新的数据库连接去加载集合,但同时集合的拥有者却被删除了,这是将会发生什么?(注意,这种情况是不会发生在上面提到的两阶段的事务模式中的 - 单个 Session 可对实体可重复读。)当所有的对象都可以通过关联导航获取时为什么还要有 Service 层?这种方式将消耗多少内存?哪些对象要首先被清除掉?所有这些问题都是无解的,因为 Hibernate 是一个在线的事务处理服务(并包含一些批处理操作),并不是一个“在未定义的工作单元中从数据持久仓库取得对象”的服务。此外,对于 n+1 查询问题,我们是否需要 n+1 的事务和连接的问题?

这个问题的解决方案当然是正确的工作单元划分和设计,支撑其的拦截技术就像这里所展现的一样,并且/或者正确的抓取技术,使得特定工作单元所需的全部信息能够以最小的影响、最好的性能和伸缩性被获得。

这一切很难吗?能否做的更简单?

Hibernate 所能做的只是持久化服务,这里所做的事情,其责任在于应用程序架构和框架。EJB3 编程模型使得事务和持久化上下文的管理变得简单了,使用 Hibernate EntityManager(这是 JPA 的一个实现)得到其 API。可以在一个完整的 Java EE 的实现服务器去运行你的 EJB 们,也可以在一个轻量级嵌入式的 EJB 容器中运行,在你的 Java 环境中。JBoss Seam 有内建的自动上下文管理机制,包括持久化和对话(Conversation),实现这些仅需要你在代码中使用一些注释。
5
0
分享到:
评论

相关推荐

    Open Session in View模式.PPT

    Open Session in View (OSIV) 模式是一种在基于Hibernate的Web应用程序中处理持久化数据的策略,它允许在视图层(例如JSP页面)中安全地访问延迟加载的对象,而无需担心Session已关闭的问题。以下是关于这个模式及其...

    struts2+hibernate3 open session in view

    在这个小项目中,"Open Session in View"(OSIV)模式被采用,这是一种处理持久化数据的策略,以避免在Web应用中出现常见的并发问题,如数据不一致和懒加载异常。 Struts2是一个强大的MVC框架,它提供了一种灵活的...

    Open_Session_In_View详解.doc

    ### Open_Session_In_View详解 #### 一、背景与概念 在使用Hibernate进行对象持久化时,经常遇到的一个问题是关于懒加载(lazy loading)的处理。懒加载是一种优化技术,允许在真正需要某个关联对象的数据时才加载...

    Hibernate Session释放模式

    3. **Open Session in View(OSIV)模式** OSIV模式旨在解决Web应用中,由于用户请求可能会引发多次数据库操作,而这些操作可能跨多个HTTP请求。在这种模式下,Session在整个HTTP请求周期内保持打开状态,直到请求...

    使用Spring引起的错误

    当使用Open Session In View模式时,如果Session的Flush Mode被设置为NEVER,并且尝试执行写操作(如更新或删除),就会触发“Write operations are not allowed in read-only mode (FlushMode.NEVER)”这个异常。...

    hibernate中session的管理

    3. **Open Session in View (OSIV)**:在视图渲染阶段保持Session打开,以允许最后时刻的懒加载,但需要注意防止Session泄露。 总的来说,Hibernate中Session的管理是保证多线程环境下数据一致性的重要环节。...

    spring中lazy=“true”的正常读取关联表(用opensessioninview)

    为了解决这个问题,Spring提供了一个过滤器 `OpenSessionInViewFilter` 或者 `OpenSessionInViewInterceptor`,通常简称为OSIV(Open Session In View)模式。 OSIV模式的核心思想是在Controller层和View层之间保持...

    Hibernate事务管理.

    在没有Spring提供的Open Session In View(OSIV)模式下,事务处理和懒加载(lazy loading)可能会遇到挑战。 懒加载是一种优化策略,允许关联对象在需要时才从数据库加载,而不是在初始加载实体时就全部加载。这...

    Spring延迟加载和声明式事务处理最终解决方案(修正版)

    1. **Open Session in View Interceptor (OSIV)**:这是Spring MVC中的一个拦截器,它的作用是在整个视图渲染过程中保持Hibernate Session的开放,确保在需要的时候可以进行延迟加载。配置OSIV拦截器后,如在`spring...

    ssh中getCurrentSession的使用

    - 首先,需要配置Hibernate,让其知道如何初始化SessionFactory,并启用Open Session In View模式。 - 在业务逻辑代码中,通过SessionFactory实例调用`getCurrentSession()`,然后执行数据库操作。 - 为了处理...

    集成spring的hibernate懒加载

    在Spring整合Hibernate的情况下,Session通常通过Transaction Management进行管理,比如使用`Open Session in View`(OSIV)模式或者基于注解的事务管理。 当你尝试在Controller层或者视图层访问懒加载的属性时,...

    课程hibernate的事务和并发.pdf

    这种方式被称为ThreadLocal Session和Open Session in View模式,能够简化事务和Session的生命周期管理。 在实际应用中,需要确保正确地开启和结束Session及事务,并将其与数据访问操作结合。HibernateUtil类可以...

    ssh_inte2模板常用方法&延迟加载问题

    可以考虑将事务范围扩大,或者使用开放Session视图模式(Open Session In View,OSIV),让Session贯穿整个HTTP请求,但请注意,OSIV模式可能导致更多的并发问题,需要谨慎使用。 3. **使用Eager Loading**:如果...

    SpringBoot-SpringData-懒加载

    2. **启用Hibernate的开放 session in view**:在Spring Boot中,如果使用Hibernate作为JPA的实现,需要在配置文件(application.properties或application.yml)中开启Open Session In View(OSIV)过滤器,以解决懒...

    hibernate笔记

    Open Session in View(OSIV)模式是一种常见的Hibernate优化模式,主要用于提高读取操作的性能。在这种模式下,Session在整个HTTP请求周期内保持打开状态。 ##### OSIV 实现方式 - **Servlet Filter 方式**:最常见...

    SSH错误集中分析.

    确保在需要的地方启用Open Session in View或者在查询时一次性加载关联数据。 2. **SQL异常**:错误的HQL或Criteria查询可能导致SQL语法错误,检查并修正查询语句。 四、Spring的常见错误 1. **AOP配置**:如果...

    hibernate的lazy策略forClass

    2. 使用Open Session In View模式,将Session的生命周期扩展到整个HTTP请求,但此方法可能带来事务管理和性能问题。 3. 在需要访问懒加载属性时,确保在Session内进行操作,或者使用`Hibernate.initialize()`手动...

    【北大青鸟内部教程】jsp中关于Hibernate延时加载的问题

    为解决此问题,可以在Controller层或Service层提前加载关联数据,或者使用Hibernate的“开放Session视图”(Open Session in View)模式。 3. **事务管理**:延时加载需要在一个有效的Hibernate Session内进行。在...

    SSH常用面试题

    #### 十、Hibernate中的Open Session in View模式 1. **模式介绍:**Open Session in View模式是一种使用Hibernate时的高级技术,它通过在每个HTTP请求开始时打开一个Session,在请求结束时关闭这个Session,从而...

    一次hibernate的优化实践

    - 使用Open Session in View模式需谨慎,因为它可能导致事务边界不清,影响性能。 4. **实体设计与映射优化** - 调整懒加载(Lazy Loading)和急加载(Eager Loading)策略,避免因延迟加载引发的额外查询。 - ...

Global site tag (gtag.js) - Google Analytics