Querydsl Value operator SDK’s example application provides a simplistic Employee Registry API system and illustrates the use of Spring Data Querydsl Value operators usage in a way that empowers this example API to offer rich Employee Search interface.
Full SDK documentation can be located here
Application is developed using Spring Boot 2.x with embedded Tomcat server. It utilizes an embedded MongoDB server for repository.
Resource model of this API can be found in org.bitbucket.gt_tech.spring.data.querydsl.value.operators.example.model package. Note the use of Document annotation on top level resource, Employee. This annotation though is optional for Spring Data MongoDB in case of a single-tenant/datastore API but is used by the Maven Querdsl API code generation to identify candidates for which Q-Classes (Q-Types) needs to be generated.
Project’s POM file has following plugin defined which generates the Q-Type classes before compilation phase.
<plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>${plugin.mongodb.mysema.apt.version}</version> <dependencies> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>4.1.4</version> </dependency> </dependencies> <executions> <execution> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <outputDirectory>${project.build.directory}/generated-sources/querydsl/java</outputDirectory> <!-- Works with Entity Annotation --> <!--<processor>com.querydsl.apt.morphia.MorphiaAnnotationProcessor</processor> --> <!-- Works with QueryEntity annotation --> <!--<processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor> --> <processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor> <options> <querydsl.logInfo>true</querydsl.logInfo> <querydsl.listAccessors>false</querydsl.listAccessors> <querydsl.useGetters>true</querydsl.useGetters> <querydsl.unknownAsEmbeddable>true</querydsl.unknownAsEmbeddable> </options> </configuration> </execution> </executions> </plugin>
POM also pulls in spring-boot-starter-data-mongodb artifact to provide run-time bootstrapping for satisfying the provided scoped dependencies of core SDK library, this was anyway required to make Spring data work with MongoDB.
Application loads test data for handful of employees in embedded MongoDB at the time of startup. Test data can be found here. Data load is handled by EmployeeDataBootstrap
EmployeeRepository is the main repository interface which as per guidance in core SDK library’s documentation extends QuerydslPredicateExecutor and QuerydslBinderCustomizer.
To keep example API code small, this repository is also annotated with Spring MVC specific annotations so it also acts as a RestController by utilizing Spring Data Rest framework.
Following two methods in repository provide the search capabilities: * Generic search with value operator support
@RequestMapping(path = { "/search" }, produces = { MediaType.APPLICATION_JSON_VALUE }, method = { RequestMethod.GET, RequestMethod.POST }) default ResponseEntity<Iterable<Employee>> search( @ApiIgnore @QuerydslPredicate(root = Employee.class) Predicate predicate, @PageableDefault Pageable pageable) { if (predicate == null || (BooleanBuilder.class.isAssignableFrom(predicate.getClass()) && !((BooleanBuilder) predicate).hasValue())) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } else { return ResponseEntity.ok(this.findAll(predicate, pageable)); } }
@RequestMapping(path = { "/emails/{emailAddress}" }, produces = { MediaType.APPLICATION_JSON_VALUE }, method = { RequestMethod.GET, RequestMethod.POST }) default ResponseEntity<Iterable<Employee>> search(@PathVariable String emailAddress) { if (StringUtils.isBlank(emailAddress)) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } else { return search(QEmployee.employee.emails.any().address.eq(emailAddress), PageRequest.of(0, 10)); } }
customize(..) method’s implementation illustrates the usage of ExpressionProviderFactory to enable rich value operators on search fields.
Example application is equipped with e2e integration tests demonstrating various queries and how the result/response varies depending on different usage of value operators. Example queries mentioned below in this document can also be seen within EmployeeSearchIT test class.
To run integration tests from Maven:
mvn clean verify
Tests can also be executed from IDE directly after example application is imported
Since example application uses Embedded MongoDB, the startup may take time as it downloads the required binaries. Note that MongoDB is started as a forked process so process running the example API must have administrative privelege on user’s machine as otherwise startup may fail.
By default application will start on port 8080 which can be overridden by providing a JVM property server.port to a different value.
http://localhost:8080/employees/search?userName=dgayle
http://localhost:8080/employees/search?userName=dgayle&userName=ssmith
http://localhost:8080/employees/search?userName=dgayle&userName=contains(smith)
http://localhost:8080/employees/search?userName=dgayle&userName=contains(smith)&userName=and(not(startsWith(k)))
http://localhost:8080/employees/search?emails.address=ssmith@company.com
http://localhost:8080/employees/search?emails.address=ne(ssmith@company.com)
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&emails.address=and(not(endsWith(@dummy.com)))
http://localhost:8080/employees/search?emails.address=endsWith(@example.com)&emails.address=or(endsWith(@dummy.com))
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&emails.address=and(endsWith(@dummy.com))
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&jobData.department=and(SALES)
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&jobData.department=and(ne(SALES))
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&status=LOCKED
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&status=ne(LOCKED) http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&status=eq(ACTIVE) http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&status=ACTIVE
http://localhost:8080/employees/search?emails.address=endsWith(@company.com)&profile.age=gte(41)
http://localhost:8080/employees/search?profile.age=gt(41)
http://localhost:8080/employees/search?profile.age=lt(41)
http://localhost:8080/employees/emails/dgayle@company.com
- Search a user with specific date of birth: http://localhost:8080/employees/search?profile.dob=1984-01-10 - Search all users with date of birth in specific date range: http://localhost:8080/employees/search?profile.dob=1994-01-10&profile.dob=1962-01-10 - Search all users with date of birth matching with any of the input: http://localhost:8080/employees/search?profile.dob=1984-01-10&profile.dob=1986-07-11&profile.dob=1978-10-08&profile.dob=1962-01-10
What changes would need to be made to allow searches to find employees between certain age group? (e.g. Find employees in 40 and 50 years old age group)
Obviously that search will not work in out of the box example application and is intentionally done so to facilitate this exercise.
Solution for this exercise can be referred here