|
| 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 | +The Query by Example API consists of three parts: |
| 13 | + |
| 14 | +* Probe: That is the actual example of a domain object with populated fields. |
| 15 | +* `ExampleSpec`: The `ExampleSpec` carries details on how to match particular fields. It can be reused across multiple Examples. `ExampleSpec` comes in two flavors: <<query.by.example.examplespec,untyped>> and <<query.by.example.examplespec.typed,typed>>. |
| 16 | +* `Example`: An Example consists of the probe and the ExampleSpec. It is used to create the query. An `Example` takes a probe (usually the domain object or a subtype of it) and the `ExampleSpec`. |
| 17 | + |
| 18 | +Query by Example is suited for several use-cases but also comes with limitations: |
| 19 | + |
| 20 | +**When to use** |
| 21 | + |
| 22 | +* Querying your data store with a set of static or dynamic constraints |
| 23 | +* Frequent refactoring of the domain objects without worrying about breaking existing queries |
| 24 | +* Works independently from the underlying data store API |
| 25 | + |
| 26 | +**Limitations** |
| 27 | + |
| 28 | +* Query predicates are combined using the `AND` keyword |
| 29 | +* No support for nested/grouped property constraints like `firstname = ?0 or (firstname = ?1 and lastname = ?2)` |
| 30 | +* Only supports starts/contains/ends/regex matching for strings and exact matching for other property types |
| 31 | + |
| 32 | +Before getting started with Query by Example, you need to have a domain object. To get started, simply create an interface for your repository: |
| 33 | + |
| 34 | +.Sample Person object |
| 35 | +==== |
| 36 | +[source,java] |
| 37 | +---- |
| 38 | +public class Person { |
| 39 | +
|
| 40 | + @Id |
| 41 | + private String id; |
| 42 | + private String firstname; |
| 43 | + private String lastname; |
| 44 | + private Address address; |
| 45 | +
|
| 46 | + // … getters and setters omitted |
| 47 | +} |
| 48 | +---- |
| 49 | +==== |
| 50 | + |
| 51 | +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 <<query.by.example.examplespec,`ExampleSpec`>>. `Example` is immutable. |
| 52 | + |
| 53 | +.Simple Example |
| 54 | +==== |
| 55 | +[source,java] |
| 56 | +---- |
| 57 | +Person person = new Person(); <1> |
| 58 | +
|
| 59 | +person.setFirstname("Dave"); <2> |
| 60 | +
|
| 61 | +Example<Person> example = Example.of(person); <3> |
| 62 | +---- |
| 63 | +<1> Create a new instance of the domain object |
| 64 | +<2> Set the properties to query |
| 65 | +<3> Create the `Example` |
| 66 | +==== |
| 67 | + |
| 68 | +NOTE: Property names of the sample object must correlate with the property names of the queried domain object. |
| 69 | + |
| 70 | +Examples can be executed ideally with Repositories. To do so, let your repository extend from `QueryByExampleExecutor`, here's an excerpt from the `QueryByExampleExecutor` interface: |
| 71 | + |
| 72 | +.The `QueryByExampleExecutor` |
| 73 | +==== |
| 74 | +[source, java] |
| 75 | +---- |
| 76 | +public interface QueryByExampleExecutor<T> { |
| 77 | +
|
| 78 | + <S extends T> S findOne(Example<S> example); |
| 79 | +
|
| 80 | + <S extends T> Iterable<S> findAll(Example<S> example); |
| 81 | +
|
| 82 | + // … more functionality omitted. |
| 83 | +} |
| 84 | +---- |
| 85 | +==== |
| 86 | + |
| 87 | +You can read more about <<query.by.example.execution, Query by Example Execution>> below. |
| 88 | + |
| 89 | +[[query.by.example.examplespec]] |
| 90 | +=== Example Spec |
| 91 | + |
| 92 | +Examples are not limited to default settings. You can specify own defaults for string matching, null handling and property-specific settings using the `ExampleSpec`. `ExampleSpec` comes in two flavors: untyped and typed. By default `Example.of(Person.class)` uses an untyped `ExampleSpec`. Using untyped `ExampleSpec` will use the Repository entity information to determine the type to query and has no control over inheritance queries. Also, untyped `ExampleSpec` will use the probe type when using with a Template to determine the type to query. Read more about <<query.by.example.examplespec.typed,typed `ExampleSpec`>> below. |
| 93 | + |
| 94 | +.Untyped Example Spec with customized matching |
| 95 | +==== |
| 96 | +[source,java] |
| 97 | +---- |
| 98 | +Person person = new Person(); <1> |
| 99 | +
|
| 100 | +person.setFirstname("Dave"); <2> |
| 101 | +
|
| 102 | +ExampleSpec exampleSpec = ExampleSpec.untyped() <3> |
| 103 | +
|
| 104 | + .withIgnorePaths("lastname") <4> |
| 105 | +
|
| 106 | + .withIncludeNullValues() <5> |
| 107 | +
|
| 108 | + .withStringMatcherEnding(); <6> |
| 109 | +
|
| 110 | +Example<Person> example = Example.of(person, exampleSpec); <7> |
| 111 | +
|
| 112 | +---- |
| 113 | +<1> Create a new instance of the domain object. |
| 114 | +<2> Set properties. |
| 115 | +<3> Create an untyped `ExampleSpec`. The `ExampleSpec` is usable at this stage. |
| 116 | +<4> Construct a new `ExampleSpec` to ignore the property path `lastname`. |
| 117 | +<5> Construct a new `ExampleSpec` to ignore the property path `lastname` and to include null values. |
| 118 | +<6> Construct a new `ExampleSpec` to ignore the property path `lastname`, to include null values, and use perform suffix string matching. |
| 119 | +<7> Create a new `Example` based on the domain object and the configured `ExampleSpec`. |
| 120 | +==== |
| 121 | + |
| 122 | +`ExampleSpec` is immutable. Calls to `with…(…)` return a copy of `ExampleSpec` with the specific setting applied. 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 a specification for `Example`. You can use `ExampleSpec` as a template to configure a default behavior for your example spec. You also can derive from it a more specific `ExampleSpec` where you need to customize it. |
| 123 | + |
| 124 | +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. |
| 125 | + |
| 126 | +.Configuring matcher options |
| 127 | +==== |
| 128 | +[source,java] |
| 129 | +---- |
| 130 | +ExampleSpec exampleSpec = ExampleSpec.untyped() |
| 131 | + .withMatcher("firstname", endsWith()) |
| 132 | + .withMatcher("lastname", startsWith().ignoreCase()); |
| 133 | +} |
| 134 | +---- |
| 135 | +==== |
| 136 | + |
| 137 | +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. |
| 138 | + |
| 139 | +.Configuring matcher options with lambdas |
| 140 | +==== |
| 141 | +[source,java] |
| 142 | +---- |
| 143 | +ExampleSpec exampleSpec = ExampleSpec.untyped() |
| 144 | + .withMatcher("firstname", matcher -> matcher.endsWith()) |
| 145 | + .withMatcher("firstname", matcher -> matcher.startsWith()); |
| 146 | +} |
| 147 | +---- |
| 148 | +==== |
| 149 | + |
| 150 | +Queries created by `Example` use a merged view of the configuration. Default matching settings can be set at `ExampleSpec` level while individual settings can be applied to particular property paths. Settings that are set on `ExampleSpec` are inherited by property path settings unless they are defined explicitly. Settings on a property patch have higher precedence than default settings. |
| 151 | + |
| 152 | +[cols="1,2", options="header"] |
| 153 | +.Scope of `ExampleSpec` settings |
| 154 | +|=== |
| 155 | +| Setting |
| 156 | +| Scope |
| 157 | + |
| 158 | +| Null-handling |
| 159 | +| `ExampleSpec` |
| 160 | + |
| 161 | +| String matching |
| 162 | +| `ExampleSpec` and property path |
| 163 | + |
| 164 | +| Ignoring properties |
| 165 | +| Property path |
| 166 | + |
| 167 | +| Case sensitivity |
| 168 | +| `ExampleSpec` and property path |
| 169 | + |
| 170 | +| Value transformation |
| 171 | +| Property path |
| 172 | + |
| 173 | +|=== |
| 174 | + |
| 175 | +[[query.by.example.examplespec.typed]] |
| 176 | +==== Typed Example Spec |
| 177 | +You have now seen the usage of untyped `ExampleSpec`. The second flavor of `ExampleSpec` is typed which adds more control over the result type. When executing an `Example` containing a typed `ExampleSpec` the type of the `ExampleSpec` is used as domain type. Control over the domain type is useful in particular when querying along the inheritance hierarchy or the repository contains multiple types within one table/collection/keyspace. |
| 178 | + |
| 179 | +.Sample Person object |
| 180 | +==== |
| 181 | +[source,java] |
| 182 | +---- |
| 183 | +public class SpecialPerson extends Person { |
| 184 | +
|
| 185 | + // … more functionality omitted. |
| 186 | +} |
| 187 | +---- |
| 188 | +==== |
| 189 | + |
| 190 | +.Typed Example Spec with customized matching |
| 191 | +==== |
| 192 | +[source,java] |
| 193 | +---- |
| 194 | +QueryByExampleExecutor<Person> personRepository = … ; |
| 195 | +
|
| 196 | +Person person = new Person(); <1> |
| 197 | +
|
| 198 | +person.setFirstname("Dave"); <2> |
| 199 | +
|
| 200 | +ExampleSpec<SpecialPerson> exampleSpec = ExampleSpec.typed(SpecialPerson.class); <3> |
| 201 | +
|
| 202 | +Example<Person> example = Example.of(person, exampleSpec); <4> |
| 203 | +
|
| 204 | +List<Person> result = personRepository.findAll(example); <5> |
| 205 | +
|
| 206 | +---- |
| 207 | +<1> Create a new instance of the domain object. |
| 208 | +<2> Set properties. |
| 209 | +<3> Create a typed `ExampleSpec` for `SpecialPerson` that extends `Person`. |
| 210 | +<4> Construct a new `Example` using the typed `ExampleSpec` and the `Person` probe. |
| 211 | +<5> Run a query to select all instances of `SpecialPerson` in the repository. Note that the result type is the base class `Person`. |
| 212 | +==== |
| 213 | + |
0 commit comments