Creating Specification<> instances for Spring Data JPA with Spring MVC

One common requirement in web applications is to be able to filter a given set of data through request parameters. One way to do this is fetch everything from the server and filter it on the client. Me, i’m more of a server guy and i tend to do this inside a database.

Many projects of mine are developed with JPA based beans for the data model and recently i’ve come to really love Spring Data JPA. With the plain declaration of an interface extending a repository interface, you get everything you need to query your domain. Through in a second interface and you can execute JPA based criteria queries:

import entities.Projekt;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
public interface ProjektRepository extends JpaRepository<Projekt, Long>, JpaSpecificationExecutor {    
}

My goal: Create controller methods for Spring MVC like so

@RequestMapping(value = "", method = GET)
public Page<Projekt> getProjekte(
    final @RequestParam(defaultValue = "0", required = false) int page,
    final @RequestParam(defaultValue = "10", required = false) int pageSize,	    
    final @RequestParam(required = false, defaultValue = "lieferjahr") String orderBy,
    final @RequestParam(required = false, defaultValue = "ASC") Direction orderByDirection,
    final @FilterDefinition(paths = FilteredPathsOnProjekt.class) Specification<Projekt> filter	    
) {
    return this.projektRepository.findAll(filter, new PageRequest(page, pageSize, new Sort(orderByDirection, orderBy)));
}

where the filter parameter is of type Specification and where i can define which fields are to be filtered in which way in a type-safe way that is checked on compile time like so:

public static class FilteredPathsOnProjekt<Projekt> extends FilteredPaths<Projekt> {
    public FilteredPathsOnProjekt() {	    
        filter(Projekt_.lieferjahr)
            .with((cb, p, value) -> cb.like(cb.function("to_char", String.class, p, cb.literal("YYYY")), value + "%" ))
        .filter(Projekt_.angelegtAm)
            .with((cb, p, value) -> cb.like(cb.function("to_char", String.class, p, cb.literal("DD.MM.YYYY")), "%" + value + "%"))
        .filter(Projekt_.anwender, Anwender_.name)
            .with((cb, p, value) -> cb.like(cb.lower((Path<String>)p), "%" + value.toLowerCase() + "%"))
        .filter(Projekt_.marktteilnehmer, Marktteilnehmer_.name)
            .with((cb, p, value) -> cb.like(cb.lower((Path<String>)p), "%" + value.toLowerCase() + "%"))
        .filter(Projekt_.versorgungsart)
            .with((cb, p, value) -> cb.like(cb.lower((Path<String>)p), "%" + value.toLowerCase() + "%"));		
    }
}

I found this very inspiring post An alternative API for filtering data with Spring MVC & Spring Data JPA by Tomasz and here again, i big thank you.

My solution is similar, but uses the JPA Metamodel API that generates a metamodel from the annotated entity classes. In the above example, the entity is Projekt, the metamodel Projekt_ that lists all attributes that are mapped from the database.

Let’s start with the controller. Parameter filter is of type Specification and is annotated with @FilterDefinition:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface FilterDefinition {
    Class<? extends FilteredPaths> paths();
 
    Class<? extends FilterSpecification> implementation() default FilterSpecification.class;
}

I need to wrap the paths i want to filter with a class because only are small list of types are allowed in annotations. The class looks like this:

import java.util.List;
import java.util.Map;
import javax.persistence.metamodel.SingularAttribute;
 
public abstract class FilteredPaths<T> {
    protected final FilteredPathsBuilder<T> builder = new FilteredPathsBuilder<>();
 
    public final Map<List<SingularAttribute<?, ?>>, PathOperation<T>> getValue() {
        return this.builder.getFilterablePaths();
    }
 
    protected FilteredPathsBuilder<T> filter(SingularAttribute<?, ?>... path) {
        return builder.filter(path);
    }
}

The core of this is the getValue method. It returns a map of lists of SingularAttributes (a list of them to do queries on sub elements) and a PathOperation. PathOperation is a functional interface:

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
 
@FunctionalInterface
public interface PathOperation<T> {
    public Predicate buildPredicate(CriteriaBuilder cb, Path<?> path, String value);
}

that is used by my FilterSpecification to build the predicate:

import java.util.Map;
import java.util.function.Function;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
 
import static java.util.stream.Collectors.reducing;
 
public class FilterSpecification<T> implements Specification<T> {
 
    protected final Map<PathOperation, PathAndValue> filteredPaths;
 
    public FilterSpecification(Map<PathOperation, PathAndValue> filteredPaths) {
        this.filteredPaths = filteredPaths;
    }
 
    @Override
    public final Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        this.customizeQuery(root, query);
        return this.filteredPaths.entrySet().stream()
            .map(entry -> entry.getKey().buildPredicate(cb, entry.getValue().getPath(root), entry.getValue().getValue()))
            .collect(
                reducing(cb.conjunction(), Function.<Predicate>identity(), (p1, p2) -> cb.and(p1, p2))
            );
    }
 
    protected void customizeQuery(Root<T> root, final CriteriaQuery<?> query) {
    }
}

