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.
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.
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.
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.
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.
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,
Assuming you have a parent Post
entity and a child PostComment
as illustrated in the following diagram:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With