Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 271
SibzTer Avatar asked Oct 22 '09 14:10

SibzTer


People also ask

What is the difference between methods find vs getReference?

The EntityManager getReference method Unlike find , the getReference only returns an entity Proxy which only has the identifier set. If you access the Proxy, the associated SQL statement will be triggered as long as the EntityManager is still open. However, in this case, we don't need to access the entity Proxy.

What does EntityManager find do?

The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities. The set of entities that can be managed by a given EntityManager instance is defined by a persistence unit.

Which method in JPA is used to retrieve the entity based on its primary keys?

The find() method used to retrieve an entity defined as below in the EntityManager interface. T find(Class<T> entityClass, Object primaryKey) – Returns entity for the given primary key. It returns null if entity is not found in the database.

What is the use of EntityManager in JPA?

In JPA, the EntityManager interface is used to allow applications to manage and search for entities in the relational database. The EntityManager is an API that manages the lifecycle of entity instances. An EntityManager object manages a set of entities that are defined by a persistence unit.


2 Answers

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,

like image 166
Arthur Ronald Avatar answered Sep 28 '22 05:09

Arthur Ronald


Assuming you have a parent Post entity and a child PostComment as illustrated in the following diagram:

enter image description here

If you call find when you try to set the @ManyToOne post association:

PostComment comment = new PostComment(); comment.setReview("Just awesome!");   Post post = entityManager.find(Post.class, 1L); comment.setPost(post);   entityManager.persist(comment); 

Hibernate will execute the following statements:

SELECT p.id AS id1_0_0_,        p.title AS title2_0_0_ FROM   post p WHERE p.id = 1   INSERT INTO post_comment (post_id, review, id) VALUES (1, 'Just awesome!', 1) 

The SELECT query is useless this time because we don’t need the Post entity to be fetched. We only want to set the underlying post_id Foreign Key column.

Now, if you use getReference instead:

PostComment comment = new PostComment(); comment.setReview("Just awesome!");   Post post = entityManager.getReference(Post.class, 1L); comment.setPost(post);   entityManager.persist(comment); 

This time, Hibernate will issue just the INSERT statement:

INSERT INTO post_comment (post_id, review, id) VALUES (1, 'Just awesome!', 1) 

Unlike find, the getReference only returns an entity Proxy which only has the identifier set. If you access the Proxy, the associated SQL statement will be triggered as long as the EntityManager is still open.

However, in this case, we don’t need to access the entity Proxy. We only want to propagate the Foreign Key to the underlying table record so loading a Proxy is sufficient for this use case.

When loading a Proxy, you need to be aware that a LazyInitializationException can be thrown if you try to access the Proxy reference after the EntityManager is closed.

like image 20
Vlad Mihalcea Avatar answered Sep 28 '22 05:09

Vlad Mihalcea