5

Please read on before saying anything along the lines of "specify the fetch type in the query". That's not what I'm after.

I'm looking for a way to eager-load a complete object-graph (the object + all its children and all their children and so on).

I do not want to enumerate all properties that are to be loaded. I don't know them until runtime.

N+1 queries aren't a problem. But at the end of this magical operation, I don't want a single proxy or lazy collection left in my graph.

It should be possible to write a bit of code that reflectively and recursively looks at all properties. But collections make this awkward and complex.

Some people have recommended Dozer for this kind of thing, but that seems a bit excessive, so I'd like to save that as a last resort.

Nim
  • 631
  • 6
  • 12

2 Answers2

0

A simple solution would be to specify lazy="false" for all collections (1:N and N:M) and associations (1:1).

That will load the entire graph into memory for every transaction. So for this to work properly, you should have at most one transaction or it will hurt performance really, really badly.

While this would do what you want, the cost might be too high. Note that you can use "fetch profiles" to select different strategies at runtime but Hibernate always gives you a copy to work with when you ask for objects, so it has to copy the graph every time.

For me, this sounds like Hibernate is simply the wrong tool for the task. Mapping entities with Hibernate is comfortable but at the cost of Hibernate leaking into your model and business code. If the constraints imposed by Hibernate don't fit your bill, then you should look elsewhere.

Maybe you can live with Record types instead of fully fleshed out Java beans. If so, then you could look at jOOQ or frameworks that implement the "active record" pattern.

If you need beans and you're not restricted to a certain type of database, try an OO database like db4o.

Lastly, why do you use SQL at all? If you always need the whole object graph, why not simply serialize it to a file and load it at startup? Or use a memory-resident database.

Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • That's not an option. The tree is, well, rather complex, and there is only one instance where I want to resolve the entire thing. Hibernate fits 99% of the bill, but there are a few edge cases where it's desirable to load and cache a detached copy of a complex graph. If no one else has bothered with this, then I'll just have to go the reflective route and post the code on here when I'm done. – Nim Jan 07 '13 at 13:04
  • You could try to extract the runtime configuration from Hibernate because that already contains all the information you need to fetch all references. When I tried this with Hibernate 3.5, I quickly ran into private classes/fields which made this too hard but maybe the API was opened for 4.x. – Aaron Digulla Jan 07 '13 at 13:12
  • @Nim : Were you able to find a solution for the problem? I am also facing this issue. – Yadu Krishnan Jun 23 '14 at 08:53
0

I once needed something similar to that, and I had to use Reflection to solve it. In my case, I was using hql to retrieve the records. Also, this is an approach I created for eager loading records that are defined as lazy loading, so you may want to adapt the first method to not look for a FetchType.LAZY property and always fetch it regardless.

One of the methods I built will "prepare" the lazy fetching. Basically two approaches: using "left join fetch" on the hql for @ManyToOne, and hibernate.initialize() for @OneToMany and @ManyToMany.

So, this first method returns a string with the required "left john fetch"es for the hql, and also builds a list of nToMany fields that have to be called by Hibernate.initialize() after the query is executed.

private String buildLazyFetch(Class<? extends GenericEntity> entityClass, List<String> nToManyFields) {
    String lazyFetches = new String();
    lazyFetches += " fetch all properties ";
    // iterate through all fields looking for lazy loaded relationships
    for (Field f : entityClass.getDeclaredFields()) {
        ManyToOne manyToOne = f.getAnnotation(ManyToOne.class);
        if (manyToOne != null) {
            if (manyToOne.fetch().equals(FetchType.LAZY)) {
                lazyFetches += " left join fetch t." + f.getName() + " ";
            }
        }
        OneToMany oneToMany = f.getAnnotation(OneToMany.class);
        if (oneToMany != null) {
            if (oneToMany.fetch().equals(FetchType.LAZY)) {
                nToManyFields.add(f.getName());
            }
        }
        ManyToMany manyToMany = f.getAnnotation(ManyToMany.class);
        if (manyToMany != null) {
            if (manyToMany.fetch().equals(FetchType.LAZY)) {
                nToManyFields.add(f.getName());
            }
        }
    }
    return lazyFetches;
}

and for after the hql is executed, call:

private void lazyFetchNToMany (List<String> nToManyFields, GenericEntity entity) {
    for (String field : nToManyFields) {
        try {
            Hibernate.initialize(BeanUtils.getProperty(entity, field));
        } catch (HibernateException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

I understand this is not exactly what you expected, but it might help you out in case you don't find your desired solution

beder
  • 1,086
  • 5
  • 10