Search code examples
hibernatejpahql

HQL Constructor With FETCH JOIN


Assuming I have a class like:

public class Primary {
    private int id;
    private Secondary secondary;
    private String otherMember1;
    private float otherMember2;
    etc...
}

I'm trying to create a DTO projection with a constructor in HQL with a query like:

SELECT com.example.Primary(p.id, p.secondary) from Primary p LEFT JOIN FETCH p.secondary

However, I get an error message when attempting to do that:

query specified join fetching, but the owner of the fetched association was not present in the select list

However, I feel like I am selecting it, it's the second argument of the constructor. Most stack questions/answers I've seen tell me just to get rid of the FETCH but the issue is that I actually need it. I need the secondary object data and if I try to remove the FETCH the data is not retrieved or is retrieved later with terrible performance. Additionally, if I simply mark p.secondary as EAGER instead of LAZY in the relationship annotation the performance is atrocious as well.

I've also tried fetching through other means (Entity Graphs) to avoid using the FETCH keyword and it actually results in the same error message.

Is there some way to do a DTO of some kind where I can JOIN FETCH?


Solution

  • Conclusion

    After fiddling around with this for a long time I've come to the conclusion that you can not use a constructor for a class that is a persistence entity in the HQL and do this. There's something about the fetched entity being a constructor argument that makes it invisible to the parser as being part of the select.

    Solution

    While it seems like you can't directly create your desired object from the HQL query, you can get pretty close. I ended up changing my query to the following:

    SELECT p.id as id, p.secondary as secondary from Primary p LEFT JOIN p.secondary
    

    That along with using a ResultsTransformer like this:

    // if you're using an entity manager like me, you need to get a Hibernate session from it first
    org.hibernate.Session session = (org.hibernate.Session) entityManager.getDelegate();
    org.hibernate.Query query = session.createQuery(hql)
        .setParameter("userId", userId)
        .setResultTransformer(Transformers.aliasToBean(Primary.class))
    ;
    
    List<Primary> results = (List<Primary>) query.list();
    

    Gets you your typed object of choice with joined/fetched data.

    2 important points:

    1. You can see I'm no longer using FETCH in my query. It seems like it's not needed for me anymore as the data is fetched anyway. I think this has to do with the results no longer being placed in a persistence entity class and therefore there are no LAZY annotations making HQL think the data doesn't need to be fetched in the first place so it's just there.

    2. You need to as every property to have the exact name of the class member you want that piece of data to be placed in even if the property name you're selecting is already the same minus the 'p.' part Look at my example query again and you'll see that I want to put p.id in the id member of the Primary class, but even though the property name is already id after the . I still need to alias it using as.