Original title: Spring certification | Spring Data JPA reference document II (content source: Spring China Education Management Center)
4.4.6. Repository methods that return collections or iteratable objects
Query methods that return multiple results can use standard Java iteratable, List and Set. In addition, we support the strerable of Spring Data, the user-defined extension iteratable of and the collection type provided by Vavr. Please refer to the appendix explaining all possible query method return types.
Use Streamable as the return type of the query method
You can replace Iterable with Streamable of any collection type. It provides a convenient way to access non parallel streams (lacking from Iterable) and the ability to directly overwrite elements with.... filter(...) and.... map(...) and connect them to other elements:
Example 19. Using Streamable to combine query method results
interface PersonRepository extends Repository<Person, Long> { Streamable<Person> findByFirstnameContaining(String firstname); Streamable<Person> findByLastnameContaining(String lastname); } Streamable<Person> result = repository.findByFirstnameContaining("av") .and(repository.findByLastnameContaining("ea"));
Returns the custom stream wrapper type
Providing specialized wrapper types for collections is a common pattern used to provide API s for query results that return multiple elements. Typically, these types are used by calling repository methods to return class collection types and manually creating instances of wrapper types. You can avoid additional steps because Spring Data allows you to use these wrapper types to return as query methods Types, if they meet the following conditions:
- Type implements Streamable
- The type of exposes any construction or named static factory method of(...) or valueOf(...), which takes Streamable as the parameter.
The following listing shows an example:
class Product { MonetaryAmount getPrice() { ... } } @RequiredArgsConstructor(staticName = "of") class Products implements Streamable<Product> { private final Streamable<Product> streamable; public MonetaryAmount getTotal() { return streamable.stream() .map(Priced::getPrice) .reduce(Money.of(0), MonetaryAmount::add); } @Override public Iterator<Product> iterator() { return streamable.iterator(); } } interface ProductRepository implements Repository<Product, Long> { Products findAllByDescriptionContaining(String text); }
A Product exposed API to access the price entity of the Product.
A wrapper type that can be constructed by using Products.of(...) (factory method created with Lombok annotation). It is also possible to use the standard constructor of streamable < Product > will.
The wrapper type exposes an additional API in streamable < Product >
Implement the Streamable interface and delegate to the actual results.
The wrapper type Products can be used directly as the return type of the query method. You do not need to return and wrap it manually after a query in the repository client.
Support Vavr set
Vavr is a library containing Java functional programming concepts. It comes with a set of custom collection types that you can use as the return type of query methods, as shown in the following table:
You can use the type in the first column (or its subtype) as the return type of the query method, and obtain the type in the second column as the implementation type according to the Java type of the actual query result (the third column). Alternatively, you can declare Traversable (iteratable is equivalent to Vavr) Then we derive the implementation class from the actual return value. That is, ajava.util.List becomes VavrList or Seq, ajava.util.Set becomes Vavr linkedhashset, and so on.
4.4.7. Empty processing of repository method
Starting with Spring Data 2.0, the repository CRUD method that returns a single aggregate instance uses Java 8option to indicate that a value may be missing. In addition, Spring Data supports the following wrapper types on query methods:
- com.google.common.base.Optional
- scala.Option
- io.vavr.control.Option
Alternatively, the query method can choose not to use the wrapper type at all. Then, it indicates that the query result null does not exist by returning. The repository method that returns collections, collection alternatives, wrappers and streams ensures that it will never return null, but the corresponding empty representation. For more information, see "repository query return type".
Nullability note
You can use the nullability annotations of the Spring Framework to express the nullability constraints of repository methods. They provide a tool friendly method and optional join check at runtime, as shown below:
- @NonNullApi: at the package level, the default behavior for declaring parameters and return values is neither accepting nor generating null values, respectively.
- @NonNull: used for parameters or return values that must not be null (not required on parameters and return values to which @ NonNullApi applies).
- @Nullable: used for parameters or return values that can be null.
Spring annotations use JSR 305 annotations (a dormant but widely used JSR) for meta annotation. JSR 305 meta annotations allow tool vendors (such as IDEA, Eclipse, and Kotlin) Provide null security support in a common way without hard coding spring annotations. To enable runtime checking of null constraints for query methods, you need to activate non nullability package-info.java at the package level using spring's @ NonNullApiin, as shown in the following example:
Example 20. In package-info.java
@org.springframework.lang.NonNullApi package com.acme;
Once the non null default is set in place, the repository query method call will be validated as an nullability constraint at run time. If the query result violates the defined constraint, an exception will be thrown. When the method will return null but is declared non Nullable (the default comment defined on the package where the repository is located) , this happens. If you want to select Nullable results again, use the @ Nullable single method selectively. Continue to work as expected using the result wrapper type mentioned at the beginning of this section: empty results are converted to values that do not exist.
The following example shows many of the techniques just described:
Example 21. Using different nullability constraints
package com.acme; import org.springframework.lang.Nullable; interface UserRepository extends Repository<User, Long> { User getByEmailAddress(EmailAddress emailAddress); @Nullable User findByEmailAddress(@Nullable EmailAddress emailAdress); Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); }
The repository resides in the package (or sub package) where we have defined non empty behavior.
EmptyResultDataAccessException is thrown when the query does not produce a result. IllegalArgumentException is thrown null when emailAddress is passed to method yes.
null returns when the query does not produce a result. null is also accepted as the value of emailAddress.
Optional.empty() returns when the query does not produce a result. IllegalArgumentException throws null when emailAddress is passed to the method.
Nullability in Kotlin based repository
Kotlin defines nullability constraints in the language. Kotlin code is compiled as bytecode, which expresses nullability constraints not by method signature, but by compiling metadata. Ensure that kotlin reflect includes jars in your project to enable introspection of kotlin's nullability constraints. The Spring Data repository uses language mechanisms to define these constraints to apply the same run-time checks , as follows:
Example 22. Using nullability constraints on the Kotlin repository
interface UserRepository : Repository<User, String> { fun findByUsername(username: String): User fun findByFirstname(firstname: String?): User? }
This method defines both parameters and results as non nullable (Kotlin default). The Kotlin compiler rejects method calls that pass null to the method. If the query produces null results,
EmptyResultDataAccessException throws an.
This method accepts the null firstname parameter and returns NULL if the query does not produce a result.
4.4.8. Streaming query results
You can use Java 8Stream < T > as the return type to incrementally process the results of the query method. Instead of wrapping the query results in a Stream, the data store specific method is used to perform streaming transmission, as shown in the following example:
Example 23. Streaming query results using Java 8 stream < T >
@Query("select u from User u") Stream<User> findAllByCustomQueryAndStream(); Stream<User> readAllByFirstnameNotNull(); @Query("select u from User u") Stream<User> streamAllPaged(Pageable pageable);
AStream potentially wraps the specific resources of the underlying data store, so it must be closed after use. You can manually close the Stream using the close() method or using the Java 7try with resources block, as shown in the following example:
Example 24. Stream < T > process results in the try with resources block
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) { stream.forEach(...); }
Not all Spring Data modules currently support stream < T > as a return type.
4.4.9. Asynchronous query results
You can run repository queries asynchronously using Spring's asynchronous method capability. This means that the method returns immediately when called, and the actual query occurs in the TaskExecutor of the task submitted to Spring. Asynchronous queries are different from reactive queries and should not be mixed. For more details on reactive support, refer to the store specific documentation. The following example shows some asynchronous queries:
@Async Future<User> findByFirstname(String firstname); @Async CompletableFuture<User> findOneByFirstname(String firstname); @Async ListenableFuture<User> findOneByLastname(String lastname);
use
java.util.concurrent.Future as the return type.
Using Java
8java.util.concurrent.completable future as the return type.
use
aorg.springframework.util.concurrent.ListenableFuture as the return type.
4.5. Create repository instance
This section describes how to create instances and bean definitions for defined repository interfaces. One way is to use the Spring namespace that comes with each Spring Data module that supports the repository mechanism, although we usually recommend using Java configuration.
4.5.1.XML configuration
Each Spring Data module contains a repository element that allows you to define the basic packages that Spring scans for you, as shown in the following example:
Example 25. Enabling the Spring Data repository through XML
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <repositories base-package="com.acme.repositories" /> </beans:beans>
In the previous example, Spring was instructed to scan com.acme.repositories and all its sub packages to find the interface that extends the Repository or one of its sub interfaces. For each interface found, the infrastructure registers a specific FactoryBean with persistence technology to create an appropriate proxy to handle query method calls. Each bean is registered under the bean name derived from the interface name, so the interface userrepository will register userrepository under. The bean name of the nested Repository interface is prefixed with its enclosing type name. The base package attribute allows wildcards so that you can define the mode of scanning packages.
Use filter
By default, the infrastructure selects each interface that extends the Persistence technology specific sub interface of the Repository under the configured base package and creates a bean instance for it. However, you may want to have more granular control over which interfaces create bean instances for them. To do this, use the < include filter / > and < exclude filter / > elements < repositories / > within the element. The semantics are exactly equivalent to the elements in the Spring context namespace. For details, see the Spring reference documentation for these elements.
For example, to exclude some interfaces from instantiation as repository bean s, you can use the following configuration:
Example 26. Use the exclude filter element
<repositories base-package="com.acme.repositories"> <context:exclude-filter type="regex" expression=".*SomeRepository" /> </repositories>
The previous example excludes all interfaces that end with SomeRepository instantiation.
4.5.2.Java configuration
You can also trigger the repository infrastructure by using store specific annotations on Java configuration classes through @ Enable${store}Repositories. For an introduction to the Java based configuration of the Spring container, see JavaConfig in the Spring reference documentation.
The sample configuration for enabling the Spring Data repository is similar to the following:
Example 27. Annotation based repository configuration example
@Configuration @EnableJpaRepositories("com.acme.repositories") class ApplicationConfiguration { @Bean EntityManagerFactory entityManagerFactory() { // ... } }
The previous example uses a JPA specific annotation that you can change depending on the store module you are actually using. The same applies to the definition of the entitymanagerfactory bean. See the section covering store specific configurations.
4.5.3. Independent use
You can also use the repository infrastructure outside the Spring container -- for example, in a CDI environment. You still need some Spring libraries in your classpath, but in general, you can also set up the repository programmatically. The Spring Data module that provides repository support comes with the repository factory, and the Persistence technology specific technologies you can use are as follows:
Example 28. Independent use of repository factories
RepositoryFactorySupport factory = ... // Instantiate factory here UserRepository repository = factory.getRepository(UserRepository.class);
4.6. Custom implementation of spring data repositories
Spring Data provides various options to create query methods that require little coding. However, when these options do not meet your needs, you can also provide your own custom implementation for repository methods. This section describes how to do this.
4.6.1. Customize a single repository
To enrich the repository with custom functions, you must first define the fragment interface and the implementation of custom functions, as shown below:
Example 29. Interface for customizing repository functions
interface CustomizedUserRepository { void someCustomMethod(User user); }
Example 30. Implementation of custom Repository Function
class CustomizedUserRepositoryImpl implements CustomizedUserRepository { public void someCustomMethod(User user) { // Your custom implementation } }
The most important part of the class name corresponding to the fragment interface is the Impl suffix.
The implementation itself does not depend on Spring Data, but can be an ordinary Spring bean. Therefore, you can use the standard dependency injection behavior to inject references to other beans (such as a JdbcTemplate), participation aspects, etc.
Then you can make your repository interface extend the fragment interface, as follows:
Example 31. Changes to the repository interface
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository { // Declare query methods here }
Use your repository interface to extend the fragment interface, which combines CRUD and customization capabilities and makes it available to clients.
The Spring Data repository is implemented by using fragments that form a combination of repositories. Fragments are the underlying repository, functional aspects (such as QueryDsl), and custom interfaces and their implementations. Each time you add an interface to the repository interface, you can enhance the composition by adding fragments. Each Spring Data module provides an implementation of the basic repository and repository aspects.
The following example shows a custom interface and its implementation:
Example 32. Fragment and its implementation
interface HumanRepository { void someHumanMethod(User user); } class HumanRepositoryImpl implements HumanRepository { public void someHumanMethod(User user) { // Your custom implementation } } interface ContactRepository { void someContactMethod(User user); User anotherContactMethod(User user); } class ContactRepositoryImpl implements ContactRepository { public void someContactMethod(User user) { // Your custom implementation } public User anotherContactMethod(User user) { // Your custom implementation } }
The following example shows the interface of the extended custom repository, CrudRepository:
Example 33. Changes to the repository interface
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository { // Declare query methods here }
The repository may consist of multiple custom implementations imported in declarative order. Custom implementations take precedence over basic implementations and repositories. If two fragments contribute the same method signature, this sort allows you to override the basic repository and aspect methods and resolve ambiguities. Repository fragments are not limited to use in a single repository interface. Multiple repositories can use fragment interfaces, allowing you to reuse customizations in different repositories.
The following example shows a repository fragment and its implementation:
Example 34. Fragment overwrite save(...)
interface CustomizedSave<T> { <S extends T> S save(S entity); } class CustomizedSaveImpl<T> implements CustomizedSave<T> { public <S extends T> S save(S entity) { // Your custom implementation } }
The following example shows a repository that uses the above repository fragment:
Example 35. Custom repository interface
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> { } interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> { }
to configure
If you use namespace configuration, the repository infrastructure attempts to automatically detect custom implementation fragments by scanning the classes under the package in which it finds the repository. These classes need to follow the naming convention of attaching the repository Impl postfix attribute of the namespace element to the fragment interface name. This suffix defaults to Impl. The following example shows a repository that uses the default suffix and a repository that sets custom values for the suffix:
Example 36. Configuration example
<repositories base-package="com.acme.repository" /> <repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
The first configuration in the previous example tries to find a configuration called
Com.acme.repository.customeduserrepositoryimpl is the class implemented by the custom repository. The second example tries to find com. Acme. Repository. Customizeduserrepository mypostfix
Resolve ambiguity
If multiple implementations with matching class names are found in different packages, Spring Data uses bean names to identify which to use.
Given the following two custom implementations shown earlier in the CustomizedUserRepository, use the first. Its bean name is
Customized user repository Impl, which matches the name of the fragment interface (customized user repository) with the suffix Impl.
Example 37. Resolving ambiguous implementations
package com.acme.impl.one; class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation }
package com.acme.impl.two; @Component("specialCustomImpl") class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation }
If you use the annotation UserRepository interface @ Component("specialCustom"), the bean name plus sign Impl matches the name defined for the repository implementation in com.acme.impl.two and uses it instead of the first name.
Content tips: This article (Spring Data JPA reference document) is incomplete and will be continued