You may create an interface that
extends Repository directly, but because it specifies no methods, you will probably never do this. A more useful approach is to extend
org.springframework.data.repository.CrudRepository<T, ID>, which specifies numerous methods for basic CRUD operations. This interface uses a few conventions:
-
count() returns a long representing the total number of unfiltered entities extending
T.
-
delete(T) and
delete(ID) delete the single, specified entity, whereas
delete(Iterable<? extends T>) deletes multiple entities and
deleteAll() deletes every entity of that type.
-
exists(ID) returns a boolean indicating whether the entity of this type with the given surrogate key exists.
-
findAll() returns all entities of type
T, whereas
findAll(Iterable<ID>) returns the entities of type
T with the given surrogate keys. Both return
Iterable<T>.
-
findOne(ID) retrieves a single entity of type
T given its surrogate key.
-
save(S) saves the given entity (insert or update) of type
S where
S extends T, and returns
S, the saved entity.
-
save(Iterable<S>) saves all the entities (again,
S extends T) and returns the saved entities as a new
Iterable<S>.
All Spring Data projects already know how to implement all these methods for a given type. You’ll notice, however, that this repository still doesn’t specify methods that support paging and sorting.
This is so that these methods don’t clutter any repositories that you don’t want to support paging and sorting. If you want a repository to provide paging and sorting methods, its interface can extend
org.springframework.data.repository.PagingAndSortingRepository<T, ID extends Serializable>:
-
findAll(Sort) returns all
T entities as an
Iterable<T> sorted with the provided
Sort instructions.
-
findAll(Pageable) returns a single
org.springframework.data.domain.Page<T> of entities sorted and bounded with the provided
Pageable instructions.
An
org.springframework.data.domain.Sort object encapsulates information about the properties that should be used to sort a result set and in what direction they should be sorted.
An
org.springframework.data.domain.Pageable encapsulates a Sort as well as the number of entities per page and which page to return (both ints). In a web application, you don’t usually have to worry about creating Pageable objects on your own. Spring Data provides two
org.springframework.web.method.support.HandlerMethodArgumentResolver implementations that can turn HTTP request parameters into
Pageable and
Sort objects, respectively:
org.springframework.data.web.PageableHandlerMethodArgumentResolver and
org.springframework.data.web.SortHandlerMethodArgumentResolver.
All these predefined methods are helpful, and the standardized
Sort and
Pageable objects definitely come in handy, but you still have no way to find specific entities or lists of entities using anything other than surrogate keys, at least, not without creating your own method implementations. This is where Spring Data’s query methods come in to play.
Creating Query Methods for Finding Entities
Query methods are specially defined methods that tell Spring Data how to find entities. The name of a query method starts with find...By, get...By, or read...By and is followed by the names of properties that should be matched on. The method parameters provide the values that should match the properties specified in the method name (in the same order the properties are listed in the method name if the values are of the same type). The method return type tells Spring Data whether to expect a single result (
T) or multiple results (
Iterable<T>,
List<T>,
Collection<T>,
Page<T>, and so on). So, for example, in a
BookRepository you might need to locate a book by its
ISBN,
author, or
publisher:
public interface BookRepository extends PagingAndSortingRepository<Book, Long>
{
Book findByIsbn(String isbn);
List<Book> findByAuthor(String author);
List<Book> findByPublisher(String publisher);
}
The algorithm that analyzes these methods knows that
findByIsbn should match the
Book’s
isbn property to the method parameter and that the result should be unique. Likewise, it knows that
findByAuthor and
findByPublisher should match multiple records using the
author and
publisher Book properties, respectively. Notice that the property names referenced in the repository method names match the JPA property names of the
Book entity, this is the convention that you must follow. In most cases, this is also the JavaBean property names. Of course, an
author can write many books and a
publisher most certainly publishes many, so you probably need your query methods to support pagination.
public interface BookRepository extends PagingAndSortingRepository<Book, Long>
{
Book findByIsbn(String isbn);
Page<Book> findByAuthor(String author, Pageable instructions);
Page<Book> findByPublisher(String publisher, Pageable instructions);
}
You can put multiple properties in your query method name and separate those properties with logical operators as well:
List<Person> findByFirstNameAndLastName(String firstName, String lastName);
List<Person> findByFirstNameOrLastName (String firstName, String lastName);
Many databases ignore case when matching string-based fields (either by default or as an optional configuration), but you can explicitly indicate that case should be ignored using IgnoreCase:
Page<Person> findByFirstNameOrLastNameIgnoreCase(String firstName, String lastName, Pageable instructions);
In the preceding example, only the last name ignores case. You can also ignore case on the first name using the method name
findByFirstNameIgnoreCaseOrLastNameIgnoreCase, but that is very verbose. Instead, you can tell Spring Data to ignore the case for all String properties using
findByFirstNameOrLastNameAllIgnoreCase.
Sometimes properties are not simple types. For example, a
Person might have an
address property of type
Address. Spring Data can also match against this property if the parameter type is
Address, but often you don’t want to match on the whole
address. You may want to return a list of people in a certain postal code, for example. This is easily accomplished using Spring Data property expressions.
List<Person> findByAddressPostalCode(PostalCode code);
Assuming
Person has an
address property and that property’s type has a
postalCode property of type
PostalCode, Spring Data can find the people in the database with the given postal code. However, property expressions can create ambiguity in the matching algorithm. Spring Data greedily matches the property name before looking for a property expression, not unlike a regular expression might greedily match an “or more” control character. The algorithm could match on a different property name than you intended, and then fail to find a property within that property’s type matching the property expression. For this reason, it’s best to always separate property expressions using an underscore:
Page<Person> findByAddress_PostalCode(PostalCode code, Pageable instructions);
This removes the ambiguity so that Spring Data matches on the correct property.
You undoubtedly remember that method names begin with find...By, get...By, or read...By. These are introducing clauses, and By is a delimiter separating the introducing clause and the criteria to match on. To a large extent, you can place whatever you want to between find, get, or read and By. For example, to be more “plain language,” you could name a method
findBookByIsbn or
findPeopleByFirstNameAndLastName.
Book and
People are ignored in this case. However, if the word Distinct (matching that case) is in the introducing clause (such as
findDistinctBooksByAuthor), this triggers the special behavior of enabling the distinct flag on the underlying query. This may or may not apply to the storage medium in use, but for JPA or JdbcTemplate repositories, it’s the equivalent of using the DISTINCT keyword in the JPQL or SQL query.
In addition to the Or and And keywords that separate multiple criteria, the criteria in a query method name can contain many other keywords to refine the way the criteria match:
-
Is and
Equals are implied in the absence of other keywords, but you may explicitly use them. For example,
findByIsbn is equivalent to
findByIsbnIs and
findByIsbnEquals.
-
Not and
IsNot negate any other keyword except for Or and And. Is and Equals are still implied in the absence of other keywords, so
findByIsbnIsNot is equivalent to
findByIsbnIsNotEqual.
-
After and
IsAfter indicate that the property is a date and/or time that should come after the given value, whereas
Before and
IsBefore indicate that the property should come before the given value. Example:
findByDateFoundedIsAfter(Date date).
-
Containing,
IsContaining, and
Contains indicate that the property’s value may start and end with anything but should contain the given value. This is similar to
StartingWith,
IsStartingWith, and
StartsWith, which indicate that the property should start with the specified value. Likewise,
EndingWith,
IsEndingWith, and
EndsWith indicate that the property should end with the specified value. Example:
findByTitleContains(String value) is equivalent to the SQL criteria
WHERE title = '%value%'.
-
Like is similar to Contains, StartsWith, and EndsWith, except that value you provide should already contain the appropriate wildcards (instead of Spring Data adding them for you). This gives you the flexibility to specify more advanced patterns.
NotLike simply negates Like. Example:
findByTitleLike(String value) could be called with value
"%Catcher%Rye%" and would match
“The Catcher in the Rye” and
“Catcher Brings Home Rye Bread.”
-
Between and
IsBetween indicate that the property should be between the two specified values. This means that you must provide two parameters for this property criterion. You can use Between on any type that may be compared mathematically in this manner, such as numeric and date types. Example:
findByDateFoundedBetween(Date start, Date end).
-
Exists indicates that something should exist. Its meaning may vary wildly between storage mediums. It is roughly equivalent to the EXISTS keyword in JPQL and SQL.
-
True and
IsTrue indicate that the named property should be true, whereas
False and
IsFalse indicate that the named property should be false. These keywords do not require method parameters because the value is implied by the keywords themselves. Example:
findByApprovedIsFalse().
-
GreaterThan and
IsGreaterThan indicate that the property is greater than the parameter value. You can include the parameter value in the bounds with
GreaterThanEqual or
IsGreaterThanEqual. The inverse of these keywords are
LessThan,
IsLessThan,
LessThanEqual, and
IsLessThanEqual.
-
In indicates that the property value must be equal to at least one of the values specified. The parameter matching this criterion should be an
Iterable of the same type of the property. Example:
findByAuthorIn(Iterable<String> authors).
-
Null and
IsNull indicate that the value should be null. These keywords also do not require method parameters because the value is implied.
-
Near,
IsNear,
Within, and
IsWithin are keywords useful for certain NoSQL database types but have no useful meaning in JPA.
-
Regex,
MatchesRegex, and
Matches indicate that the property value should match the String regular expression (do not use Pattern) specified in the corresponding method parameter.