【问题标题】:Grails Quartz Job has no Hibernate session after upgrade, causing LazyInitializationExceptionGrails Quartz Job 升级后没有 Hibernate 会话,导致 LazyInitializationException
【发布时间】:2010-12-24 01:15:25
【问题描述】:

我已将 Grails 1.0.4 应用程序升级到 1.1.1。升级后,我在执行 Quartz 作业时反复出现异常(使用 Quartz 插件 0.4.1)。该插件用于通过服务使用 Simple 和 Cron Triggers 手动安排作业(代码如下):

class SchedulerService implements InitializingBean
{
    static scope = 'singleton'
    ...
    def schedule(def batch) {
        JobDetail job = new JobDetail(uniqueId, groupName, BatchJob.class, false, false, true)
        job.jobDataMap.put("batchId", batch.id)

        SimpleTrigger trigger = new SimpleTrigger(triggerId, triggerGroup, 0)

        SchedulerFactory factory = new SchedulerFactory()
        factory.initialize(properties)
        Scheduler scheduler = factory.getScheduler()

        scheduler.scheduleJob(job, trigger)
    }
    ...
}

我的 BatchJob 作业设置如下:

class BatchJob implements Job, InterruptableJob
{
    static triggers = {}
    void execute(JobExecutionContext context) {
        def batch = Batch.get(context.jobDetail.jobDataMap.getLongValue("batchId"))
        // the next line is "line 49" from the stack trace below
        def foo = batch.batchStatus.description
    }
}

这是 Batch.groovy(域)的缩写定义:

class Batch
{
    BatchStatus batchStatus // relationship
}

但是,当 schedulerService.schedule() 使用现有的已保存批处理调用时,我收到以下异常:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
        at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:86)
        at org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil.unwrapProxy(GrailsHibernateUtil.java:311)
        at org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil$unwrapProxy.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
        ...
        <b>at BatchJob.execute(BatchJob.groovy:49)</b>
        ...

我已尝试以下操作来解决此问题,但均未奏效:

  • 我在 Batch 域类中指定了 static fetchMode = [batchStatus: 'eager']
  • 我在 Batch 域类中使用了 static mapping = { columns { batchStatus lazy:false }}
  • 在 Job 中调用 Batch.get() 后,我尝试使用 batch.attach()

在这种情况下我不能使用BatchJob.triggerNow(),因为这只是几个示例之一 - 其他示例仍由服务安排,但可能被安排为 cron 作业或其他方式。我应该提一下,我在升级 Grails 时也升级了 Quartz 插件;之前的 Quartz 版本是 0.4.1-SNAPSHOT(相对于升级版,只有 0.4.1)。

如何让 Hibernate 会话在这些手动触发的 Quartz 作业中正常工作?

我也将这个问题发送到 grails-user 邮件列表,至于像这样的更小众问题,该列表似乎会引起更多的响应。如果有人从那里出来,我会用答案更新这个问题。 Here's a link.

【问题讨论】:

    标签: grails scheduling quartz-scheduler hibernate-session


    【解决方案1】:

    我认为您可以调用attach() 方法将会话添加到传递给计划作业的对象。

    job.attach()
    

    【讨论】:

      【解决方案2】:

      使用最新的 grails 版本 (Grails 2.0.0) 和更早的版本,您可以使用这个辅助方法来包装您的调用:

      class BatchJob implements Job, InterruptableJob
      {
        static triggers = {}
      
        void execute(JobExecutionContext context) {
          Batch.withSession { sess ->
            def batch = Batch.get(context.jobDetail.jobDataMap.getLongValue("batchId"))
            def foo = batch.batchStatus.description
          }
        }
      }
      

      【讨论】:

      • 对我完全不起作用,但 Batch.withTransaction{ .. } 起作用。
      • withSession 只提供了一种简单的方法来访问已经绑定到线程的现有会话。见stackoverflow.com/questions/26933458/…
      【解决方案3】:

      查看 jira 问题 165 (http://jira.codehaus.org/browse/GRAILSPLUGINS-165) Quartz Plugin 中也有线索(您可能想查看)此代码与 JMS 插件一起使用,似乎运行良好。

      试试

          import org.hibernate.FlushMode
          import org.hibernate.Session
          import org.springframework.orm.hibernate3.SessionFactoryUtils
          import org.springframework.orm.hibernate3.SessionHolder
      
          class BatchJob implements Job, InterruptableJob
          {
              static triggers = {}
              void execute(JobExecutionContext context) {
                 Session session = null;   
                 try { 
                    session = SessionFactoryUtils.getSession(sessionFactory, false); 
                 }
                 // If not already bound the Create and Bind it! 
                 catch (java.lang.IllegalStateException ex) { 
                    session = SessionFactoryUtils.getSession(sessionFactory, true);  
                    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); 
                 }
                session.setFlushMode(FlushMode.AUTO);
                if( log.isDebugEnabled()) log.debug("Hibernate Session is bounded to Job thread");
      
              // Your Code!
              def batch = Batch.get(context.jobDetail.jobDataMap.getLongValue("batchId"))
              // the next line is "line 49" from the stack trace below
              def foo = batch.batchStatus.description
      
      
      
              try {
               SessionHolder sessionHolder = (SessionHolder) 
               TransactionSynchronizationManager.unbindResource(sessionFactory);
               if(!FlushMode.MANUAL.equals(sessionHolder.getSession().getFlushMode())) {
                 sessionHolder.getSession().flush(); 
               }
               SessionFactoryUtils.closeSession(sessionHolder.getSession());
               if( log.isDebugEnabled()) log.debug("Hibernate Session is unbounded from Job thread and closed");
             }
             catch (Exception ex) { 
               ex.printStackTrace(); 
             }
         }
      }
      

      希望这会有所帮助。它对我有用。

      【讨论】:

      • 斯科特,感谢您的回答。几个问题:1) 是注入了sessionFactory 实例(即def sessionFactory),还是通过XML bean 配置配置 - 当我将sessionFactory 传递给getSession() 时,sessionFactory 为空; 2)我已经对您指定的代码使用哪些导入做了一些假设,并将它们添加到您的答案中的代码 sn-p - 您可以验证它们吗?再次感谢。
      • 要添加到我上面的评论中,我查看了 SessionBinderJobListener 的代码(此示例代码所基于)svn.codehaus.org/grails-plugins/grails-quartz/tags/… ,看起来它的sessionFactory 有访问器方法.
      • 我看起来你在正确的轨道上。很抱歉延迟回来。
      • 我还没有时间重新审视这个问题,但是我上次使用它时仍然遇到了一些麻烦。但是,我相信这些麻烦是由于我们使用 Quartz 的方式造成的,并且我相信我可以重新设计我们正在做的事情以使其正常工作。我会接受这个答案,因为我相信如果遇到类似的问题,它会解决大多数其他人的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-27
      • 2014-05-22
      • 1970-01-01
      • 2014-07-17
      • 2011-01-18
      • 1970-01-01
      • 2013-01-03
      相关资源
      最近更新 更多