0

I'm using JPA in Swing based desktop application. This is what my code looks like:

public Object methodA() {

  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
  boolean hasError = false;

  try {
       SwingUtilities.invokeLater(new Runnable() {
           public void run() {
              // JPA operation does not work here 
              // because transaction has been committed!
           }
       });
       ...
       return xXx;
  } catch (Exception ex) {
       hasError = true;
       em.getTransaction().rollback();
  } finally {
       em.close();
       if (!hasError) {
          em.getTransaction().commit();
       }
  }
  return null;    
}

I'm using this try - catch - finally for all methods that requires transaction. It is working as expected, except for methods that has SwingUtilities.invokeLater().

finally will be reached before all code in new thread has been executed; thus, if there is JPA operation inside SwingUtilities.invokeLater(), it will fail because transaction has been committed.

Is there a generic use case of try - catch - finally that I can follow to make sure transaction will be committed only after all code has been executed including code inside SwingUtilities.invokeLater() ?

jocki
  • 1,728
  • 16
  • 26
  • 1
    why are you manipulating the entities using `SwingUtilities.invokeLater`? the point of that method is to update the gui _after_ you are done doing the update work. – jtahlborn May 06 '13 at 17:43
  • I'm afraid that one day I will unexpectedly trigger lazy fetching there. – jocki May 06 '13 at 18:21
  • 1
    Lazy fetching is your real problem, so work on resolving it. Your code as written is *the exact opposite* of how you're supposed to work with Swing. And since the EDT thread *never finishes* until your program exits, your basic assumption of how your code should execute is incorrect. – kdgregory May 08 '13 at 13:22
  • I'm planning to use AOP to inject these try-catch-finally to all relevant methods. I will try to solve lazy fetching by keeping EntityManager open as long as possible. Hibernate will perform lazy fetching (lazy loading) even when the code is not inside transaction (maybe this is not in JPA?). This way, lazy fetching will works inside `SwingUtilities.invokeLater` and EDT. – jocki May 08 '13 at 15:17

1 Answers1

1

You have to rethink about your approach. First of all SwingUtilities.invokeLater() is not the best choice for performing JPA operation. The main purpose of that utility method is update UI. Regarding to your code, implement separate thread for JPA operations, this thread will take listener of transaction status. You will update UI, once transaction has completed.

/**
 * Transaction callback.
 */
public interface TransactionListener {
    void onTransactionFinished(boolean hasError);
}

/**
 * Worker Thread which takes data and performs JPA operations.
 */
public class JPATask implements Runnable {
    private final Object dataToPersist;
    private final TransactionListener transactionListener;

    public JPATask(Object dataToPersist,
            TransactionListener transactionListener) {
        this.dataToPersist = dataToPersist;
        this.transactionListener = transactionListener;
    }

    private EntityManager getEntityManager() {/* code is omited */}

    @Override
    public void run() {
        EntityManager em = getEntityManager();
        try {
            em.getTransaction().begin();
            // perform JPA actions here
            em.getTransaction().commit();
            transactionListener.onTransactionFinished(false);
        } catch (Exception ex) {
            em.getTransaction().rollback();
            transactionListener.onTransactionFinished(true);
        }
    }
}

/**
 * Finally you method. Now it looks like this.
 */
public Object methodA() {
    JPATask jpaTask = new JPATask(<data to persist>, new TransactionListener() {
        @Override
        public void onTransactionFinished(boolean hasError) {
            // Update UI. It's time to use SwingUtilities.invokeLater() 
        }
    }).start();     
}
Eugene
  • 520
  • 3
  • 8
  • If I'm performing transaction commit and close the `EntityManager` in `JPATask`, there will be `LazyInitializationException`. I know there shouldn't be lengthy SQL operation here, but sometimes when I pass entities to a `TableModel` or `ComboBoxModel`, their renderer unexpectedly causes lazy fetching to be performed. – jocki May 06 '13 at 19:15
  • 1
    That's because you touch _lazy fields_ out of transaction. The simpliest way to fix this is just use [FetchType.EAGER](http://docs.oracle.com/javaee/6/api/javax/persistence/FetchType.html) parameter in your annotation for appropriate fields. – Eugene May 08 '13 at 20:44