45

I have a generic Spring Data repository interface that extends QuerydslBinderCustomizer, allowing me to customize the query execution. I am trying to extend the basic equality testing built into the default repository implementation, so that I can perform other query operations using Spring Data REST. For example:

GET /api/persons?name=Joe%20Smith  // This works by default
GET /api/persons?nameEndsWith=Smith  // This requires custom parameter binding.

The problem I am having is that every alias of an entity path I create seems to override the preceding alias bindings.

@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable>
    extends PagingAndSortingRepository<T, ID>, QueryDslPredicateExecutor<T>, QuerydslBinderCustomizer { 

    @Override
    @SuppressWarnings("unchecked")
    default void customize(QuerydslBindings bindings, EntityPath entityPath){

        Class<T> model = entityPath.getType();
        Path<T> root = entityPath.getRoot();
        for (Field field: model.getDeclaredFields()){
            if (field.isSynthetic()) continue;
            Class<?> fieldType = field.getType();
            if (fieldType.isAssignableFrom(String.class)){
                // This binding works by itself, but not after the next one is added
                bindings.bind(Expressions.stringPath(root, field.getName()))
                        .as(field.getName()  + "EndsWith")
                        .first((path, value) -> {
                            return path.endsWith(value);
                        });
                // This binding overrides the previous one
                bindings.bind(Expressions.stringPath(root, field.getName()))
                        .as(field.getName()  + "StartsWith")
                        .first((path, value) -> {
                            return path.startsWith(value);
                        });
            }
        }
    }
}

Is it possible to create more than one alias for the same field? Can this be accomplished in a generic way?

woemler
  • 7,089
  • 7
  • 48
  • 67
  • 2
    Digging through the source it seems that the bindings are stored in a Map keyed by path: `private final Map> pathSpecs` which would explain why `bindings.bind(Expressions.stringPath(root, field.getName()))` overrides the previous binding. Don't know if there is another way to achieve this however. – Alan Hay Jan 16 '17 at 10:26
  • 1
    Check this other answer about a somewhat similar question: http://stackoverflow.com/a/43852346/5747715 Basically you need to create more fields (that are not persisted) to attach functionality to them. – Jorge C May 15 '17 at 15:49
  • 1
    Is there an easy way to do this now? Limitations like this makes it difficult to use querydsl – Charlie Aug 22 '17 at 03:21
  • 1
    Looks like you are trying to provide generic search api. If so, check rsql parser and rsql-jpa. – Adisesha Oct 27 '17 at 11:29
  • 2
    @Adi: What I eventually implemented is a lot like RSQL. Would be nice if Spring Data and QueryDSL was more flexible in this regard. – woemler Oct 27 '17 at 13:04
  • @woemler to turn the tables on this, looks like it could used some operators like `/search/users?emails.value=endsWith(@company.com)&emails.value=endsWith(@legacycompany.com)` there is a project providing those: https://gt-tech.bitbucket.io/spring-data-querydsl-value-operators/README.html – hakamairi Feb 25 '19 at 12:16

1 Answers1

1

You can create a transient property bound to QueryDSL this way:

@Transient
@QueryType(PropertyType.SIMPLE)
public String getNameEndsWith() {
    // Whatever code, even return null
}

If you are using the QueryDSL annotation processor, you will see the "nameEndsWith" in the metadata Qxxx class, so you can bind it like any persisted property, but without persisting it.

apetrelli
  • 718
  • 4
  • 18