4 Replies Latest reply on Jan 30, 2009 5:15 PM by fridgebuzz

    Transactions in Application scoped component using Quartz timer

    fridgebuzz

      Hi,


      I'm having trouble with a no transaction is in progress exception whenever I try to flush an entity manager in a particular case (I'll explain below.)


      There are two separate things going on here that could be the cause and I don't know which. The first is that I'm using an Application-scoped component, and the second is that I'm using an @Asynchronous method (a Quartz scheduled method.) Either one alone could be the problem, so I apologize if I'm confounding the two.


      What I have is an Application-scoped component that starts a periodic Quartz-scheduled task to clean up un-needed records in the database.


      In shortened form--with some messy details removed--the code looks something like this:



      @Name("accountReaper")
      @Startup
      @Scope(ScopeType.APPLICATION)
      public class AccountReaper {
      
           private EntityManagerFactory emf = null;
      
           public AccountReaper() {
           emf = Persistence.createEntityManagerFactory("yourview");
           reapInterval =  reapHours * 60 * 60 * 1000;
           reapAccounts(new Date(), reapInterval);
           }
      
           @Asynchronous
           public QuartzTriggerHandle reapAccounts(@Expiration Date when, 
                                      @IntervalDuration Long interval) {
                
      
           Calendar cal = Calendar.getInstance();
           cal.add(Calendar.HOUR_OF_DAY, -1*timeoutHours);
           Date maxCreationDate = cal.getTime();
                
           pruneUsers(maxCreationDate);
                
           return null;
           }
      
           @Transactional
           private void pruneUsers(Date maxCreationDate) {
                
           EntityManager entityManager = emf.createEntityManager();
      
           Query query = entityManager.createQuery("select m from User m
                  where m.creationDate < :date and m.status = :status")
           .setParameter("date", maxCreationDate, TemporalType.TIMESTAMP)
           .setParameter("status", User.UserStatusType.UNCONFIRMED);
      
           List<User> users = query.getResultList();
           for (User user: users) {
                entityManager.remove(user);
           }
           if (!users.isEmpty()) entityManager.flush();
      
      }
      


      The trouble is that the flush() causes a this exception:



      javax.persistence.TransactionRequiredException: no transaction is in progress



      In a way it makes sense that @Transactional wouldn't work, since that applies to conversations and we're not in conversation-land here anymore (I believe.) Can anyone give me a hint about how to proceed? I don't know if it's the @Asynchronous method that's the problem or the Application-scope of the component that's the problem.


      The only solution I can think of to try is to create my own transaction, with something like (leaving out the try/catch stuff for brevity):



           UserTransaction transaction = Transaction.instance();
           transaction.begin();
           for (User user: users) {
                entityManager.remove(user);
           }
           if (!users.isEmpty()) entityManager.flush();
           transaction.commit();
      



      Am I on the right track? Can anyone suggest a better solution or explain the cause of the problem?


      Thank you all for your attention,


      Vanessa

        • 1. Re: Transactions in Application scoped component using Quartz timer
          fridgebuzz

          I'm replying to myself to add that the following alternative for the pruneUsers() method did seem to work.



          private void pruneUsers(Date maxCreationDate) {
                    
               EntityManager entityManager = emf.createEntityManager();
               Query query = entityManager.createQuery("select m from User m
                     where m.creationDate < :date and m.status = :status")
               .setParameter("date", maxCreationDate, TemporalType.TIMESTAMP)
               .setParameter("status", User.UserStatusType.UNCONFIRMED);
               List<User> users = query.getResultList();
               if (!users.isEmpty()) {
               UserTransaction transaction = Transaction.instance();
               try {
                    transaction.begin();
                    entityManager.joinTransaction();
                    for (User user: users) {
                    entityManager.remove(user);
                 }
                 transaction.commit();
               } catch (Exception e) {
                    log.error("Unable to purge expired users", e);
               } 
          }
          



          I'd still appreciate knowing if this is the best way to accomplish this, or if there is a better way. For all I know, this only appears to work correctly, but may be in error in some way.


          Thanks again,


          Vanessa

          • 2. Re: Transactions in Application scoped component using Quartz timer
            nicolas.bielza

            I think you should use a distinct component to call the asynchronous method. If you call it from the constructor, you will bypass Seam interceptor and there will be two consequences:


            1) There won't be a transaction
            2) The method will not repeat itself


            Try creating a @Startup component, inject AccountReaper into it and call reapAccounts() from there. Another bonus is that you will be able to use injection (for instance of the EntityManager) in the AccountReaper component.


            There is no need to create your own transaction (the @Transactional annotation will work).

            • 3. Re: Transactions in Application scoped component using Quartz timer
              fridgebuzz

              Thanks for the suggestions, Nicolas. Really appreciate it. I'm going to try it right now. Cheers,


              Vanessa

              • 4. Re: Transactions in Application scoped component using Quartz timer
                fridgebuzz

                You absolutely saved my life with that suggestion, Nicolas. I was about ready to throw Seam on the dust-heap!


                Cheers,


                Vanessa