Grails Quartz Job не имеет сеанса Hibernate после обновления, что вызывает LazyInitializationException

Я обновил приложение Grails 1.0.4 до версии 1.1.1. После обновления я неоднократно получаю исключения при выполнении моих заданий Quartz (используя плагин Quartz 0.4.1). Плагин используется для ручного планирования заданий с использованием триггеров Simple и Cron через сервис (перефразированный код ниже):

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>
        ...

Я пробовал следующие действия, чтобы исправить это, но ни одно из них не сработало:

  • Я указал static fetchMode = [batchStatus: 'eager'] в своем классе домена пакетной службы.
  • Я использовал static mapping = { columns { batchStatus lazy:false }} в своем классе домена пакетной службы
  • Я пытался использовать batch.attach() после вызова Batch.get() в задании.

Я не могу использовать BatchJob.triggerNow() в этом случае, потому что это только один из нескольких примеров — остальные по-прежнему планируются службой, но могут быть запланированы как задание cron или как-то иначе. Я должен упомянуть, что я также обновил плагин Quartz при обновлении Grails; предыдущая версия Quartz была 0.4.1-SNAPSHOT (в отличие от обновленной версии, всего 0.4.1).

Как заставить сеансы Hibernate правильно работать в этих запускаемых вручную заданиях Quartz?

Я также отправил этот вопрос в список рассылки пользователей Grails, так как в отношении такой узкой темы, как эта, список, кажется, вызывает больше откликов. Я обновлю этот вопрос ответом, если он выйдет оттуда. Вот ссылка.


person Rob Hruska    schedule 07.12.2009    source источник


Ответы (3)


Ознакомьтесь с проблемой jira 165 (http://jira.codehaus.org/browse/GRAILSPLUGINS-165) В плагине Quartz также есть подсказки (которые вы, возможно, захотите проверить). Этот код использовался с плагином 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(); 
       }
   }
}

Надеюсь это поможет. Это сработало для меня.

person Scott Warren    schedule 07.12.2009
comment
Скотт, спасибо за ответ. Пара вопросов: 1) введен ли экземпляр sessionFactory (т.е. def sessionFactory) или он настроен с помощью конфигурации компонента XML - sessionFactory имеет значение null, когда я передаю его getSession(); 2) Я сделал несколько предположений о том, какой импорт использовать для указанного вами кода, и добавил их в фрагмент кода в вашем ответе - можете ли вы их проверить? Еще раз спасибо. - person Rob Hruska; 08.12.2009
comment
Чтобы добавить к моему комментарию выше, я взглянул на код SessionBinderJobListener (на котором основан этот пример кода) svn.codehaus.org/grails-plugins/grails-quartz/tags/ , и похоже, что у него есть методы доступа для его sessionFactory. - person Rob Hruska; 08.12.2009
comment
Мне кажется, ты на правильном пути. Извините за задержку с ответом. - person Scott Warren; 11.12.2009
comment
У меня не было времени вернуться к этой проблеме, но в последний раз, когда я работал с ней, у меня все еще были некоторые проблемы с этим. Тем не менее, я считаю, что эти проблемы связаны с тем, как мы используем Quartz, и я считаю, что могу перестроить то, что мы делаем, чтобы заставить его работать правильно. Я приму этот ответ, так как считаю, что он решит проблемы большинства других людей, если они столкнутся с чем-то подобным. - person Rob Hruska; 22.02.2010

В последней версии 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
    }
  }
}
person Ilya Scharrenbroich    schedule 16.04.2012
comment
У меня вообще не сработало, но Batch.withTransaction{ .. } работает. - person sebnukem; 24.07.2014
comment
withSession предоставляет только простой способ доступа к существующему сеансу, уже привязанному к потоку. См. раздел stackoverflow.com/questions/26933458/. - person bassmartin; 16.04.2015

Я думаю, вы можете вызвать метод attach(), чтобы добавить сеанс к объекту, переходящему в запланированное задание.

job.attach()
person Habtamu    schedule 03.04.2015