Eager-load the whole object-graph at runtime in Hi

2019-06-21 18:15发布

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.

2条回答
女痞
2楼-- · 2019-06-21 18:33

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.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-06-21 18:42

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

查看更多
登录 后发表回答