JDO Typesafe Queries : Part 3 – Examples

In two previous blog posts, we introduced the idea of having a typesafe refactor friendly API for queries for JDO, and then described typical expression types necessary to achieve that. In this blog post we take that a step further, particularly now that DataNucleus SVN has a proof of concept query API to fulfil this requirement.

In these examples we have a class Product. Consequently we have a “query” class autogenerated – QProduct. Here is the QProduct class

public class QProduct extends PersistableExpressionImpl implements PersistableExpression
{
    public static QProduct candidate() {...} // candidate "this"
    public static QProduct candidate(String name) {...}
    public static QProduct parameter(String name) {...}
    public static QProduct variable(String name) {...}
    public NumericExpression id;
    public StringExpression name;
    public NumericExpression value;
    ...
}

So, as you can see we have access to the persistable fields/properties of Product by way of public fields in its query class. Now on to some examples

Filter only
Here we select all Products that have a value less than 40.0

TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0));
List results = tq.executeList();

Filter + Order
Here we select all Products that have a value less than 40.0 and ordering by their name


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0)).orderBy(cand.name.asc());
List results = tq.executeList();

Filter + Order + Result
Here we select the product name and product value for all Products that have a value less than 40.0 , ordering by their name


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0)).orderBy(cand.name.asc());
List results = tq.executeResultList(true, cand.name, cand.value);

Filter with Methods
Here we select all Products that have a value less than 40.0 and name starting with “Wal”, and ordering by their name


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0).and(cand.name.startsWith("Wal")));
List results = tq.executeList();

Filter, using Parameters
Here we select all Products that have a value less than 40.0, using a parameter


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate()
tq.filter(cand.value.lt(tq.doubleParameter("param1"))).setParameter("param1", 40.0);
List results = tq.executeList();

Order + Range
Here we select Products, ordering by the name and restrict to the first two


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.orderBy(cand.name.asc()).range(0,2);
List results = tq.executeList();

Filter with Variable
Here we select Inventory (which has a collection of Products) that contain a Product with a particular name


TypesafeQuery tq = pm.newTypesafeQuery(Inventory.class);
QInventory cand = QInventory.candidate();
QProduct var = QProduct.variable("var");
tq.filter(cand.products.contains(var).and(var.name.startsWith("Wal")));
List results = tq.executeList();

Filter with Subquery
Here we select Products with a value less than the average value of any Product


TypesafeQuery tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
TypesafeSubquery tqsub = tq.subquery(Product.class, "p");
QProduct candsub = QProduct.candidate("p");
tq.filter(cand.value.lt(tqsub.select(candsub.value.avg())));
List results = tq.executeList();

As you can see from the above queries we have no hard-coded class or field names, providing better refactorability. We can obviously make use of all JDOQL supported methods in the above queries, and define parameters and variables just like in the current JDOQL API.

It should be noted that this JDOQL facility is much more elegant and requires less code than the equivalent queries using JPA “Criteria”.

This entry was posted in JDO, JDOQL, JPA, LINQ, Typesafe. Bookmark the permalink.

