Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

What results are returned by Repository

We have already described the Repository interface, so let’s take a look at the returned results supported by these interfaces, as shown in the following figure:

SimpleJpaRepository implements methods and the methods and return types of its parent interface: Optional, Iterable, List, Page, Long, Boolean, Entity object, etc.

Since Repository supports Iterable, the Java standard List and Set can be returned as a result. Spring Data also supports subclasses of the List and Set. Spring Data defines a special subclass, Steamable. Streamable can replace Iterable or any collection type. It also provides a convenient way to access streams directly on elements… The filter (…). And… The map (…). Action, and connect the Streamable to other elements. Let’s look at an example where UserRepository directly inherits JpaRepository.

public interface UserRepository extends JpaRepository<User,Long> {

}
Copy the code

Using the previous UserRepository class, make the following call in the test class:

User user = userRepository.save(User.builder().name("jackxx").email("[email protected]").sex("man").address("shanghai").build()); Assert.assertNotNull(user); Streamable<User> userStreamable = UserRepository. The.findall (PageRequest) of (0, 10)). And (User. Builder (). The name (" jack222 "). The build ()); userStreamable.forEach(System.out::println);Copy the code

We should then get the following output:

User(id=1, name=jackxx, [email protected], sex=man, address=shanghai)

User(id=null, name=jack222, email=null, sex=null, address=null)
Copy the code

This example Streamable

userStreamable implements the Streamable return result. If you want to customize the method, you can do the following.

Custom Streamable

Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable = Streamable