As you see there’s one big difference to Tomasz solution: I don’t support OR’ing the filters, i’ve only support AND. I like the way i can use the Java 8 stream api and the reduction method to add them together very much.

How get’s this class instantiated? Here come’s the additional Spring MVC argument resolver:

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.metamodel.SingularAttribute;
import org.springframework.core.MethodParameter;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
 
public class FilteringSpecificationArgumentResolver implements HandlerMethodArgumentResolver {
 
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(FilterDefinition.class) && parameter.getParameterType() == Specification.class;
    }
 
    @Override
    public Object resolveArgument(
        MethodParameter parameter,
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        WebDataBinderFactory binderFactory
    ) throws Exception {
        final FilterDefinition filterDefinition = parameter.getParameterAnnotation(FilterDefinition.class);	
        final FilteredPaths<?> paths = filterDefinition.paths().newInstance();
 
        final Map<PathOperation<?>, PathAndValue> filterValues = new HashMap<>();
        paths.getValue().entrySet().forEach(entry -> {
            final String param = entry.getKey().stream().map(SingularAttribute::getName).collect(Collectors.joining("."));
            final String paramValue = Optional.ofNullable(webRequest.getParameter(param)).orElse("");
            if (!paramValue.isEmpty()) {
                filterValues.put(entry.getValue(), new PathAndValue(entry.getKey(), paramValue));
            }
        });
 
        return filterDefinition.implementation().getConstructor(Map.class).newInstance(filterValues);
    }
}

It supports controller arguments of type Specification annotated with @FilterDefinition. To resolver the argument it gets the annotation and then instantiates the class that holds the lists of paths that should be filtered. The lists actually contains the path and the filter operation. It’s iterated and each list of singular attributes is joined together with a “.”, so one attribute becomes “versorgungsart” and a nested becomes “marktteilnehmer.name” for example. It then checks the web requests if there are request params with those names. If so, the filter operation is added together with the path and the value for that path (from the request) to a new map (the operations are the key). This map than is passed to the implementation of the Specification.

What’s nice here: I don’t need to check if the attributes that are requested from the outside exists. As i start with the attributes of the entity from the start, there’s no chance i try to access one that doesn’t exists.

What’s missing is the pair type PathAndValue

import java.util.List;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.SingularAttribute;
 
public class PathAndValue {
 
    private final List<SingularAttribute<?, ?>> path;
    private final String value;
 
    public PathAndValue(List<SingularAttribute<?, ?>> path, String value) {
        this.path = path;
        this.value = value;
    }
 
    public String getValue() {
        return value;
    }
 
    public Path<?> getPath(final Root<?> root) {
        Path<?> rv = root;
        for (SingularAttribute attribute : path) {
            rv = rv.get(attribute);
        }
        return rv;
    }
}

that not only holds both the path (as a list of attributes) to be filtered and value to be filtered with, but also generates a complete JPA criteria path that is given to the PathOperation.

And last but not least is the little FilteredPathsBuilder that allows me to write the filter definition in a readable way and not obstructed with map and list operations as shown in FilteredPathsOnProjekt above:

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.metamodel.SingularAttribute;
 
public class FilteredPathsBuilder<T> {
    private final Map<List<SingularAttribute<?, ?>>, PathOperation<T>> filterablePaths = new HashMap<>();
 
    private List<SingularAttribute<?, ?>> lastPath;
    private PathOperation<T> lastOp;
 
    public FilteredPathsBuilder<T> filter(SingularAttribute<?, ?>... path) {
        if(lastPath != null && lastOp != null) {
            filterablePaths.put(lastPath, lastOp);	    
        }
        this.lastPath = Collections.unmodifiableList(Arrays.asList(path));
        this.lastOp = null;
        return this;
    }
 
    public FilteredPathsBuilder<T> with(PathOperation<T> op) {
        this.lastOp = op;
        return this;
    }
 
    public Map<List<SingularAttribute<?, ?>>, PathOperation<T>> getFilterablePaths() {
        if(lastPath != null && lastOp != null) {
            filterablePaths.put(lastPath, lastOp);	    
        }
        this.lastPath = null;
        this.lastOp = null;
        return Collections.unmodifiableMap(filterablePaths);
    }    
}

As you can see in FilteredPathsOnProjekt above i have now a little DSL that i use to define which attribute of my entity should be filtered. Writing down the PathOperation as lambdas and using the generated MetaModel to specify the attributes i have not only filters that are checked on compile time but also have the the attributes definition only in one place. All solutions that require writing down lists of request parameters or fiddling with map parameters are too error prone and in the end, way too much work to write.

The above solution works fine with Spring 4.0.x and 4.1.x and certainly with Spring Boot. I’ve got tests for all of the code above and should anyone find this solution useful, i’m gonna prepare a standalone project with it.

Let me know, what you think.

| Comments (13) »

24-Sep-14


