`
yt
  • 浏览: 17178 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Hibernate脏数据问题异常及解决思路

阅读更多

产品提交测试小组,返回如下异常bug

Hibernate:
    update
        MAILBOX_VIEW
    set
        name=?,
        homedir=?,
        domainid=?,
        groupid=?,
        max_msg_count=?,
        quota=?,
        usagespace=?,
        valid_msg_count=?,
        status=?,
        zone=?,
        scope=?
    where
        id=?
[class/org.hibernate.util.JDBCExceptionReporter] Thread:pool3_thread5 WARN : 2007-10-29 11:31:10 SQL Error: 17008, SQLState: null
[class/org.hibernate.util.JDBCExceptionReporter] Thread:pool3_thread5 ERROR: 2007-10-29 11:31:10 ?????
[class/org.hibernate.event.def.AbstractFlushingEventListener] Thread:pool3_thread5 ERROR: 2007-10-29 11:31:10 Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: could not update: [com.ceno.mail.mailbox.persistent.MailBoxPO#2000060]
        at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2360)
        at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2242)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2542)
        at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
        at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
        at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:41)
        at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:969)
        at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1114)
        at org.hibernate.impl.QueryImpl.list(QueryImpl.java:79)
        at com.ceno.orm.hibernate.HibernateDAOSupport$HibernateTemplate.find(HibernateDAOSupport.java:1291)
        at com.ceno.mail.user.persistent.MailUserDAO.findMailUser(MailUserDAO.java:313)
        at com.ceno.mail.user.persistent.MailUserDAO.getUser(MailUserDAO.java:350)
        at com.ceno.mail.user.implement.MailUserProxyManager.getUserByName(MailUserProxyManager.java:289)
        at com.ceno.mail.user.persistent.MailUserDAO.getUserByName(MailUserDAO.java:400)
        at com.ceno.mail.user.implement.MailUserProxyManager.getUserByName(MailUserProxyManager.java:88)
        at com.ceno.mail.user.MailUserUtil.getUserByName(MailUserUtil.java:70)
        at com.ceno.mail.login.MailUserAuthModule.login(MailUserAuthModule.java:49)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)

我们在产品试运行期间从没有发现该问题,该异常是在做Query操作抛出的,经分析后做出如下判断:
1、该段代码本身没有对MailBoxViewPO做任何操作,脏数据是之前遗留的。
2、该段代码前面没有任何Hibernate操作调用,脏数据不会是在同一个thread调用引起的。
3、该段代码对HibernateSession的open/close操作是成对出现的,全部hibernate操作都通过HibernateTemplate来调用,不会出现session遗留问题。

那么问题会在什么地方,难道close的时候数据没有清除掉?再看HibernateSessionFactory的实现,每次OpenSession的时候都是new HibernateSessionImpl对象,不存在这个问题。
灵光一现,Session对象不会被复用,但我们的Thread是复用的,而在获取session的时候会把session捆在ThreadLocal里面,getSession的代码如下:

    /**
     * Retrieves the current Session local to the thread.
     * <p/>
     * If no Session is open, opens a new Session for the running thread.
     *
     * @return Session
     */
    public static Session getSession()
    {
        CountSession s = (CountSession) threadSession.get();

        if (s == null) {
            if (log.isDebug()) {
                log.debug("Opening new Session for this thread.");
            }
            s = new CountSession();
            if (getInterceptor() != null) {
                if (log.isDebug()) {
                    log.debug("Using interceptor: " + getInterceptor().getClass());
                }

                s.setSession(getSessionFactory().openSession(getInterceptor()));
            }
            else {
                s.setSession(getSessionFactory().openSession());
            }
            s.setCount(1);
            threadSession.set(s);
        }
        else {
            s.setCount(s.getCount() + 1);
        }
        return s.getSession();
    }

MailUserUtil所在代码模块的Hibernate调用都通过HibernateTemplate来进行,否认该模块会出现没有调用close操作把session从threadLocal里面移走的低级错误。怀疑的重点到业务和管理系统模块的老代码上,查找老代码的HibernateUtil.getSession(),发现所有代码在调用hibernate操作后都会在finally处调用HibernateUtil.closeSession()。
再次陷入无线索状态中,再次仔细检查调用Hibernate操作的老代码,发现如下一些代码:

        Zone[] zones = new BusinessZonePO[0];
        Session session = HibernateUtil.getSession();
        try {
            HibernateUtil.beginTransaction();
            Query query = session.createQuery("from BusinessZonePO where pid=?");
            query.setInteger(0, pid);
            List list = query.list();
            HibernateUtil.commitTransaction();
            if (list != null && list.size() > 0) {
                zones = (Zone[]) list.toArray(zones);
            }
        }
        catch (HibernateException e) {
            HibernateUtil.rollbackTransaction();
            throw e;
        }
        finally {
            HibernateUtil.closeSession();
        }
        return zones;

有一部份类似的调用都是在getSession后,再beginTransaction,这里代码写法明显有问题,如果在begin和commit之间抛出Unchecked Exception,那么commit和rollback都不会执行。把怀疑重点转到这部分代码上来了,调来HibernateUtil.beginTransaction的实现:

        Transaction tx = (Transaction) threadTransaction.get();
        if (tx == null) {
            if (log.isDebug()) {
                log.debug("Starting new database transaction in this thread.");
            }
            tx = getSession().beginTransaction();
            threadTransaction.set(tx);
        }

一看代码基本上有些眉目了,在这个实现里面,再次调用了getSession,在beginTransaction之前已经调用了一次getSession,这意味着这一次getSession调用会从ThreadLocal对旬里面取到CountSession,并把count置为2。
这样如果没有调用commitTransaction,只会调finally一次closeSession,closeSession的代码如下:

        CountSession s = (CountSession) threadSession.get();
        if (s != null) {
            if (s.getCount() <= 1) {
                threadSession.set(null);
                threadTransaction.set(null);
                s.close();
            }
            else {
                s.setCount(s.getCount() - 1);
            }
        }

如果只调用一次closeSession,那么CountSession对象只会把count减1,并不会从ThreadLocal里面移走。这时候思路渐渐清晰了,再检查HibernateUtil.commitTransaction的实现,问题马上就找到了:

        Transaction tx = (Transaction) threadTransaction.get();
        try {
            if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
                if (log.isDebug()) {
                    log.debug("Committing database transaction of this thread.");
                }
                tx.commit();
            }
            threadTransaction.set(null);
        }
        catch (HibernateException ex) {
            rollbackTransaction();
            throw ex;
        }

原是是怀疑在begin和commit之间抛出Unchecked Exception,导致commit没调用,检查这段代码才发现commit里面根本没有调用closeSession,导致session残留在ThreadLocal对象里面,在thread被回收到thread pool并下一次被取出调用,该session第二次使用,而这时候session里面会残留大量po对象,有些可能为dirty data。

在commitTransaction里面加入finally{closeSession();},问题解决。


总结:


1、同时使用ThreadLocal和Thread Pool需十分谨慎检查thread回到pool前,确保local对象一定被释放。
2、对于Session或Connection之类的有限资源,必须通过模板机制保证close/open成对出现,原则上不允许调用者自已进行显示的open/close,一旦代码出现问题,会对系统造成十分严重的影响。
3、深入理解catch/finally,Unchecked Exception。

分享到:
评论

相关推荐

    自己动手模仿Hibernate写数据库框架

    博文链接虽然没有提供,但通常这类博客会包含作者在模仿Hibernate过程中的代码示例和实现思路,帮助读者理解如何构建类似功能的框架。 【标签】"源码"和"工具"表明我们将会关注源代码级别的实现,以及这个自定义...

    高板撒三框架的常见异常和解决方案

    本文将针对“高板撒三框架”(假设此处指的是Spring、Hibernate、MyBatis三大框架)中的常见异常进行总结,并提供相应的解决思路,旨在帮助初学者快速定位并解决问题。 #### 标题解析 标题“高板撒三框架的常见异常...

    J2EE系统之-hibernate学习总结

    2. **需求分析与抽象**:将业务需求中的实体和行为抽象为Hibernate中的实体和相关服务接口及方法。 3. **面向对象思考**:在设计映射时不直接考虑SQL语句,而是侧重于面向对象的设计思路。完成后可再考虑SQL语句优化...

    黑马程序员_hibernate框架开发2016版讲义和笔记资料_day02

    11. 异常处理:学习Hibernate常见的异常,如ConstraintViolationException、ObjectRetrievalFailureException等,以及如何进行相应的错误处理。 12. 实战演练:结合提供的"代码"和"思路图",通过实际编写和运行代码...

    Hibernate+Struts2在线投票系统

    其中,可能包括了Action类(Struts2的核心组件,处理用户请求),DAO(数据访问对象,与Hibernate交互)类,实体类(与数据库表对应的Java对象),配置文件(如Hibernate的配置文件hibernate.cfg.xml,Struts2的...

    hibernate错误示例一

    标签“源码”和“工具”提示我们,解决问题可能需要查看Hibernate的相关源码,理解其内部工作机制,同时也可能需要借助一些开发工具如IDE的调试功能来辅助排查。 在“Hibernate_OneToOne_bi”这个压缩文件中,很...

    Struts2整合Spring Hibernate的CRUD实例

    2. **Spring配置**:在Spring的配置文件(applicationContext.xml)中,我们需要配置数据源、Hibernate的SessionFactory以及Service和DAO层的Bean。使用@Autowired注解实现依赖注入,使得Struts2 Action可以直接使用...

    Java EE数据持久层解决方案的设计与实现.pdf

    Java EE数据持久层解决方案的设计与实现是企业级应用开发中的关键环节,主要...同时,这种设计思想也符合“学中做,做中学,学会做,独立做”的教学改革思路,让学生在实践中掌握更实用的技术,提高解决问题的能力。

    Hibernate struts2 spring 整合应用学生信息管理系统源码及文档

    此外,"程序设计与框架I课程设计报告"进一步详细阐述了系统的设计思路、架构选择及实现过程,包括需求分析、系统设计、模块划分、数据库设计、各层之间的交互以及具体的技术实现细节,对于理解整个系统有极大的帮助...

    用户管理jsp+struts2+hibernate代码

    同时,良好的异常处理和日志记录也是系统不可或缺的部分,能够帮助开发者调试和排查问题。 总的来说,这个用户管理系统展示了如何将JSP、Struts2和Hibernate结合使用,以实现一个功能完善的用户登录和管理应用。...

    Struts2+Hibernate开发学生成绩管理系统

    本项目采用了Struts2和Hibernate这两个强大的开源框架,旨在提供高效、稳定且易于维护的解决方案。Struts2作为MVC(Model-View-Controller)架构的一部分,负责处理前端请求并控制业务流程,而Hibernate则作为ORM...

    酒店管理系统(struts+hibernate)

    【酒店管理系统】是一款基于Struts和Hibernate框架的典型企业级应用,它主要用于管理酒店的日常运营,提升工作效率。Struts是Java EE平台上的一个MVC(Model-View-Controller)框架,而Hibernate则是一个强大的对象...

    一个通用的Hibernate DAO

    - **异常处理**:使用try-catch块捕获并处理可能出现的异常,确保事务回滚及Session关闭。 - **删除操作**:`public void delete(Object obj) throws Exception {`,此方法接收一个泛型对象作为参数,并从数据库...

    Hibernate3.2 官方用户手册

    - **文档编写**: 详细记录系统架构及关键组件设计思路。 通过以上内容的学习,开发者可以全面了解 Hibernate 3.2 的核心特性和使用方法,进而提高应用系统的数据访问效率和稳定性。在实际开发过程中,还需要根据...

    Hibernate+Struts分页的实现

    "Hibernate+Struts分页的实现"这个话题关注的就是如何在大型数据集的场景下,结合这两种技术来有效地实施分页策略。 首先,让我们了解一下Struts框架。Struts是一个基于MVC(Model-View-Controller)设计模式的Java...

    ssh整合jar包spring hibernate structure

    在SSH整合中,Hibernate作为数据访问层,处理数据库的CRUD操作。 3. **Struts框架** Struts是基于MVC设计模式的Web应用程序框架,主要用于控制层。它通过Action和ActionForm类来处理用户请求,并将结果转发到相应...

    项目实践精解:基于Struts-Spring-Hibernate的Java应用开发

    - **持久层**:采用Hibernate框架进行数据存取操作。 #### 四、表示层:Struts框架的应用 Struts是一个成熟的MVC框架,用于构建动态Web应用。其主要职责包括: 1. **请求处理**:管理用户请求并做出相应响应。 2....

    Hibernate和Spring集成分页方法

    在Java企业级应用开发中,经常需要处理大量数据的展示和查询问题。为了提高用户体验并优化服务器资源利用,分页查询是一种非常实用的技术手段。本文将详细介绍如何在Hibernate与Spring框架集成的环境下实现分页功能...

Global site tag (gtag.js) - Google Analytics