Search code examples
javapolymorphic-associationsactivejdbceager

ActiveJDBC eager loading of Polymorphic Parents


I am unable to eagerly load the polymorphic parent of a child class. The include statement seems to make no difference.

Child Class:

@BelongsToPolymorphic(
    parents = {ParentRequest.class},
    typeLabels  = {"parent_request"})
public class Child extends Model {
}

Parent Class:

public class ParentRequest extends Model {

}

Query that should eagerly return child + parent:

List<Child> children = Child.where("... limit 500").include(ParentRequest.class);

children.size(); //this gives me 500
children.cachedParents.size(); //this gives me 0;

Ultimately, I am trying to speed up the following operation:

for (Child child : children) {
     ParentRequest pr = child.parent();
     // lots of pr.getString("parent_field");
     ...
 }

I have benched these operations, and the above operation seems to take around 67 ms regardless of whether .include(ParentRequest.class) is used on the Child.where() method or not.

Any insight or help is greatly appreciated.

NOTE: I am aware the Child only has one parent. In the near future it will have several.

EDIT: Inverting the Query produced much faster results for some reasons. That is, rather than looking for Children and including ParentRequest, if I searched for ParentRequest and included Child the operation was much faster. Note that I specifically did a findBySql to join the child table to the parent_request table in my results. Below I've left in the specifics of the query.

List<ParentRequest> parents = ParentRequest.findBySQL("SELECT child.*, parent_requests.* " +
                "FROM child JOIN parent_requests ON child.parent_id=parent_requests.id WHERE " +
                "RAND()<=? AND (child.metersToA BETWEEN ? AND ?) " +
                        " AND (child.metersToB BETWEEN ? AND ?) limit ?",
                decimation_value,
                minDistanceToA, maxDistanceToA ,
                minDistanceToB, maxDistanceToB,
                MAX_POINTS).include(Child.class);

Solution

  • I wrote a simple test and enabled logging:

    Article article = Article.findById(1);
    article.add(Comment.create("author", "tjefferson", "content", "comment 1"));
    article.add(Comment.create("author", "tjefferson", "content", "comment 2"));
    LazyList<Comment> comments = Comment.where("author = ?", "tjefferson").include(Article.class);
    
    System.out.println(comments.size());// does loading of data, prints 2
    Article parent1 = comments.get(0).parent(Article.class); // does not generate DB query
    Article parent2 = comments.get(1).parent(Article.class); // does not generate DB query
    
    assert (parent1 == parent2); // true
    

    Execution of this code will log the following to a console:

    SELECT * FROM comments WHERE author = ?", with parameters: <tjefferson>, took: 1 milliseconds
    SELECT * FROM articles WHERE id IN (?)", with parameters: <1>, took: 1 milliseconds
    

    As you can see, there were only two queries to the database. Additionally, the line you mention:

    children.cachedParents.size(); //this gives me 0;
    

    will not compile, because a LazyList does not have the member cachedParents.

    If you loop through children in this case, and get a parent like:

    child.parent(Parent.class)
    

    , there will be no database queries to the database because objects are cached in memory.

    The framework is working as expected. The reason for your timing being the same with and without include() s the size of your dataset. 67 milliseconds is pretty fast, and the "bottleneck" is elswhere.

    What you need is to do to see a difference is to load a much larger data set.

    Additionally, keep in mind, that the more data you load, the more heap space you will allocate. Ultimately the include() method solves the N+1 problem (http://javalite.io/lazy_and_eager) , but it does not mean your application will be faster. You need to experiment, and decide if it is best to use it or not for your cases.

    Summary: If you use include() method, you will have fewer calls to the database, but will allocate more RAM. It is up to you to decide if it is faster for your app.