1

I'm using Hibernate 3.3.0.GA and I noticed some strange behavior, not documented (I think).

I noticed that entityManager.find resolve the EAGER relationships of my entity and entityManager.createQuery not.

Per example, if I have an entity:

@Entity
public class Person {

     @Id
     private int id;

     private String name;

     @ManyToOne //EAGER by default in JPA
     private Address address;

}

The generated SQL with entityManager.find(Person.class, 1L):

select
    person0_.id as id1_1_,
    person0_.address_id as address3_1_1_,
    person0_.name as name1_1_,
    address1_.id as id2_0_ 
from
    Person person0_ 
left outer join
    Address address1_ 
        on person0_.address_id=address1_.id 
where
    person0_.id=?

And the generated SQL with entityManager.createQuery("SELECT p FROM Person where id = 1"):

select
    person0_.id as id1_,
    person0_.address_id as address3_1_,
    person0_.name as name1_ 
from
    Person person0_ 
where
    person0_.id=?

So, is there a explanation about why this happen? For me, both need to have the same behavior.

Working example

I create an example in my repository showing that problem, using Hibernate 4.1.6.Final: https://github.com/dherik/hibernate-find-em-so-question. Just use mvn clean install and the console will print the queries.

Updated

@KlausGroenbaek said the EclipseLink 2.5.2 have the same behaviour in the two methods. He did a Hibernate example too and obtain the similar result in the two methods (find it does a join fetch and createQuery it does multiple selects, both EAGER by definition).

Dherik
  • 17,757
  • 11
  • 115
  • 164
  • Both queries do have the same behavior, at least from the point of view of the person data being the same in both. Do you have specific code which is having a problem? – Tim Biegeleisen Jan 16 '17 at 15:06
  • Once the Person is loaded it must have a valid Address instance or null. ManyToOne can only be lazy if you use weaving to create an Address subclass. Are you sure there is not a second query for selecting the Address, maybe it is not logged? – Klaus Groenbaek Jan 16 '17 at 16:52
  • Hi @TimBiegeleisen, yes, I have the code (the simulation was there). Sadly, I can't share the code, but I can create an example in my github and share. – Dherik Jan 16 '17 at 16:58
  • @KlausGroenbaek, I'm sure. This specific result was reproduced in the Display tab from Eclipse, during the Debug, and works like that for any entity in the same application. – Dherik Jan 16 '17 at 17:01
  • I did a test with EclipseLink 2.5.2 (JavaSE), and there there was no difference between using em.find() and em.createQuery(), the logs are exactly the same. Are you using the same EntityManager for both calls, and if you are, did you remember to call EntityManager.clear() in between to clear the cached entities (because I forgot, and then the query generated no SQL at all). – Klaus Groenbaek Jan 16 '17 at 17:41
  • @KlausGroenbaek I'm using the same entity manager. We already have some optimizations in our system changing the `find` to `createQuery` and the gain of performance are expressive for entities with a lot of `EAGER` mappings. Do you have some eager relationship on your test? If all relationships area `LAZY` in the returned entity, the SQLs will be the same. – Dherik Jan 16 '17 at 17:46
  • ManyToOne is always Eager (unless you use weaving), and the class I used for my test has two ManyToOne. I'm using EclipseLink and not Hibernate, but I don't think there should be any difference. If you use the same Entity manager, for both find() and createQuery(), you have no idea which SQL it will generate unless you call em.clear() in between, as the Persistence Context will cache any entity already loaded. – Klaus Groenbaek Jan 16 '17 at 17:58
  • @KlausGroenbaek I will try to create an example using Hibernate to demonstrate the problem. But I'm glad to know that EclipseLink have the expected behavior. – Dherik Jan 16 '17 at 18:02
  • I did a Hibernate example, and for ```find()``` it does a join fetch, and for ```createQuery()``` it does multiple selects (EclipseLink does multiple selects for both find and createQuery). Both solutions are technically Eager fetch (only one is join fetch), as Eager just means that the referenced entity should be loaded when the call completes (it does not specify how it is loaded). If you want join fetch to work for createQuery I think you need to use the join fetch query syntax explicitly. In my example it did not respect ```@Fetch(FetchMode.JOIN)``` – Klaus Groenbaek Jan 16 '17 at 18:35
  • @KlausGroenbaek, thank you! I will try to do an example with the same version of Hibernate that I'm using (3.5, very old), because this is very strange. What Hibernate version you used? I'm sure that only one query are triggered using the `createQuery` in my example. – Dherik Jan 16 '17 at 18:44
  • I'm using Hibernate 5.2.5. One query does not make sense, and it makes no sense that the first query doesn't select the ```p.address_id```. Even if the Address was already loaded into the current persistence context, it would need to know the primary key in order to find it. – Klaus Groenbaek Jan 16 '17 at 18:55
  • @KlausGroenbaek, sorry, I edited the SQL. As you can see, in `createQuery` it brings the `address_id` from Person. It brings all information from table `Person` in `createQuery`. – Dherik Jan 16 '17 at 19:04
  • You didn't include @JoinColumn(nullable=??), is there any chance that the Address field was null after the Person was loaded with createQuery(). If you ever generate DDL from you JPA model it is essential to use the nullable in Column and JoinColumn. I can say from experience that a novice JPA developer can do a lot of damage when he misses this, so in a JPA project with many developers, I always write a test to enforce that all fields have nullable, and that foreign key fields are named correctly (and a bunch of other stuff) – Klaus Groenbaek Jan 16 '17 at 19:24
  • @KlausGroenbaek, create an example in my repository showing the problem. – Dherik Jan 16 '17 at 20:38

1 Answers1

2

I have looked at your example and after fixing it, it works exactly like HiberNate 5.2.5 find() uses a join and createQuery uses multiple selects.

The reason you don't see this in your example is because you have NO DATA in the database, find still does a join, but because there is no Person in the DB, it doesn't need to look for any Address, so the second select is missing.

However, you will notice that even if you add data to your example, the select from address does not show up. This is because the address is already in the Persistence Context from when you used find(), so there is no need to load it again - In fact JPA MUST return the exact same Java instance for the address if it is already loaded in the Persistence Context. If you insert em.clear() or use a different Persistence Context (EntityManager) you will see the select because the address is not already loaded.

Your example is of cause just an example, but the way you store an application managed EntityManager in a field, is absolutely forbidden, they should always be local variables when you manage them your self. If you use a container managed EntityManager (Spring JavaEE) they can be fields when injected using @PersistenceContext, but that is only because the injected EntityManager is a proxy which stores the state is a ThreadLocal.

I have used JPA extensively since 2009, and I made a lot of mistakes in the beginning because I didn't understand exactly what a Persistence Context was, so I had overlapping EntityManagers, unmaintained bidirectional relations, and I didn't fully understand Entity states, or container vs application managed EntityManager. Much of it I learned the hard way (which for me was debugging and reading the EclipseLink source code); looking back I should definitely have bought a book to get the big picture, instead of solving problems by randomly Googling what I though the issue was. For anyone how have not read the JPA documentation, a good book, or in dept article, do your self a favor and learn from my mistakes.

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • Hi! What fixes you made? I invert the order of queries and the `find` and `createQuery` still have the same behavior. I used `em.clear()` after the `find` and `createQuery` and not different happens too. – Dherik Jan 17 '17 at 01:24
  • I have forked you project, converted it to gradle (I refuse to use Maven and its verbose XML configuration), and fixed the issues so you can see the queries. Again your problem is that without data in the database you don't see all the queries. https://github.com/QwertGold/hibernate-find-em-so-question – Klaus Groenbaek Jan 17 '17 at 16:20