As we saw in the last tutorial that Query By Example (QBE) can be carried out by using an Example instance which takes a Probe (an entity instance populated with the desired fields' values). Example can also use ExampleMatcher which carries the details on how to match particular fields.
Example interface:
package org.springframework.data.domain;
....
public interface Example<T> {
//Creates a new Example with all non-null properties by default.
static <T> Example<T> of(T probe) {
return new TypedExample<>(probe, ExampleMatcher.matching());
}
//Creates a new Example using the given ExampleMatcher.
static <T> Example<T> of(T probe, ExampleMatcher matcher) {
return new TypedExample<>(probe, matcher);
}
....
}
In the last tutorial we saw the use of the first method Example.of(probe) which, by default, uses an instance of ExampleMatcher created by ExampleMatcher.matching() . That targets all non-null properties to generate the query.
In this tutorial we will see how to use the second method Example.of(probe, matcher) and how to create an instance of ExampleMatcher to customize the underlying query.
ExampleMatcher interface:
package org.springframework.data.domain;
....
public interface ExampleMatcher {
//targets all non-null properties ('and' conjunction among different properties of the same entity)
static ExampleMatcher matching() {return matchingAll();}
//targets any non-null properties ('or' conjunction among different properties of the same entity)
static ExampleMatcher matchingAny() {....}
//targets all non-null properties (same as matching())
static ExampleMatcher matchingAll() {....}
//ignores provided property paths
ExampleMatcher withIgnorePaths(String... ignoredPaths);
//matches string as per the provided stringMatcher (an enum)
ExampleMatcher withStringMatcher(StringMatcher stringMatcher);
//ignores case
default ExampleMatcher withIgnoreCase() {....}
//turn on/off case sensitivity via provided defaultIgnoreCase
ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase);
//creates matcher with the specified matcherConfigurer and propertyPath.
default ExampleMatcher withMatcher(String propertyPath,
MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {....}
//creates matcher with the specified genericPropertyMatcher and propertyPath.
ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);
//creates matcher with the specified propertyValueTransformer
ExampleMatcher withTransformer(String propertyPath,
PropertyValueTransformer propertyValueTransformer);
//creates a case insensitive matcher for the specified propertyPaths
ExampleMatcher withIgnoreCase(String... propertyPaths);
//includes null values
default ExampleMatcher withIncludeNullValues() {....}
//ignores null values
default ExampleMatcher withIgnoreNullValues() {....}
//creates matcher with the specified nullHander
ExampleMatcher withNullHandler(NullHandler nullHandler);
//Null handling
enum NullHandler {INCLUDE, IGNORE}
//configures a matcher
interface MatcherConfigurer<T> {
void configureMatcher(T matcher);
}
....
//A matcher that specifies StringMatcher string matching and case sensitivity.
class GenericPropertyMatcher {
public static GenericPropertyMatcher of(StringMatcher stringMatcher, boolean ignoreCase){..}
public static GenericPropertyMatcher of(StringMatcher stringMatcher) {....}
public GenericPropertyMatcher ignoreCase() {....}
public GenericPropertyMatcher ignoreCase(boolean ignoreCase) {....}
public GenericPropertyMatcher caseSensitive() {}
public GenericPropertyMatcher contains() {....}
public GenericPropertyMatcher endsWith() {....}
public GenericPropertyMatcher startsWith() {....}
public GenericPropertyMatcher exact() {....}
public GenericPropertyMatcher storeDefaultMatching() {....}
public GenericPropertyMatcher regex() {....}
public GenericPropertyMatcher stringMatcher(StringMatcher stringMatcher) {....}
public GenericPropertyMatcher transform(PropertyValueTransformer propertyValueTransformer){..}
}
....
//Predefined GenericPropertyMatcher
class GenericPropertyMatchers {
public static GenericPropertyMatcher ignoreCase() {....}
public static GenericPropertyMatcher caseSensitive() {....}
public static GenericPropertyMatcher contains() {....}
public static GenericPropertyMatcher endsWith() {....}
public static GenericPropertyMatcher startsWith() {....}
public static GenericPropertyMatcher exact() {....}
public static GenericPropertyMatcher storeDefaultMatching() {....}
public static GenericPropertyMatcher regex() {....}
}
//Match modes for treatment of String values.
enum StringMatcher {
DEFAULT, //Store specific default
EXACT,//default if StringMatcher not applied
STARTING,
ENDING,
CONTAINING,
REGEX;//not yet supported (tested in version spring-data-jpa 2.1.1.RELEASE)
}
//Allows to transform the property value before it is used in the query
interface PropertyValueTransformer extends Function<Optional<Object>, Optional<Object>> {....}
....
}
We can divided above methods of ExampleMatcher in the following groups:
Applying and/or conjunction on non-null properties
'and' conjunction' methods matching()/matchingAll()
'or' conjunction method matchingAny();
Above methods are static factory methods, so they are starting point. The return value of ExampleMatcher can be chained further as all other methods also return ExampleMatcher.
Example
Employee employee = new Employee();
employee.setName("Diana");
employee.setDept("IT");
//it will return all Employees with name 'Diana' OR with dept 'IT'
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAny());
....
//it will return all Employees with name 'Diana' AND with dept 'IT'
Example<Employee> employeeExample2 = Example.of(employee, ExampleMatcher.matchingAll());
Ignoring paths
withIgnorePaths(String... ignoredPaths)
Example
In following example, assuming Person entity's id is type of 'long' which will default to 0 instead of null. In that case we must ignore id property via ExampleMatcher:
Person person = new Person();
person.setName("Tara");
ExampleMatcher exampleMatcher = ExampleMatcher.matching()//same as matchingAll()
.withIgnorePaths("id");
Example<Person> personExample = Example.of(person, exampleMatcher);
Case sensitivity
withIgnoreCase(), withIgnoreCase(true/false), withIgnoreCase(String... propertyPaths)
Example
Employee employee = new Employee();
employee.setName("tim");
employee.setDept("qa");
//ignoring case for all non-null properties
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAll().withIgnoreCase());
//ignoring case for just 'name' property
Example<Employee> employeeExample2 = Example.of(employee, ExampleMatcher.matchingAll().withIgnoreCase("name"));
Other String matching criteria
For all paths:
withStringMatcher(StringMatcher stringMatcher)
Example
Employee employee = new Employee();
employee.setName("k");
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withStringMatcher(ExampleMatcher.StringMatcher.ENDING);
Example<Employee> employeeExample = Example.of(employee, matcher);
For specified path:
withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) withMatcher(String propertyPath, MatcherConfigurer
matcherConfigurer)
Example
Using first method:
Employee employee = new Employee();
employee.setName("k");
employee.setDept("IT");
ExampleMatcher name = ExampleMatcher.matchingAll()
.withMatcher("name",
ExampleMatcher.GenericPropertyMatchers.endsWith());
Example<Employee> employeeExample = Example.of(employee, name);
Using second method:
Employee employee = new Employee();
employee.setName("k");
employee.setDept("IT");
ExampleMatcher name =
ExampleMatcher.matchingAll()
.withMatcher("name",
//can be replaced with lambda
new ExampleMatcher.MatcherConfigurer<ExampleMatcher.GenericPropertyMatcher>() {
@Override
public void configureMatcher(ExampleMatcher.GenericPropertyMatcher matcher) {
matcher.endsWith();
}
});
Example<Employee> employeeExample = Example.of(employee, name);
Note that StringMatcher.REGEX is not yet supported (version spring-data-jpa 2.1.1.RELEASE). Check out source code: QueryByExamplePredicateBuilder#getPredicates()
Transforming property values
withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer)
Example
Employee employee = new Employee();
employee.setName("Tim");
employee.setDept("qa");
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withTransformer("dept",
//can be replaced with lambda
new ExampleMatcher.PropertyValueTransformer() {
@Override
public Optional<Object> apply(Optional<Object> o) {
if (o.isPresent()) {
return Optional.of(((String) o.get()).toUpperCase());
}
return o;
}
});
Example<Employee> employeeExample = Example.of(employee, matcher);
Handling nulls
withIncludeNullValues() withIgnoreNullValues() withNullHandler(NullHandler nullHandler)
Example
Person person = new Person();
//this will return all persons with null name
ExampleMatcher exampleMatcher = ExampleMatcher.matchingAll()
.withIgnorePaths("id")
.withIncludeNullValues();
Example<Person> employeePerson = Example.of(person, exampleMatcher);
Complete Example
Entities
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
private String dept;
.............
}
@Entity
public class Person {
@Id
@GeneratedValue
private long id;
private String name;
.............
}
Repositories
public interface EmployeeRepository extends CrudRepository<Employee, Long>,
QueryByExampleExecutor<Employee> {
}
public interface PersonRepository extends CrudRepository<Person, Long>,
QueryByExampleExecutor<Person> {
}
Example Client
@Component
public class ExampleClient {
@Autowired
private EmployeeRepository exampleRepo;
@Autowired
private PersonRepository personRepo;
public void run() {
List<Employee> employees = createEmployees();
exampleRepo.saveAll(employees);
findAllEmployees();
List<Person> persons = createPersons();
personRepo.saveAll(persons);
findAllPersons();
findEmployeesByNameOrDept();
findEmployeesByNameAndDept();
findPersonByNameIgnoringIdPath();
findEmployeeByNameAndDeptCaseInsensitive();
findEmployeeByNameAndDeptIgnoringPathForCase();
findEmployeeByNameEnding();
findEmployeeByNameEndingAndByDept();
//regex is not yet supported
//findEmployeeByNameUsingRegex();
findEmployeeByTransformingName();
findPersonWithNullName();
findEmployeeByNameIgnoringCaseAndContains();
}
private void findEmployeesByNameOrDept() {
System.out.println("-- finding employees with name Diana or dept IT --");
Employee employee = new Employee();
employee.setName("Diana");
employee.setDept("IT");
System.out.println("Example entity: "+employee);
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAny());
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeesByNameAndDept() {
System.out.println("-- finding employees with name Diana and dept IT --");
Employee employee = new Employee();
employee.setName("Diana");
employee.setDept("IT");
System.out.println("Example entity: "+employee);
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAll());
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findPersonByNameIgnoringIdPath() {
System.out.println("-- finding person by name Tara ignoring id=0 --");
Person person = new Person();
person.setName("Tara");
System.out.println("Example entity: "+person);
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withIgnorePaths("id");
Example<Person> personExample = Example.of(person, exampleMatcher);
personRepo.findAll(personExample).forEach(System.out::println);
}
private void findEmployeeByNameAndDeptCaseInsensitive() {
System.out.println("-- finding employees with name tim and dept qa ignoring cases --");
Employee employee = new Employee();
employee.setName("tim");
employee.setDept("qa");
System.out.println("Example entity: "+employee);
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAll().withIgnoreCase());
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeeByNameAndDeptIgnoringPathForCase() {
System.out.println("-- finding employees with name tim (ignoring case) and dept QA (not ignoring case) --");
Employee employee = new Employee();
employee.setName("tim");
employee.setDept("QA");
System.out.println("Example entity: "+employee);
Example<Employee> employeeExample = Example.of(employee, ExampleMatcher.matchingAll().withIgnoreCase("name"));
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeeByNameEnding() {
System.out.println("-- finding employees with name ending k --");
Employee employee = new Employee();
employee.setName("k");
System.out.println("Example entity: "+employee);
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withStringMatcher(ExampleMatcher.StringMatcher.ENDING);
Example<Employee> employeeExample = Example.of(employee, matcher);
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeeByNameEndingAndByDept() {
System.out.println("-- finding employees with name ending k and dept IT --");
Employee employee = new Employee();
employee.setName("k");
employee.setDept("IT");
System.out.println("Example entity: "+employee);
ExampleMatcher name =
ExampleMatcher.matchingAll()
.withMatcher("name",
//can be replaced with lambda
new ExampleMatcher.MatcherConfigurer<ExampleMatcher.GenericPropertyMatcher>() {
@Override
public void configureMatcher(ExampleMatcher.GenericPropertyMatcher matcher) {
matcher.endsWith();
}
});
Example<Employee> employeeExample = Example.of(employee, name);
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeeByNameUsingRegex() {
System.out.println(" -- getting all Employees with name regex D.*a.*a --");
Employee employee = new Employee();
employee.setName("D.*a.*a");
System.out.println("Example entity: "+employee);
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withStringMatcher(ExampleMatcher.StringMatcher.REGEX);
Example<Employee> employeeExample = Example.of(employee, matcher);
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findEmployeeByTransformingName() {
System.out.println(" -- getting all Employees with dept and transforming name during execution time --");
Employee employee = new Employee();
employee.setName("Tim");
employee.setDept("qa");
System.out.println("Example entity: "+employee);
ExampleMatcher matcher = ExampleMatcher.matchingAll().
withTransformer("dept",
//can be replaced with lambda
new ExampleMatcher.PropertyValueTransformer() {
@Override
public Optional<Object> apply(Optional<Object> o) {
if (o.isPresent()) {
return Optional.of(((String) o.get()).toUpperCase());
}
return o;
}
});
Example<Employee> employeeExample = Example.of(employee, matcher);
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findPersonWithNullName() {
System.out.println("-- finding person with null name --");
Person person = new Person();
person.setName(null);
System.out.println("Example entity: "+person);
ExampleMatcher exampleMatcher = ExampleMatcher.matchingAll()
.withIgnorePaths("id")
.withIncludeNullValues();
Example<Person> personExample = Example.of(person, exampleMatcher);
personRepo.findAll(personExample).forEach(System.out::println);
}
private void findEmployeeByNameIgnoringCaseAndContains() {
System.out.println("-- finding employees with ignored case name contains 'AC' --");
Employee employee = new Employee();
employee.setName("AC");
employee.setDept("IT");
System.out.println("Example entity: "+employee);
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase("name")
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example<Employee> employeeExample = Example.of(employee, matcher);
Iterable<Employee> employees = exampleRepo.findAll(employeeExample);
for (Employee e : employees) {
System.out.println(e);
}
}
private void findAllEmployees() {
System.out.println(" -- getting all Employees --");
Iterable<Employee> iterable = exampleRepo.findAll();
for (Employee employee : iterable) {
System.out.println(employee);
}
}
private void findAllPersons() {
System.out.println(" -- getting all Persons --");
Iterable<Person> iterable = personRepo.findAll();
for (Person person : iterable) {
System.out.println(person);
}
}
private static List<Employee> createEmployees() {
return Arrays.asList(Employee.create("Diana", "IT"),
Employee.create("Mike", "Admin"),
Employee.create("Tim", "QA"),
Employee.create("Jack", "IT"));
}
private static List<Person> createPersons() {
return Arrays.asList(Person.create("Joe"),
Person.create("Tara"),
Person.create(null));
}
}
Main class
public class ExampleMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
ExampleClient exampleClient = context.getBean(ExampleClient.class);
exampleClient.run();
EntityManagerFactory emf = context.getBean(EntityManagerFactory.class);
emf.close();
}
} -- getting all Employees -- Employee{id=1, name='Diana', dept='IT'} Employee{id=2, name='Mike', dept='Admin'} Employee{id=3, name='Tim', dept='QA'} Employee{id=4, name='Jack', dept='IT'} -- getting all Persons -- Person{id=5, name='Joe'} Person{id=6, name='Tara'} Person{id=7, name='null'} -- finding employees with name Diana or dept IT -- Example entity: Employee{id=null, name='Diana', dept='IT'} Employee{id=1, name='Diana', dept='IT'} Employee{id=4, name='Jack', dept='IT'} -- finding employees with name Diana and dept IT -- Example entity: Employee{id=null, name='Diana', dept='IT'} Employee{id=1, name='Diana', dept='IT'} -- finding person by name Tara ignoring id=0 -- Example entity: Person{id=0, name='Tara'} Person{id=6, name='Tara'} -- finding employees with name tim and dept qa ignoring cases -- Example entity: Employee{id=null, name='tim', dept='qa'} Employee{id=3, name='Tim', dept='QA'} -- finding employees with name tim (ignoring case) and dept QA (not ignoring case) -- Example entity: Employee{id=null, name='tim', dept='QA'} Employee{id=3, name='Tim', dept='QA'} -- finding employees with name ending k -- Example entity: Employee{id=null, name='k', dept='null'} Employee{id=4, name='Jack', dept='IT'} -- finding employees with name ending k and dept IT -- Example entity: Employee{id=null, name='k', dept='IT'} Employee{id=4, name='Jack', dept='IT'} -- getting all Employees with dept and transforming name during execution time -- Example entity: Employee{id=null, name='Tim', dept='qa'} Employee{id=3, name='Tim', dept='QA'} -- finding person with null name -- Example entity: Person{id=0, name='null'} Person{id=7, name='null'} -- finding employees with ignored case name contains 'AC' -- Example entity: Employee{id=null, name='AC', dept='IT'} Employee{id=4, name='Jack', dept='IT'}
Example ProjectDependencies and Technologies Used: - spring-data-jpa 2.1.1.RELEASE: Spring Data module for JPA repositories.
Uses org.springframework:spring-context version 5.1.1.RELEASE - hibernate-core 5.3.7.Final: Hibernate's core ORM functionality.
Implements javax.persistence:javax.persistence-api version 2.2 - h2 1.4.197: H2 Database Engine.
- JDK 1.8
- Maven 3.5.4
|