2

What happens when committing a transaction from the entity manager that doesn't contain any dirty object? Can it be that no COMMIT command is sent to the DB?

I was having some test cases failing now and then without a reasonable cause. After some investigation, I have now a theory which I would like to have confirmed here.

I have a small fixture framework to prepare the data on the DB for each test. The fixtures use such a method to store objects to the DB using JPA (Hibernate):

  public <R> R doInTransaction(final Function<EntityManager, R> whatToDo) {
    final EntityManager em = emf.createEntityManager();
    final R result;
    try {
      try {
        em.getTransaction().begin();
        result = whatToDo.apply(em);
        em.getTransaction().commit();
      } finally {
        if (em.getTransaction().isActive()) {
          em.getTransaction().rollback();
        }
      }
    } finally {
      em.close();
    }
    return result;
  }

So, the fixture calls this method passing the whatToDo function where objects are persisted and the method wraps a transaction around the passed function. My failing test cases are using a fixture that relies on legacy code that uses stored procedures and store the objects directly via JDBC, i. e. instead of using em.persist(), I use the following in the passed function to call the stored procedures:

em.unwrap(Session.class).doWork(connection -> {
  // stored procedures are called here directly over JDBC
});

So, my theory is that JPA on this circumstance is not immediately committing as there are no JPA dirty objects managed by the EntityManager. Consequently, the actual commits occurs only later, i. e. after the assertion of my test and the test fails. Could it be?

What is the transactional behaviour of Hibernate when "unwrapping" the connection out of the EntityManager?

I've added now an em.flush() before the em.getTransaction().commit() and it seems to help, but I'm still not 100% confident that this solves the issue. Can somebody confirm?

Alex
  • 1,126
  • 1
  • 11
  • 24
  • You have to be careful when mixing and matching persistence handlers. Hibernate maintains a cache of whats dirty and what's not. If you're updating things outside hibernate, there's a possibility of hibernate getting out of sync with the db and dropping an update because it thinks its cached version of the entity is still valid. I used to run into issues like this a lot a few years ago, and if i remember correctly, we disabled the cache in hibernate to prevent the behavior. – Stephan Feb 21 '17 at 18:52
  • I prefer not entering the mysterious world of caching in Hibernate... :) If `em.flush()` would do the job here, this is would be more than enough for me. – Alex Feb 21 '17 at 19:50
  • If youre in a test environment, you can always try em.flush() and see. As far as the cache settings, if youre not expecting a ridiculous number of concurrent users, you should be able to disable the cache in hibernate and let the cache in the database handle the bulk of the work. That's another thing, the default caching of the db can interfere with the default caching of hibernate. I highly recommend turning off the hibernate cache if you continue to run into issues. – Stephan Feb 21 '17 at 19:57

1 Answers1

1

The behavior is the same regardless of unwrapping the connection. if you are not using JTA, the alternative is the underlying transaction provided by JDBC, i.e. local transaction. (or you can implement your own managed transaction provider)

When you unwrap the connection and deal with JDBC directly, you still get the same connection that is initially obtained by this session/entity manager. So it's the same effect.

raminr
  • 784
  • 3
  • 12
  • 1
    Correct, unwrapping the connection and dealing directly with JDBC gives me the same connection, that's why I was hoping the that `em.getTransaction().commit()` would also commit the changes done directly on JDBC. But this is apparently not necessarily the case if there aren't dirty objects, right? My theory is therefore valid? Would `em.flush()` force the commit? I prefer not commiting directly from JDBC so that whatever is done inside `doInTransaction()` is one single transaction from the DB's perspective. – Alex Feb 21 '17 at 19:46
  • If there are any updates made to a JPA entity, it is marked as dirty. In Hibernate, changes to an entity are translated into events, and only at flush time, the events are translated into SQL and passed to JDBC. If there are no changes (no dirty entities), there will not be any SQL in the pipeline, so commit or no commit will have the same effect, i.e. no op. – raminr Feb 21 '17 at 20:28