Spring Data Relational
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

296 lines
12 KiB

[[jdbc.query-methods]]
= Query Methods
This section offers some specific information about the implementation and use of Spring Data JDBC.
Most of the data access operations you usually trigger on a repository result in a query being run against the databases.
Defining such a query is a matter of declaring a method on the repository interface, as the following example shows:
.PersonRepository with query methods
[source,java]
----
interface PersonRepository extends PagingAndSortingRepository<Person, String> {
List<Person> findByFirstname(String firstname); <1>
List<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <2>
Slice<Person> findByLastname(String lastname, Pageable pageable); <3>
Page<Person> findByLastname(String lastname, Pageable pageable); <4>
Person findByFirstnameAndLastname(String firstname, String lastname); <5>
Person findFirstByLastname(String lastname); <6>
@Query("SELECT * FROM person WHERE lastname = :lastname")
List<Person> findByLastname(String lastname); <7>
@Query("SELECT * FROM person WHERE lastname = :lastname")
Stream<Person> streamByLastname(String lastname); <8>
@Query("SELECT * FROM person WHERE username = :#{ principal?.username }")
Person findActiveUser(); <9>
}
----
<1> The method shows a query for all people with the given `firstname`.
The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`.
Thus, the method name results in a query expression of `SELECT … FROM person WHERE firstname = :firstname`.
<2> Use `Pageable` to pass offset and sorting parameters to the database.
<3> Return a `Slice<Person>`.Selects `LIMIT+1` rows to determine whether there's more data to consume. `ResultSetExtractor` customization is not supported.
<4> Run a paginated query returning `Page<Person>`.Selects only data within the given page bounds and potentially a count query to determine the total count. `ResultSetExtractor` customization is not supported.
<5> Find a single entity for the given criteria.
It completes with `IncorrectResultSizeDataAccessException` on non-unique results.
<6> In contrast to <3>, the first entity is always emitted even if the query yields more result documents.
<7> The `findByLastname` method shows a query for all people with the given `lastname`.
<8> The `streamByLastname` method returns a `Stream`, which makes values possible as soon as they are returned from the database.
<9> You can use the Spring Expression Language to dynamically resolve parameters.
In the sample, Spring Security is used to resolve the username of the current user.
The following table shows the keywords that are supported for query methods:
[cols="1,2,3",options="header",subs="quotes"]
.Supported keywords for query methods
|===
| Keyword
| Sample
| Logical result
| `After`
| `findByBirthdateAfter(Date date)`
| `birthdate > date`
| `GreaterThan`
| `findByAgeGreaterThan(int age)`
| `age > age`
| `GreaterThanEqual`
| `findByAgeGreaterThanEqual(int age)`
| `age >= age`
| `Before`
| `findByBirthdateBefore(Date date)`
| `birthdate < date`
| `LessThan`
| `findByAgeLessThan(int age)`
| `age < age`
| `LessThanEqual`
| `findByAgeLessThanEqual(int age)`
| `age \<= age`
| `Between`
| `findByAgeBetween(int from, int to)`
| `age BETWEEN from AND to`
| `NotBetween`
| `findByAgeNotBetween(int from, int to)`
| `age NOT BETWEEN from AND to`
| `In`
| `findByAgeIn(Collection<Integer> ages)`
| `age IN (age1, age2, ageN)`
| `NotIn`
| `findByAgeNotIn(Collection ages)`
| `age NOT IN (age1, age2, ageN)`
| `IsNotNull`, `NotNull`
| `findByFirstnameNotNull()`
| `firstname IS NOT NULL`
| `IsNull`, `Null`
| `findByFirstnameNull()`
| `firstname IS NULL`
| `Like`, `StartingWith`, `EndingWith`
| `findByFirstnameLike(String name)`
| `firstname LIKE name`
| `NotLike`, `IsNotLike`
| `findByFirstnameNotLike(String name)`
| `firstname NOT LIKE name`
| `Containing` on String
| `findByFirstnameContaining(String name)`
| `firstname LIKE '%' + name + '%'`
| `NotContaining` on String
| `findByFirstnameNotContaining(String name)`
| `firstname NOT LIKE '%' + name + '%'`
| `(No keyword)`
| `findByFirstname(String name)`
| `firstname = name`
| `Not`
| `findByFirstnameNot(String name)`
| `firstname != name`
| `IsTrue`, `True`
| `findByActiveIsTrue()`
| `active IS TRUE`
| `IsFalse`, `False`
| `findByActiveIsFalse()`
| `active IS FALSE`
|===
NOTE: Query derivation is limited to properties that can be used in a `WHERE` clause without using joins.
[[jdbc.query-methods.strategies]]
== Query Lookup Strategies
The JDBC module supports defining a query manually as a String in a `@Query` annotation or as named query in a property file.
Deriving a query from the name of the method is is currently limited to simple properties, that means properties present in the aggregate root directly.
Also, only select queries are supported by this approach.
[[jdbc.query-methods.at-query]]
== Using `@Query`
The following example shows how to use `@Query` to declare a query method:
.Declare a query method by using @Query
[source,java]
----
interface UserRepository extends CrudRepository<User, Long> {
@Query("select firstName, lastName from User u where u.emailAddress = :email")
User findByEmailAddress(@Param("email") String email);
}
----
For converting the query result into entities the same `RowMapper` is used by default as for the queries Spring Data JDBC generates itself.
The query you provide must match the format the `RowMapper` expects.
Columns for all properties that are used in the constructor of an entity must be provided.
Columns for properties that get set via setter, wither or field access are optional.
Properties that don't have a matching column in the result will not be set.
The query is used for populating the aggregate root, embedded entities and one-to-one relationships including arrays of primitive types which get stored and loaded as SQL-array-types.
Separate queries are generated for maps, lists, sets and arrays of entities.
Properties one-to-one relationships must have there name prefixed by the name of the relationship plus `_`.
For example if the `User` from the example above has an `address` with the property `city` the column for that `city` must be labeled `address_city`.
WARNING: Note that String-based queries do not support pagination nor accept `Sort`, `PageRequest`, and `Limit` as a query parameter as for these queries the query would be required to be rewritten.
If you want to apply limiting, please express this intent using SQL and bind the appropriate parameters to the query yourself.
Queries may contain SpEL expressions.
There are two variants that are evaluated differently.
In the first variant a SpEL expression is prefixed with `:` and used like a bind variable.
Such a SpEL expression will get replaced with a bind variable and the variable gets bound to the result of the SpEL expression.
.Use a SpEL in a query
[source,java]
----
@Query("SELECT * FROM person WHERE id = :#{#person.id}")
Person findWithSpEL(PersonRef person);
----
This can be used to access members of a parameter, as demonstrated in the example above.
For more involved use cases an `EvaluationContextExtension` can be made available in the application context, which in turn can make any object available in to the SpEL.
The other variant can be used anywhere in the query and the result of evaluating the query will replace the expression in the query string.
.Use a SpEL in a query
[source,java]
----
@Query("SELECT * FROM #{tableName} WHERE id = :id")
Person findWithSpEL(PersonRef person);
----
It is evaluated once before the first execution and uses a `StandardEvaluationContext` with the two variables `tableName` and `qualifiedTableName` added.
This use is most useful when table names are dynamic themselves, because they use SpEL expressions as well.
NOTE: Spring fully supports Java 8’s parameter name discovery based on the `-parameters` compiler flag.
By using this flag in your build as an alternative to debug information, you can omit the `@Param` annotation for named parameters.
NOTE: Spring Data JDBC supports only named parameters.
[[jdbc.query-methods.named-query]]
== Named Queries
If no query is given in an annotation as described in the previous section Spring Data JDBC will try to locate a named query.
There are two ways how the name of the query can be determined.
The default is to take the _domain class_ of the query, i.e. the aggregate root of the repository, take its simple name and append the name of the method separated by a `.`.
Alternatively the `@Query` annotation has a `name` attribute which can be used to specify the name of a query to be looked up.
Named queries are expected to be provided in the property file `META-INF/jdbc-named-queries.properties` on the classpath.
The location of that file may be changed by setting a value to `@EnableJdbcRepositories.namedQueriesLocation`.
Named queries are handled in the same way as queries provided by annotation.
[[jdbc.query-methods.customizing-query-methods]]
=== Customizing Query Methods
[[jdbc.query-methods.at-query.streaming-results]]
=== Streaming Results
When you specify Stream as the return type of a query method, Spring Data JDBC returns elements as soon as they become available.
When dealing with large amounts of data this is suitable for reducing latency and memory requirements.
The stream contains an open connection to the database.
To avoid memory leaks, that connection needs to be closed eventually, by closing the stream.
The recommended way to do that is a `try-with-resource clause`.
It also means that, once the connection to the database is closed, the stream cannot obtain further elements and likely throws an exception.
[[jdbc.query-methods.at-query.custom-rowmapper]]
=== Custom `RowMapper` or `ResultSetExtractor`
The `@Query` annotation allows you to specify a custom `RowMapper` or `ResultSetExtractor` to use.
The attributes `rowMapperClass` and `resultSetExtractorClass` allow you to specify classes to use, which will get instantiated using a default constructor.
Alternatively you may set `rowMapperClassRef` or `resultSetExtractorClassRef` to a bean name from your Spring application context.
If you want to use a certain `RowMapper` not just for a single method but for all methods with custom queries returning a certain type,
you may register a `RowMapperMap` bean and registering a `RowMapper` per method return type.
The following example shows how to register `DefaultQueryMappingConfiguration`:
[source,java]
----
@Bean
QueryMappingConfiguration rowMappers() {
return new DefaultQueryMappingConfiguration()
.register(Person.class, new PersonRowMapper())
.register(Address.class, new AddressRowMapper());
}
----
When determining which `RowMapper` to use for a method, the following steps are followed, based on the return type of the method:
. If the type is a simple type, no `RowMapper` is used.
+
Instead, the query is expected to return a single row with a single column, and a conversion to the return type is applied to that value.
. The entity classes in the `QueryMappingConfiguration` are iterated until one is found that is a superclass or interface of the return type in question.
The `RowMapper` registered for that class is used.
+
Iterating happens in the order of registration, so make sure to register more general types after specific ones.
If applicable, wrapper types such as collections or `Optional` are unwrapped.
Thus, a return type of `Optional<Person>` uses the `Person` type in the preceding process.
NOTE: Using a custom `RowMapper` through `QueryMappingConfiguration`, `@Query(rowMapperClass=…)`, or a custom `ResultSetExtractor` disables Entity Callbacks and Lifecycle Events as the result mapping can issue its own events/callbacks if needed.
[[jdbc.query-methods.at-query.modifying]]
=== Modifying Query
You can mark a query as being a modifying query by using the `@Modifying` on query method, as the following example shows:
[source,java]
----
@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);
----
You can specify the following return types:
* `void`
* `int` (updated record count)
* `boolean`(whether a record was updated)
Modifying queries are executed directly against the database.
No events or callbacks get called.
Therefore also fields with auditing annotations do not get updated if they don't get updated in the annotated query.