1

Im using JPA in my Java ee application. I have a table A mapped to a class A. Now when I query the entity (e.g. by its id) it always returns a different object. Example:

public A getAById(int id) {
    A obj = (A) em.createNamedQuery("getAById")
            .setParameter("id", id).getSingleResult();
    return obj;
}

public void test(){
    getAById(1)==getAById(1) //is false
}

Is there a way to tell JPA not to always create new instances when querying from a database but to return an existing object if it hast alreay been queried?

//EDIT This article helped me a lot: http://en.wikibooks.org/wiki/Java_Persistence/Caching#Example_JPA_2.0_Cacheable_annotation

user1727072
  • 309
  • 4
  • 14
  • 3
    [This][1] [1]: http://stackoverflow.com/questions/4226772/jpa-does-entitymanager-find-always-return-the-same-object-reference-for-the-s contains a good explanation on the topic. – Olimpiu POP Feb 06 '13 at 12:20
  • Update your actual namedQuery `getAById` in your question – vels4j Feb 06 '13 at 12:39
  • Are you sure both queries are executed in the same EntityManager and transaction? – Adam Dyga Feb 06 '13 at 14:10

3 Answers3

3

Is there a way to tell JPA not to always create new instances when querying from a database but to return an existing object if it hast already been queried?

JPA never creates new entity for select queries. See the following example

Query qry = em.createNamedQuery("SELECT r FROM BusinessUnit r WHERE r.buKey = :buKey");
Object result1 = qry.setParameter("buKey", "32").getSingleResult();
qry = em.createNamedQuery("SELECT r FROM BusinessUnit r WHERE r.buKey = :buKey");
Object result2 = qry.setParameter("buKey", "32").getSingleResult();
System.out.println(result1 == result2);

For the above, I get result1 == result2 true, since hashCode method is overwritten in entity class.

If your entity class is overwritten the equals method, you can test with equals method instead of comparing with object reference.

public void test(){
    getAById(1).equals(getAById(1)) ;
}
vels4j
  • 11,208
  • 5
  • 38
  • 63
  • You probably didn't understand what's the issue – Adam Dyga Feb 06 '13 at 12:35
  • Removed downvote. And your examples show something that I always thought, that two queries in the same persistence context always return the same instance. That's why I was surprised with the other answers – Adam Dyga Feb 06 '13 at 14:09
2

JavaEE containers wrap the JPA providers EntityManager in a proxy, so the behavior you see is likely because your container gets a new EM outside of a transaction. Your two getAById(1) calls are going to two different EntityManagers underneath the covers. You can get around this by wrapping them in a single transaction, forcing the container to use the same entity manager for the life of the transaction.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • So basically I have two options: Create an application-wide EntityManager that is used by all DAO-EJBs or save the A-Object in an application-wide List and check if the A-object exists in that list before querying from the database? – user1727072 Feb 06 '13 at 13:45
  • but then I only have the same objects for one request and not for the whole application right? – user1727072 Feb 06 '13 at 15:03
  • 2
    yes. If you are holding onto the object past the life of the request, it loses its scope. The common use case is to make changes which require merging them into the current context anyway. Implementing a cache is possible but I've never seen a need for the extra resources and complexity - it would need to account for changes and relationships all being read from different contexts. Duplicating something some JPA providers second level caches already do. – Chris Feb 06 '13 at 16:51
-2

if there is already an existing entity instance in the persistence context, yes, but you need to use EntityManager operations such entityManager.find(entity.class, id); Doing this will ask entitymanager to find entity from associated PersistenceContext first, if it cannot find it it will query from DB. Once the entity instance is first time loaded into associated PersistenceContext, the rest query against same entity ID will not trigger a DB query and always reuse the one cached in PersistenceContext. This is JPA first level cache.

Using JPQL will not fit your requirement, since JPQL will bypass PersistenceContext and always communicate with DB directly, which will end up with getting different java object(same entity in terms of primary key).

spiritwalker
  • 2,257
  • 14
  • 9
  • Damn it, I always thought that if a query returns entities, the `PersistenceContext` will be asked anyway for their presence in memory... – Adam Dyga Feb 06 '13 at 12:36
  • entityManager operation actually does what you thought but not JPQL Query :) And there is a simple way to verify the difference between entityManager.find() and JPQL query. Firstly turn on show_sql in JPA provider property, and then use entityManger.persist() to save a new entity and use entityManager.find(), you will find that there is not corresponding "select" statement printed out, because the find() method will reuse the one in PersistenceContext. – spiritwalker Feb 06 '13 at 12:39
  • And also, if, for whatever reason, you have to use **JPQL** for retrieving entity instance, make sure there was no update made against same entity(database record) beofore the **JPQL** Query within same **PersistenceContext**, otherwise you will end up with two different entity instances(not only just difference java object, but different values). So if you cannot not guarantee that, it would be always good to force **flush** your current **persistence context** first. – spiritwalker Feb 06 '13 at 12:48
  • Again, I thought that JPA (Hibernate) would automatically flush the entity if it decides that the change may affect query result.... I need to check that – Adam Dyga Feb 06 '13 at 12:57
  • by default the FlushMode is **commit** – spiritwalker Feb 06 '13 at 13:05
  • 1
    It should return the same entity instance if you are in the same EntityManager context regardless of querying using find, JPQL or a native SQL query. – Chris Feb 06 '13 at 13:13
  • @Chris, yes, you are right. I've run a few testing case and seems **JPQL select** query wouldn't create new entity instance as long as in the same **PersistenceContext**. But the interesting thing is that, **JPQL update** and **delete** query will not synchronize the existing entity instance in **PersisenceContext**. it is bit odd, but thanks for the comments anyway. I will vote. – spiritwalker Feb 06 '13 at 13:36
  • @spiritwalker check your flush mode. I'm pretty sure with the default setting (AUTO I guess) Hibernate flushes any pending updates if it decides that the update would affect query result. Not sure about other JPA implementations... – Adam Dyga Feb 06 '13 at 14:14
  • bulk Update and deletes are not required to touch/modify managed entities, and the specification recommends only issuing them in a newly acquired context for this reason - so that following read queries get the modified data from the database rather than return now potentially stale managed entities. – Chris Feb 06 '13 at 16:35