0

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);
Noah Ternullo
  • 677
  • 6
  • 16

1 Answers1

1

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.

ipolevoy
  • 5,432
  • 2
  • 31
  • 46
  • Thanks for you fast reply! The member children.cachedParents.size is something I can see in the debugger, so I can't explain why it is that I have it and you do not. For whatever reason, I found that if I invert the search, that is do a query that joins the two tables, and then pull out ParentRequest with an include(Child.class) I get much better performance. Querying the way I had above took about 25-35 seconds to return 500 points. Querying the way I mention in this comment allowed me to return 500 points in 12-15 seconds or less. I'll post my psudocode below. – Noah Ternullo Aug 25 '16 at 17:51
  • I'm glad to see that the framework is working as expected (I'm sure that 99 times out of 100 that's the case). I'm wondering what I could be doing differently. – Noah Ternullo Aug 25 '16 at 18:05
  • Enable logging: http://javalite.io/logging and see what queries are generated in your case. After that, you can run these by hand in DB console, run explain and figure out why some are faster and others are slower. ActiveJDBC is just a tool - it gives you options:) – ipolevoy Aug 26 '16 at 03:19
  • 1
    Just saw your edit. Your query is probably more efficient than the include. First, the include will find the immediate objects, and then related. In your case you can grab all you need only for `List `, so you can get away with only one query. Also, your query collects values from two tables, which might not be the best thing: http://javalite.github.io/activejdbc/snapshot/org/javalite/activejdbc/Model.html See docs for `findBySQL` : Ensure that the query returns all columns associated with this model ... – ipolevoy Aug 26 '16 at 03:25