2010-07-31

Query Extensions in Action

Querydsl is all about providing the best type-safe querying tool for Java. To be the best fit for most of the situations, Querydsl provides lots of customization possibilities.

Query types can be extended to fit custom querying needs, custom method templates can be added to generated types and static delegate methods can be used as well.

Until recently static delegate methods could only be used for entity types. Since version 1.8.3 they can be used for built-in types as well.

This feature was requested by Luis Fernando Planella Gonzalez from the Cyclos project and they immediately took it into use.

Here is the declaration of their delegate methods :

/**
 * Contains extensions for common query classes
 * @author luis
 */
public class QueryExtensions {

    /**
     * Adds a period filter
     */
    @QueryDelegate(Date.class)
    public static EBoolean period(PDate<Date> expr, IDatePeriod period) {
        BooleanBuilder bb = new BooleanBuilder();
        Date begin = period == null ? null : period.getBegin();
        if (begin != null) {
            bb.and(expr.goe(begin));
        }
        Date end = period == null ? null : period.getEnd();
        if (end != null) {
            bb.and(expr.loe(end));
        }
        return bb;
    }

    /**
     * Adds a date period filter on a timestamp expression
     */
    @QueryDelegate(Timestamp.class)
    public static EBoolean period(PDateTime<Timestamp> expr, IDatePeriod period) {
        BooleanBuilder bb = new BooleanBuilder();
        Date begin = period == null ? null : period.getBegin();
        if (begin != null) {
            bb.and(expr.goe(DateHelper.toTime(begin)));
        }
        Date end = period.getEnd();
        if (end != null) {
            bb.and(expr.lt(DateHelper.toTime(DateHelper.truncateNextDay(end))));
        }
        return bb;
    }

    /**
     * Adds a timestamp period filter on a timestamp expression
     */
    @QueryDelegate(Timestamp.class)
    public static EBoolean period(PDateTime<Timestamp> expr, ITimestampPeriod period) {
        BooleanBuilder bb = new BooleanBuilder();
        Timestamp begin = period == null ? null : period.getBegin();
        if (begin != null) {
            bb.and(expr.goe(begin));
        }
        Timestamp end = period.getEnd();
        if (end != null) {
            bb.and(expr.loe(end));
        }
        return bb;
    }
}

All of the shown delegate methods are factory methods for "date in period" style expressions. The types for date and period vary in the methods.

The semantics of QueryDelegate annotations are simple. The @QueryDelegate.value determines the real type for the expression type to be extended and the first argument of the delegate method is used for the expression type instance itself. The rest of the arguments are used for the method in the extended query type.

In this example extensions for the types java.sql.Date and java.sql.Timestamp are declared. The respective builtin types in Querydsl are PDate and PDateTime. Two classes are created to extend the signature for PDate and PDateTime for Date and Timestamp usage :

public class QDate extends PDate {

    private static final long serialVersionUID = 494982445;

    public QDate(PEntity entity) {
        super(entity.getType(),entity.getMetadata());
    }

    public QDate(PathMetadata metadata) {
        super(java.sql.Date.class, metadata);
    }

    public EBoolean period(IDatePeriod period) {
        return QueryExtensions.period(this, period);
    }

}

public class QTimestamp extends PDateTime {

    private static final long serialVersionUID = 789961463;

    public QTimestamp(PEntity entity) {
        super(entity.getType(),entity.getMetadata());
    }

    public QTimestamp(PathMetadata metadata) {
        super(java.sql.Timestamp.class, metadata);
    }

    public EBoolean period(IDatePeriod period) {
        return QueryExtensions.period(this, period);
    }

    public EBoolean period(ITimestampPeriod period) {
        return QueryExtensions.period(this, period);
    }

}

With these generated types all of the property paths in the entity query types of type java.sql.Date and java.sql.Timestamp will use QDate and QTimestamp.

Now the extensions are ready to use in your queries. In Cyclos 4 the presented extensions are used in many places, for example the CardServiceBean class :

private List<CardVO> doSearch(Class<CardVO> resultType, CardQuery params) {
        QCard c = QCard.card;
        DBQuery query = from(c);

        if (params.getCardNumber() != null) {
            query.where(c.cardNumber.eq(params.getCardNumber()));
        }

        if (params.getCardType() != null) {
            query.where(c.cardType().id.eq(params.getCardType().getId()));
        }

        if (params.getExpirationDate() != null) {
            query.where(c.expirationDate.period(params.getExpirationDate()));
        }

        if (params.getCreationDate() != null) {
            query.where(c.creationDate.period(params.getCreationDate()));
        }

        if (params.getActivationDate() != null) {
            query.where(c.activationDate.period(params.getActivationDate()));
        }

        if (params.getGroups() != null) {
            Set groupsIds = EntityVO.getIds(params.getGroups());
            query.where(c.owner().group().id.in(groupsIds));
        }

        if (params.getOwner() != null) {
            query.where(c.owner().id.eq(params.getOwner().getId()));
        }

        if (params.getStatus() != null && !params.getStatus().isEmpty()) {
            query.where(c.status.in(params.getStatus()));
        }

        return query.run(resultType, params, c);
    }

The properties creationDate and activationDate are of type java.sql.Timestamp and expirationDate is of type java.sql.Date.

Conclusion


When you find yourself using lots of static helper methods to create your Querydsl queries, then consider using annotated delegate methods instead. They will make your queries more compact and are also easier to write as code autocomplete can be used.

3 comments:

  1. The query extensions with static delegates are really clever Querydsl feature, beyond my expectations on the original request.
    I wish standard java could have such an extension mechanism, so all static helpers / utils (commons lang and so many others) would be used as instance methods on types themselves.
    Congrats again, Timo and the Querydsl team!

    ReplyDelete
  2. I am new to QueryDSL and trying to figure out the extensions we can make on querydsl. I am confused where above mentioned extension might be helpful. Can you give me any scenario to use this extension?

    ReplyDelete
    Replies
    1. The blog post shows one example, but in general it is useful if you to construct custom expressions via fluent calls. Always when you end up creating static helper methods for Querydsl expression creation, it might be more intuitive to use them via extension methods.

      Delete