Quartz作业停止在StaleObjectStateException异常上执行

时间:2013-05-06 07:56:25

标签: hibernate grails exception-handling quartz-scheduler grails-2.0

我有一个石英问题(插件:quartz2:2.1.6.2,但我测试了插件:quartz:1.0-RC7,但问题没有改变)我的Grails项目(grails 2.2.1)。

我有这样的工作

class MyJob {

def concurrent = false

def execute(context){

        try {

            //....
            // works with domains .....
            myDomain.save(flush: true)
            // works with domains .....
            //....

            sessionFactory.currentSession.flush()

        } catch (org.springframework.dao.OptimisticLockingFailureException olfe) {
            println "Job failed by database exception "
        } catch ( org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException ole){
            println "Job failed by database exception "
        } catch ( org.hibernate.HibernateException hibe ){
            println "Job failed by database exception "
        }
    }

}

}

有时,执行方法中会出现StaleObjectStateException。这对我的逻辑来说没问题,我使用grails乐观锁定,这个例外每周只发生一次。

问题是当这个异常出现时,Job停止再次触发。

我试过在try catch中刷新方法代码并在里面刷新hibernate会话以捕获异常,但没有运气。我的任何捕获都没有捕获例外情况。

在线查看我发现这是一个old grails quartz bug,但它已经修复,无论如何使用try {} catch都必须绕过这个bug。

P.S。 通过此类呼叫

从bootstrab安排作业
MyJob.schedule( 10000L )

停止计划的例外是

[194949896] core.ErrorLogger Unable to notify JobListener(s) of Job that was executed: (error will be ignored). trigger= DEFAULT.MT_3tbn6lewgiqa3 job= DEFAULT.MyJob
org.quartz.SchedulerException: JobListener 'persistenceContextJobListener' threw exception: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42] [See nested exception: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42]]
    at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1939)
    at org.quartz.core.JobRunShell.notifyJobListenersComplete(JobRunShell.java:361)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:235)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyDomain#42]
    at grails.plugin.quartz2.PersistenceContextJobListener.jobWasExecuted(PersistenceContextJobListener.groovy:46)
    at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1937)
    ... 3 more

.....

events.PatchedDefaultFlushEventListener Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [MyJob#42]
    at MyJob.execute(MyJob.groovy:354)
    at grails.plugin.quartz2.GrailsArtefactJob.execute(GrailsArtefactJob.java:57)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557)

3 个答案:

答案 0 :(得分:1)

我为恢复旧帖子而道歉,但我们最近遇到了遗留Grails应用程序(Grails 2.2.3)的这个问题,并且在执行方法中刷新会话并不总能解决问题所以我将概述什么我们确实解决了这个问题。

在我们的例子中,有时异常会在execute方法的上下文之外发生,即使我们在execute方法中显式刷新了会话。更具体地说,抛出异常是在Quartz2插件的PersistenceContextJobListener代码中抛出的,该代码在执行方法执行完毕后刷新会话。因此,在查看Quartz2插件代码后,我们意识到我们需要覆盖包装作业执行方法并刷新会话的默认PersistenceContextJobListener。

首先,请注意PersistenceContextJobListener的jobWasExecuted回调方法中没有异常处理。

https://github.com/9ci/grails-quartz2/blob/master/src/groovy/grails/plugin/quartz2/PersistenceContextJobListener.groovy#L44

您真正需要做的就是实现自己的作业监听器并将tryWasExecuted代码包装在try / catch中。有关如何执行此操作的示例,请参阅以下代码段。

https://gist.github.com/jmiranda/45084eb32f07f6e3d1934547cd4fbb9f https://gist.github.com/jmiranda/5148f0a67afc8950bad950793e9c2303

我们使用原始的Quartz插件的SessionBinderJobListener作为示例(错误,我们或多或少地复制了它)。

https://github.com/grails-plugins/grails-quartz/blob/master/src/main/groovy/grails/plugins/quartz/listeners/SessionBinderJobListener.java

无论如何,这会让你到达防止触发的作业由于未被捕获的StaleObjectStateException而完全停止的程度。

答案 1 :(得分:0)

不知道你的例子有多准确,但你需要知道groovy正在包装异常。这意味着即使在代码处于StaleObjectStateException状态时,您也可以将它包装在简单的RuntimeException中,而上面没有捕获它。 myDomain.save(flush: true)方法有多深(直接在工作中或从其他服务执行)?

答案 2 :(得分:0)

我遇到了类似的问题,石英作业在一个没有绑定hibernate会话的线程上运行,我能够通过抓住一个新会话然后强制刷新()和清除来绕过它()。如果你不冲洗&清除作业最终将重新使用之前的一个作业线程,并尝试写出相同的对象(不能回忆它是否必须是同一个或同一个类的任何对象),但是会有一个un绑定到该线程的会话中的委托副本,这将导致StaleObjectException:

以下是我的代码:

def sessionFactory

def execute() {
    def session = SessionFactoryUtils.getSession(sessionFactory,true)

    myDomain.save(flush: true)

    session.flush()
    session.clear()
}

您可能只需要在示例代码中执行flush()和clear()以获得相同的结果。