15 Responses to JDO Typesafe Queries : Part 3 – Examples

  1. Looks quite nice. The name parameter for the subqueries feels strange though. The instance itself should be enough.

    Like

  2. andy says:

    If we don't pass in the “alias” for the subquery, how do we get the candidate of the subquery to be able to refer to it in filter/result of the subquery ? i.e candsub.value. Obviously there is TypesafeSubquery.candidate() but then the user has to cast it. If you have some proposal then I'd be interested

    Like

  3. andy says:

    You mean tq.subquery(QProduct.candidate(“p”)); presumably ?

    Like

  4. Ok, I think I understand now. The subquery and subquery candidate are a pair and are combined via the common alias.

    If the typesafe query and subquery would contain the candidate q-type as a generic parameter it could also be accessed as query.candidate() / subquery.candidate.

    Like

  5. Btw, how do you handle many-to-one references in this API?

    e.g.
    class Product { Shop shop;},
    class Shop { User owner;}

    would it be

    QProduct.candidate().shop.user

    or

    QProduct.candidate().shop().user()

    Querydsl supports both modes,

    Like

  6. andy says:

    You mean chained relations presumably, since I don't care what type the relation is (1-1, 1-N, N-1, M-N). If a persistable class has a field (or property) then the “Q” class has a *field* of type Q{otherType}. So QProduct.candidate().shop.owner

    Not convinced that we need to allow accessor methods on Q classes. Reason for doing such a thing? (obviously in the implementation of the Q class it can just return the field). Allowing accessor methods you would then have to look out for naming collisions … what if the user has a property called “count” and there is also a method on XXXExpression called count() ? Better just to stick to field syntax IMHO

    Like

  7. Yes, I mean chained relations. How do you treat recursions then?

    e.g
    class User { User superior; }

    -> QUser.candidate().superior.superior;

    Querydsl supports method access for lazy initialization and annotated properties (QueryInit) for selective eager initialization.

    Do you have something similar?

    Like

  8. andy says:

    DN supports relations (recursive or otherwise), just as basic JDOQL does. In JDOQL you could do this.superior.superior, so you can here too. Infinite relations (only limited by memory) 😉

    Annotated properties are translated into a *field* in the Q class. This is for the reason I mentioned above; you have to avoid method name clashes, and cannot restrict the field/property names a user chooses, but in the same way need to allow min, max, avg etc hence you create fields in the Q class.

    Eager and lazy is defined by fetch groups in JDO, not by the query language.

    Like

  9. I mean doesn't it go into an infinite loop with a recursive property (e.g. User.superior) in the initialization?

    class QUser {

    public final QUser superior = new QUser(…);

    }

    How do you avoid this?

    Like

  10. andy says:

    You can get recursion if not implemented right, obviously, but that is an *implementation* issue, not a specification issue. In the same way we could define a standard way of limiting such things in the specification … and your proposal is ?
    Has to go in the query class generator. Detecting simple recursion (A.a.a.a.a) is easy. Detecting complex recursion (A.a.b.a.b.a.b) is less easy.

    Like

  11. Querydsl has two solutions.

    a) annotate the field to be initialized in deep form

    e.g.

    @QueryInit(“user”)
    private Shop shop;

    this way QProduct.candidate().shop.user can be used

    b) accessors for entity fields

    e.g.

    QProduct.candidate().shop().user()

    Most of our users prefer the second option.

    Like

  12. andy says:

    a). With the first one, I'd expect to pass in a value of recursion depth, to be consistent with JDO fetch groups. So if a user annotates a field with @QueryDepth(3) then it initialises 3 levels down from the candidate of that type.

    b). As above, we can't allow property accessors – naming clashes with the methods that are needed for JDOQL. If I have a field “avg” then I can't use that route. However I could call the property accessors getXxx() maybe, but definitely not something that will clash. And if allowing property accessors then I'd expect to go that way totally or via fields totally – don't know why we need both (users get confused enough).

    Like

  13. andy says:

    To be precise with property name clashes, the user cannot make use of eq, ne, count, countDistinct, jdoObjectId, jdoVersion (as the current interface is). So maybe not a big restriction … but still needs to be listed as a requirement. If going the property route, then don't provide fields at all.

    Like

  14. andy says:

    DN SVN now supports both modes of access.

    Default is 'property', as in “cand.field()”. User has responsibility for avoiding name clashes with API methods.

    Also supports field access by compiler argument, and this works with recursion, initialising down to a level of 5 (arbitrary for now, but could be made compiler argument)

    Like

  15. Great. Another thing that came to my mind is to support casts on the Q-class level.

    e.g.

    QProduct.candidate().shop.as(QSpecialShop.class).specialProperty

    Like

Leave a comment