Skip to content

Commit ca2224a

Browse files
committed
DATACMNS-810 - Add common documentation for Query by Example.
1 parent c9d9f90 commit ca2224a

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
[[query.by.example]]
2+
= Query by Example
3+
4+
== Introduction
5+
6+
This chapter will give you an introduction to Query by Example and explain how to use Examples.
7+
8+
Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require to write queries containing field names. In fact, Query by Example does not require to write queries using store-specific query languages at all.
9+
10+
== Usage
11+
12+
An `Example` takes a data object (usually the domain object or a subtype of it) and a specification how to match properties.
13+
14+
Query by Example is suited for several use-cases but also comes with limitations:
15+
16+
**When to use**
17+
18+
* Querying your data store with a set of static or dynamic constraints
19+
* Frequent refactoring of the domain objects without worrying about breaking existing queries
20+
* Works independently from the data store API
21+
22+
**Limitations**
23+
24+
* Query predicates are combined using the `AND` keyword
25+
* No support for nested/grouped property constraints like `firstname = ?0 or (firstname = ?1 and lastname = ?2)`
26+
* Only supports starts/contains/ends/regex matching for strings and exact matching for other property types
27+
28+
Before getting started with Query by Example, you need to have your interface to the data store set up.
29+
30+
.Sample Person object
31+
====
32+
[source,java]
33+
----
34+
public class Person {
35+
36+
@Id
37+
private String id;
38+
private String firstname;
39+
private String lastname;
40+
private Address address;
41+
42+
// … getters and setters omitted
43+
}
44+
----
45+
====
46+
47+
This is a simple domain object. You can use it to create an Example. By default, fields having `null` values are ignored, and strings are matched using the store specific defaults. Examples can be built by either using the `of` factory method or by using the <<query.by.example.examplespec,`ExampleSpec`>>. Once the `Example` is constructed it becomes immutable.
48+
49+
.Simple Example
50+
====
51+
[source,xml]
52+
----
53+
Person person = new Person(); <1>
54+
55+
person.setFirstname("Dave"); <2>
56+
57+
Example<Person> example = Example.of(person); <3>
58+
----
59+
<1> Create a new instance of the domain object
60+
<2> Set the properties to query
61+
<3> Create an `Example`
62+
====
63+
64+
65+
NOTE: Property names of the sample object must correlate with the property names of the queried domain object.
66+
67+
Examples can be executed ideally with Repositories. To do so, let your repository extend from `QueryByExampleExecutor`, here's an excerpt from the interface:
68+
69+
.The `QueryByExampleExecutor`
70+
====
71+
[source, java]
72+
----
73+
public interface QueryByExampleExecutor<T> {
74+
75+
<S extends T> T findOne(Example<S> example);
76+
77+
<S extends T> Iterable<T> findAll(Example<S> example);
78+
79+
// … more functionality omitted.
80+
}
81+
----
82+
====
83+
84+
85+
[[query.by.example.examplespec]]
86+
== Example Spec
87+
88+
Examples are not limited to default settings. You can specify own defaults for string matching, null handling and property-specific settings using the example builder.
89+
90+
91+
.Example Spec with customized matching
92+
====
93+
[source,xml]
94+
----
95+
Person person = new Person(); <1>
96+
97+
person.setFirstname("Dave"); <2>
98+
99+
ExampleSpec<Person> exampleSpec = ExampleSpec.of(Person.class) <3>
100+
101+
.withIgnorePaths("lastname") <4>
102+
103+
.withIncludeNullValues() <5>
104+
105+
.withStringMatcherEnding(); <6>
106+
107+
Example example = Example.of(person, exampleSpec); <7>
108+
109+
----
110+
<1> Create a new instance of the domain object
111+
<2> Set the properties to query
112+
<3> Create an `ExampleSpec` for the `Person` type. The ExampleSpec is already usable at this stage.
113+
<4> Construct a new `ExampleSpec` to ignore the property path `lastname`
114+
<5> Construct a new `ExampleSpec` to ignore the property path `lastname` and to include null values
115+
<6> Construct a new `ExampleSpec` to ignore the property path `lastname`, to include null values, and use perform suffix string matching
116+
<6> Create a new `Example` based on the domain object and the configured `ExampleSpec`
117+
====
118+
119+
`ExampleSpec` is immutable. It creates a new `ExampleSpec` instance on each `with…(…)` invocation so intermediate objects can be safely reused. An `ExampleSpec` can be used to create ad-hoc example specs or to be reused across the application as specification for `Example`. You can use `ExampleSpec` as template to configure a default behavior for your example spec and derive from it to create a more specific `ExampleSpec` where you need to customize it.
120+
121+
You can specify behavior for individual properties (e.g. "firstname" and "lastname", "address.city" for nested properties) You can tune it with matching options and case sensitivity.
122+
123+
.Configuring matcher options
124+
====
125+
[source,java]
126+
----
127+
ExampleSpec<Person> example = ExampleSpec.of(Person.class)
128+
.withMatcher("firstname", endsWith())
129+
.withMatcher("lastname", startsWith().ignoreCase());
130+
}
131+
----
132+
====
133+
134+
Another style to configure matcher options is by using Java 8 lambdas. This approach is a callback that asks the implementor to modify the matcher. It's not required to return the matcher because configuration options are held within the matcher instance.
135+
136+
.Configuring matcher options with lambdas
137+
====
138+
[source,java]
139+
----
140+
ExampleSpec<Person> example = ExampleSpec.of(Person.class)
141+
.withMatcher("firstname", matcher -> matcher.endsWith())
142+
.withMatcher("firstname", matcher -> matcher.startsWith());
143+
}
144+
----
145+
====
146+
147+
Matcher settings are merged at the time of creating the data-store specific query. Any options that are not explicitly configured on a property-specific matcher are used from the default settings.

0 commit comments

Comments
 (0)