13

Say I have CustomerQueryInfo bean with the following properties:

  • String firstName
  • String lastName
  • StatusEnum status

I want to perform a "QueryDSL" search using this an object of this type that will return a list of customers List<Customer>.

If one of the fields of CustomerQueryInfo is null, I don't want to use it in the search. Thus a CustomerQueryInfo object with all three fields set to null will return all customers.

I am looking for best practices to perform such a search with QueryDSL.

Is something like this OK:

private BooleanExpression isFirstNameLike(String firstName){
    if(firstName==null)
        return true BooleanExpression somehow;
    return QCustomer.customer.firstName.like(firstName);
}

private BooleanExpression isStatutEq(StatusEnum status){
    if(status==null)
        return true BooleanExpression somehow;
    return QCustomer.customer.status.eq(status);
}

then:

return query.from(customer).where(isFirstNameLike(customerQueryInfo.getFirstName).and(isLastNameLike(customerQueryInfo.getLastName).and(isStatusEq(customerQueryInfo.getStatus))).list;
  1. How do I return a BooleanExpression that evaluates to true?
  2. If the above approach is not advisable, then what is the recommended best practice?
NikolaS
  • 503
  • 2
  • 8
  • 20
balteo
  • 23,602
  • 63
  • 219
  • 412

5 Answers5

41

How do I return a BooleanExpression that evaluates to true?

BooleanExpression alwaysTrue = Expressions.asBoolean(true).isTrue();
Fabian Barney
  • 14,219
  • 5
  • 40
  • 60
  • 3
    Seemed like a great solution, but in my case I ended up with `java.lang.UnsupportedOperationException: Illegal operation true = true at com.querydsl.mongodb.MongodbSerializer.visit(MongodbSerializer.java:259` – GaspardP Feb 11 '18 at 20:58
  • Works here with Oracle. Seems to be an issue related to the combination with MongoDB. – Fabian Barney Feb 12 '18 at 16:38
  • When you find a working solution for MongoDB then please let us know. Maybe something that is converted to `1=1` works?! We just need anything tautological ... – Fabian Barney Feb 12 '18 at 16:47
  • 3
    Actually, the `BooleanBuilder` (see Lam Le's answer) ended up working perfectly. I did not realize at first that the `BooleanBuilder` is a `Predicate` so you can pass it directly to the `where` clause once you're done building it. – GaspardP Feb 13 '18 at 18:50
  • 1
    If you're already there, why not use `Expressions.TRUE`? – OrangeDog Jun 30 '20 at 17:12
  • @OrangeDog Expressions.TRUE does not work solely as Predicate in where-clause. – Fabian Barney Jul 06 '20 at 14:45
  • 4
    @FabianBarney `Expressions.TRUE.isTrue()` does though – OrangeDog Jul 06 '20 at 14:56
  • @OrangeDog Does it have any advantages over the other statement except that it is more readable? Nevertheless I would like to adjust my answer to your statement if it's ok for you? – Fabian Barney Jul 06 '20 at 15:13
  • 1
    @FabianBarney it'll be slightly faster, skipping ~6 method calls. – OrangeDog Jul 06 '20 at 15:18
22

You can safely use null predicates like this

private BooleanExpression isFirstNameLike(String firstName){
    return firstName != null ? customer.firstName.like(firstName) : null;        
}

private BooleanExpression isStatusEq(StatusEnum status){
    return status != null ? customer.status.eq(status) : null;
}

And use the varargs aspect of where

query.from(customer)
     .where(
         isFirstNameLike(customerQueryInfo.getFirstName()),
         isLastNameLike(customerQueryInfo.getLastName()),
         isStatusEq(customerQueryInfo.getStatus()))
     .list(customer);
Welington Veiga
  • 151
  • 1
  • 7
Timo Westkämper
  • 21,824
  • 5
  • 78
  • 111
  • Not sure if related, but today I ran into a problem in QueryDSL 3.5.0 (with spring-data-jpa) where `isTrue()` returned everything correctly, but `eq(Boolean.TRUE)` returned nothing. However, `eq(Boolean.FALSE)` worked correctly. Is this a bug? – EpicPandaForce Dec 28 '15 at 07:54
  • 1
    Skipping nulls in where is documented. Is it possible to do the same for `BooleanExpression.and(Predicate)`? Currently null skipping is an implementation detail here and null behaviour is not documented while the parameter is annotated as @Nullable. – Fabian Barney Oct 10 '17 at 08:11
8

With java 8 and BooleanBuilder you can achieve elegant way like this:

Java 8 Optional & Lambda

public final class ProductQuery {
    public static BooleanExpression nameEqualTo(String name){
        return ofNullable(name).map(QProduct.product.name::eq).orElse(null);
    }
}

BooleanBuilder

BooleanBuilder where = new BooleanBuilder()
    .and(ProductQuery.nameEqualTo(name));
Lam Le
  • 809
  • 8
  • 14
6

I create a QueryDSLHelper class which has static methods which do the null check before adding the expression. Something like this:

public static void goe(BooleanBuilder builder, DateTimePath<Date> path, Date value) {
    if(date!=null) {
        builder.and(path.goe(value));
    }
}

public static void like(BooleanBuilder builder, StringPath path, String value) {
    if(value!=null) {
        builder.and(path.like(value));
    }
}

Now I can just statically import those methods and call them on one line:

        like(builder, book.isbn, isbn);

This is extremely useful and very clean/readable when implementing 'filter' or 'filterByExample' queries.

Although, the answer above from Timo is probably better solution.

prule
  • 2,536
  • 31
  • 32
1

You could do something like this:

private BooleanExpression isFirstNameLike(String firstName){
    final QCustomer customer = QCustomer.customer;
    return customer.firstName.isNull().or(customer.firstName.like(firstName));
}

private BooleanExpression isStatutEq(StatusEnum status){
    final QCustomer customer = QCustomer.customer;
    return customer.status.isNull().or(customer.status.eq(status));    
}

Or if you want to really match firstName and/or status then change the

.isNull().or(...) to isNotNull().and(...)
Unheilig
  • 16,196
  • 193
  • 68
  • 98