javahibernatejpajakarta-eepersistence

When to use EntityManager.find() vs EntityManager.getReference() with JPA


I have come across a situation (which I think is weird but is possibly quite normal) where I use the EntityManager.getReference(LObj.getClass(), LObj.getId()) to get a database entity and then pass the returned object to be persisted in another table.

So basically the flow was like this:

class TFacade{

  createT(FObj, AObj) {
    T TObj = new T();
    TObj.setF(FObj);
    TObj.setA(AObj);
    ...
    EntityManager.persist(TObj);
    ...
    L LObj = A.getL();
    FObj.setL(LObj);
    FFacade.editF(FObj);
  }
}

@TransactionAttributeType.REQUIRES_NEW
class FFacade{

  editF(FObj){
    L LObj = FObj.getL();
    LObj = EntityManager.getReference(LObj.getClass(), LObj.getId());
    ...
    EntityManager.merge(FObj);
    ...
    FLHFacade.create(FObj, LObj);
  }
}

@TransactionAttributeType.REQUIRED
class FLHFacade{

  createFLH(FObj, LObj){
    FLH FLHObj = new FLH();
    FLHObj.setF(FObj);
    FLHObj.setL(LObj);
    ....
    EntityManager.persist(FLHObj);
    ...
  }
}

I was getting the following exception

"java.lang.IllegalArgumentException: Unknown entity: com.my.persistence.L$$EnhancerByCGLIB$$3e7987d0"

After looking into it for a while, I finally figured out that it was because I was using the EntityManager.getReference() method that I was getting the above exception as the method was returning a proxy.

This makes me wonder, when is it advisable to use the EntityManager.getReference() method instead of the EntityManager.find() method?

EntityManager.getReference() throws an EntityNotFoundException if it cant find the entity being searched for which is very convenient in itself. EntityManager.find() method merely returns null if it cant find the entity.

With regards to transaction boundaries, sounds to me like you would need to use the find() method before passing the newly found entity to a new transaction. If you use the getReference() method then you would probably end up in a situation similar to mine with the above exception.


Solution

  • I usually use getReference method when i do not need to access database state (I mean getter method). Just to change state (I mean setter method). As you should know, getReference returns a proxy object which uses a powerful feature called automatic dirty checking. Suppose the following

    public class Person {
    
        private String name;
        private Integer age;
    
    }
    
    
    public class PersonServiceImpl implements PersonService {
    
        public void changeAge(Integer personId, Integer newAge) {
            Person person = em.getReference(Person.class, personId);
    
            // person is a proxy
            person.setAge(newAge);
        }
    
    }
    

    If i call find method, JPA provider, behind the scenes, will call

    SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?
    
    UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
    

    If i call getReference method, JPA provider, behind the scenes, will call

    UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
    

    And you know why ???

    When you call getReference, you will get a proxy object. Something like this one (JPA provider takes care of implementing this proxy)

    public class PersonProxy {
    
        // JPA provider sets up this field when you call getReference
        private Integer personId;
    
        private String query = "UPDATE PERSON SET ";
    
        private boolean stateChanged = false;
    
        public void setAge(Integer newAge) {
            stateChanged = true;
    
            query += query + "AGE = " + newAge;
        }
    
    }
    

    So before transaction commit, JPA provider will see stateChanged flag in order to update OR NOT person entity. If no rows is updated after update statement, JPA provider will throw EntityNotFoundException according to JPA specification.

    regards,