Mockito#validateMockitoUsage

Hi, long time no see… I really had a lot of work to do, been doing some freelance work in the nighttime and then there’s certainly my family. And apart from that, my current setup for this years projects is working quite well, thanks to Spring Boot and NetBeans for example.

Recently i had some failing tests. I used to Mockito to verify method calls and though i was sure the tested code was correct, the test kept failing.

What was wrong? The verification of calls run before the next mock. So what was failing was the previous test and I was too stupid to see the navigable exception at first (and second…).

To avoid this behavior and make the test fail that actually fails verification, add the following to your test class:

@After
public void validate() {
    Mockito.validateMockitoUsage();
}

or run your test with the MockitoJUnitRunner.

Read some about this behavior in the Mockito API: Mockito.validateMockitoUsage.

| Comments (0) »

12-Sep-14


How to screw up iMessages and FaceTime on your iDevice

Recently i managed to screw up iMessages and FaceTime on my phone. Well, not exactly i screwed up, but Apple did.

Some months back i changed my Apple ID but kept the old email address as verified other address in my Apple account. I changed all iCloud settings (Sharing etc.) on all Apple devices because they wouldn’t authorize. iMessages and FaceTime didn’t complain at this time, neither on iDevices nor on a desktop.

Some days ago i decided to change my password and then things went bad. iCloud settings complained as expected but so did iMessages and FaceTime.

The problem? Both programs kept the old Apple ID in their settings and as soon as i hit the Apple-Id button to change it, the modal dialogue that the password was wrong kept popping up. This was the case in both iMessage and FaceTime.

The solution? Hitting the dialogue and the settings button on my iPhone like a madman… I didn’t expect this to work and was ready to reset my phone but sometime my timing was right.

Well. Pretty bad UX fail on Apples side.

So, if you want to change your Apple ID, remember to change this in iMessages and FaceTime immediately if this programs don’t complain right away.

| Comments (0) »

30-May-14


JaCoCo, Maven and NetBeans 8 integration

I was looking for a nice solution to measure the code coverage in my Spring Boot biking project.

It should support Java 8, Maven and for added bonus, my IDE.

I ended up using JaCoCo respectively the Maven plugin.

If you expect a lengthier post, i must disappoint you. All that was need to turn this:

before

into this

after

and also having a nice report like this (right click in NetBeans 8 on the project and choose “Code Coverage > Show Report…”)

report

was the following plugin declaration in maven:

<plugin>
	<groupId>org.jacoco</groupId>
	<artifactId>jacoco-maven-plugin</artifactId>
	<version>0.7.1.201405082137</version>
	<configuration>
	    <excludes>
		<!-- Application starter -->
		<exclude>ac/simons/biking2/Application.class</exclude>
		<!-- Configuration -->
		<exclude>ac/simons/biking2/config/*</exclude>			
	    </excludes>
	</configuration>
	<executions>
	    <execution>
		<id>pre-unit-test</id>
		<goals>
		    <goal>prepare-agent</goal>
		</goals>
	    </execution>
	    <execution>
		<id>post-unit-test</id>
		<phase>test</phase>
		<goals>
		    <goal>report</goal>		
		    <goal>check</goal>
		</goals>
		<configuration>
		    <rules>
			<!--  implmentation is needed only for Maven 2  -->
			<rule implementation="org.jacoco.maven.RuleConfiguration">
			    <element>BUNDLE</element>
			    <limits>
				<limit implementation="org.jacoco.report.check.Limit">
				    <counter>INSTRUCTION</counter>
				    <value>COVEREDRATIO</value>
				    <minimum>0.95</minimum>			
				</limit>
				<!--  implmentation is needed only for Maven 2  -->
				<limit implementation="org.jacoco.report.check.Limit">
				    <counter>COMPLEXITY</counter>
				    <value>COVEREDRATIO</value>
				    <minimum>0.75</minimum>
				</limit>
			    </limits>
			</rule>
		    </rules>
		</configuration>
	    </execution>
	</executions>
</plugin>

That’s it. NetBeans 8 recognizes JaCoCo immediately and everything works (except for my project not reaching my self set limits). No additional installs, no weird maven problems. Awesome.

Also i had no problems with JaCoCo and Java 8 features of any kind.

| Comments (3) »

22-May-14


Java 8: Grouping stuff

I needed a function to sum (and therefor group) the values of a map of objects to Integers. My first solution was something like

As you can see, i use the collect method with a custom supplier, accumulator and combiner. The supplier prepares a new map, the accumulator takes the map and an entry and then uses Map#merge to sum the values.

The combiner than merges all created maps with the same logic.

There’s a nicer solution:

Use Collectors.html#groupingBy. This static helper method takes a classifier and a downstream. The classifier acts the same way as a Group-By clause in SQL, the downstream performs the actual reduction (in this case, a sum).

Neat.

Anyway, i have the slight feeling, i’m recreating a SQL syntax or at least using the idea.

| Comments (0) »

06-May-14