Class Product {(1) MonetaryAmount getPrice() {... } } @RequiredArgConstructor(staticName = "of") class Products implements Streamable<Product> { (2) private Streamable<Product> streamable; public MonetaryAmount getTotal() { (3) return streamable.stream() // .map(Priced::getPrice) .reduce(Money.of(0), MonetaryAmount::add); } } interface ProductRepository implements Repository<Product, Long> { Products findAllByDescriptionContaining(String text); (4)}Copy the code

The above four steps describe the method of customizing Streamable as follows:

(1) Product entities, which expose apis to access Product prices.

(2) Streamable can be wrapped by Products. Of (…) Construct (factory method created through Lombok annotations).

(3) The wrapper type exposes other apis for calculating new values on Streamable .

(4) You can use the wrapper type directly as the query method return type. There is no need to return Stremable and manually wrap it in the repository Client.

In this example, you can customize Streamable by implementing the Streamable interface and defining your own implementation class. We can also see the source code inside the QueryExecutionResultHandler Streamable subclasses of judgment, to support custom Streamable, key source code is as follows:

To see why Streamable works in the source code, let’s take a look at the return implementation of a common collection class.

Return result type List/Stream/Page/Slice

In real development, how do we return List/Stream/Page/Slice?

First, create our UserRepository:

package com.example.jpa.example1; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.stream.Stream; Public interface UserRepository extends JpaRepository<User,Long> { And paging properties @ Query (" select u from User u ") Stream < User > findAllByCustomQueryAndStream (Pageable Pageable); / / test Slice return results @ Query (" select u from User u ") Slice < User > findAllByCustomQueryAndSlice (Pageable Pageable); }Copy the code

Then, modify our test case class as follows to verify the result:

package com.example.jpa.example1; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.assertj.core.util.Lists; import org.junit.Assert; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.util.Streamable; import java.util.List; import java.util.stream.Stream; @DataJpaTest public class UserRepositoryTest { @Autowired private UserRepository userRepository; @test public void testSaveUser() throws JsonProcessingException {// We add 7 new data to Test paging results userRepository.save(User.builder().name("jack1").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack2").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack3").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack4").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack5").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack6").email("[email protected]").sex("man").address("shanghai").build()); userRepository.save(User.builder().name("jack7").email("[email protected]").sex("man").address("shanghai").build()); Json to String ObjectMapper ObjectMapper = new ObjectMapper(); / / return results (1) the Stream Stream type < User > userStream = userRepository. FindAllByCustomQueryAndStream (PageRequest) of (1, 3)); userStream.forEach(System.out::println); Page<User> userPage = userrepository.findall (pagerequest.of (0,3)); System.out.println(objectMapper.writeValueAsString(userPage)); / / return (3) Slice Slice results < User > userSlice = userRepository. FindAllByCustomQueryAndSlice (PageRequest) of (0, 3)); System.out.println(objectMapper.writeValueAsString(userSlice)); / / return the List results (4) the List < User > userList. = userRepository findAllById (Lists. NewArrayList (1 l, 2 l)); System.out.println(objectMapper.writeValueAsString(userList)); }}Copy the code

Stream

= Stream

= Stream

= Stream



User(id=4, name=jack4, [email protected], sex=man, address=shanghai)

User(id=5, name=jack5, [email protected], sex=man, address=shanghai)

User(id=6, name=jack6, [email protected], sex=man, address=shanghai)

Spring Data support can step through the results of a query method by using Java 8 Stream as the return type. Try catch is a common method of closing streams, as shown below:

Stream<User> stream; Try {stream = repository. FindAllByCustomQueryAndStream () stream. The forEach (...). ; } catch (Exception e) { e.printStackTrace(); } finally { if (stream! =null){ stream.close(); }}Copy the code

Second: return to Page<User>The result of paging data is as follows:

{ "content":[ { "id":1, "name":"jack1", "email":"[email protected]", "sex":"man", "address":"shanghai" }, { "id":2, "name":"jack2", "email":"[email protected]", "sex":"man", "address":"shanghai" }, { "id":3, "name":"jack3", "email":"[email protected]", "sex":"man", "address":"shanghai" } ], "pageable":{ "sort":{ "sorted":false, "unsorted":true, "Empty ":true}, "pageNumber":0, current page "pageSize":3, offset":0, offset" paged":true, unpaged":false}, "TotalPages ":3 "totalElements":7" totalofElements ":3 "sort":{"sorted":false "Unsorted ":true, "empty":true}, "size":3, current content size" number":0, current page index "first":true, whether the first page "empty":false Whether there is data}Copy the code

Here we can see that Page

returns the data for the first Page and tells us that there are three parts of the data:

  • Content: The content of the data, now refers to the User’s List 3 items.
  • Pageable: Pageable data, including what sort field is and its direction, current page number, number of pages, whether it is the last one, etc.
  • Description of current data: “size” : 3, current content size; Number: 0, the index of the current page code. “First” : true, is the first page; Empty: false, whether there is no data.

Through these three parts of data we can know the paging information to look up the number. Let’s move on to the third test.

Third: Return Slice<User>The result is as follows:

{

   "content":[

      {

         "id":4,

         "name":"jack4",

         "email":"[email protected]",

         "sex":"man",

         "address":"shanghai"

      },

      {

         "id":5,

         "name":"jack5",

         "email":"[email protected]",

         "sex":"man",

         "address":"shanghai"

      },

      {

         "id":6,

         "name":"jack6",

         "email":"[email protected]",

         "sex":"man",

         "address":"shanghai"

      }

   ],

   "pageable":{

      "sort":{

         "sorted":false,

         "unsorted":true,

         "empty":true

      },

      "pageNumber":1,

      "pageSize":3,

      "offset":3,

      "paged":true,

      "unpaged":false

   },

   "numberOfElements":3,

   "sort":{

      "sorted":false,

      "unsorted":true,

      "empty":true

   },

   "size":3,

   "number":1,

   "first":false,

   "last":false,

   "empty":false

}
Copy the code

How many results and how many pages of data are there? Let’s compare the execution SQL of the second and third test results:

The second type executes plain paged query SQL:

Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.email as email3_0_, user0_.name as name4_0_, Hibernate: select count(user0_.id) as col_0_0_ from user user0_Copy the code

The third type of SQL executes as follows:

Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.email as email3_0_, user0_.name as name4_0_, user0_.sex as sex5_0_ from user user0_ limit ? offset ?
Copy the code

As you can see from the comparison, only offsets are queried, not paging data, which is the main difference between Page and Slice. Let’s move on to the fourth test.

Return List<User>The results are as follows:

[

   {

      "id":1,

      "name":"jack1",

      "email":"[email protected]",

      "sex":"man",

      "address":"shanghai"

   },

   {

      "id":2,

      "name":"jack2",

      "email":"[email protected]",

      "sex":"man",

      "address":"shanghai"

   }

]
Copy the code

At this point, we can easily query the data with ID=1 and ID=2 without paging information.

The above four methods introduce the common multiple data return form, I will not introduce a single article, believe you to understand, is nothing more than JDK8 Optional support. For example, the elegant determination of Null is supported, and the direct return of Entity, some existing/non-existing Boolean results and some count returns are supported.

How does the Repository approach support asynchrony?

The Repository for Feature/CompletableFuture asynchronous return results support:

We can execute a Repository query using Spring’s asynchronous method, which means that the method will return immediately upon invocation, and that the actual query execution will take place in a task submitted to Spring TaskExecutor, which is more appropriate for the real world of scheduled tasks. Async is easy to use, just add @async annotation as follows:

@Async Future<User> findByFirstname(String firstname); (1) @Async CompletableFuture<User> findOneByFirstname(String firstname); (2) @Async ListenableFuture<User> findOneByLastname(String lastname); (3)Copy the code

The results of the above three asynchronous methods are explained as follows:

  • The first place: using Java. Util. Concurrent. The return type of the Future;
  • The second: use java.util.concurrent.Com pletableFuture as the return type;
  • In the third place: use the org. Springframework. Util. Concurrent. ListenableFuture as the return type.

The above is the support for @async. The following three points should be paid attention to in practical use:

  • In practice, asynchronous methods are rarely used directly in the Repository layer. Asynchronous annotations are typically placed on top of Service methods to allow additional logic such as SMS, email, and messaging to be used.
  • Always configure a thread pool when using asynchrony, remember that otherwise it will die ugly.