0

I'm using Hibernate with the JPA API in Java and I'm running the following test:

public void test1()
{
    A a = new A();
    a.setValue(1);
    getDao().store(a);

    a.setValue(2);

    A loaded = getDao().load(a.getId());
    assertEquals(1, loaded.getValue());
}

I am expecting the test to pass because the second part of the code with the load call may be called by a different method that loads the object for a different purpose. I am expecting the state of the object to reflect the state of the database and this is not the case here!

So I found out that the reason this happens is that the persistence context is caching the object unless we detach it. So the way I implemented the store method is:

void store(T object)
{
   EntityManager em = getEntityManager();
   em.getTransaction().begin();

   if (object.getId() == 0)
   {
      em.persist(object);
   }
   else
   {
      em.merge(object);
   }

   em.getTransaction.commit();
   em.detach(object); // Detaches this very object from 1st level cache so that it always reflects DB state
}

public T load(long id)
{
   return getEntityManager().find(getPersistentClass(), id);
}

The detach call made the test pass but since then I have a different problem: When I'm storing an entity that contains a reference to a different object and a getter which is using that referenced object is annotated to be validated the validation fails because the persistence context (or the Hibernate session) is passing a different object to the validator (a copy which is not having the same state). Let me give you an example:

class B
{
   private int value;

   @Transient
   private C c = new C();   // Shall not be persisted

   @Size(min = 1)
   public String getName()
   {
      return c.getName();
   }

   public void setName(String name)
   {
      c.setName(name);
   }
}

public void test2()
{
    B b = new B();
    b.setName("name");
    getDao().store(b);   // Includes detach, as above

    // Validation has passed. c.name was "name".

    getDao().store(b);

    // Validation has failed. c.name was empty.
}

So c.name was empty in the second attempt although I haven't changed the object. The only thing inbetween is the detach() call. If I remove it it test2 will pass but test1 will fail again. What kind of magic is going on here? How do I fix this?

FarwoodHill
  • 552
  • 1
  • 9
  • 18
  • `em.getTransaction.commit();` makes me shudder (even though it doesn't compile as is). Unless you know what you're doing you should _not_ manually begin and commit transactions. I guess you want to call `flush()` here. – Thomas Feb 15 '17 at 12:39
  • When I remove the begin and commit I get a javax.persistence.TransactionRequiredException: no transaction is in progress. This is a plain Java app. It's not running from a Java EE container. – FarwoodHill Feb 15 '17 at 13:21
  • Well, in that case you should at least begin and commit the transaction within the same scope, i.e. not begin it outside `store()` but commit inside. – Thomas Feb 15 '17 at 13:36
  • Oh yes, of course. I simply forgot to copy that line from the source code (for simplicity I did not copy the whole code), sorry. I've added that in the code above. Apart from that, transactions don't seem to be part of my problem. – FarwoodHill Feb 15 '17 at 13:54
  • Can you add some details on how you validate? Is it your own code or do you use Hibernate Validation? If so, how is it configured? – Thomas Feb 15 '17 at 14:26
  • I use Hibernate validation. It works out of the box without configuring, I simply attach the annotations. I stepped through the call stack and the problem seems not to be in the implementation of validation itself but the entity that is passed to validation from the Hibernate session is not the same instance of B. It's a 'shadow' object with a different reference. All attributes are the same but the c.name is empty. – FarwoodHill Feb 15 '17 at 14:39
  • That's what I'd expect from `merge()`: it gets the entity from cache or the db and "merges" the entity you passed into it. Since `c` is transient it's not merged. Then persisting an entity there's no need to load or create one and thus the one you passed is being used. – Thomas Feb 16 '17 at 08:43

0 Answers0