Spring Data JPA教程, 第六部分: Sorting(未翻译)

The fifth part of my Spring Data JPA tutorialdescribed how you can create advanced queries with Spring Data JPA and Querydsl. This blog entry will describe how you can use Spring Data JPA for sorting the query results. As an example I will be adding two new requirements for my example application:

  • The list of persons shown in the front page should be sorted in ascending order by using the last name of a person.
  • The search results should be sorted in ascending order by using the last name of a person.

Spring Data JPA offers four different ways to sort the query results. Each of these approaches is described with more details in the following Sections.

Sorting with Method Name

If you are building your queries by using the query generation from method name strategy, you can sort the query results by using the OrderBy keyword in the name of your query method. If you want to search for persons whose last name matches with the given parameter and sort the query results, you can use the following query methods:

//Results are sorted in ascending order
public  List <Person >  findByLastNameOrderByLastNameAsc ( String  lastName ) ;

//Results are sorted in descending order
public  List <Person >  findByLastNameOrderByLastNameDesc ( String  lastName )

Sorting with JPQL

If you are creating query method by using either @NamedQuery or @Query annotation as explained in the third part of this tutorial, you can sort the query results by using the ORDER BYkeyword of the JPQL. For example, if you want to get all persons and sort the query results in ascending order by using the last name of a person, you can use the following query:

SELECT  p   FROM  Person p   ORDER   BY  p .lastName   ASC

You can now consult the third part of this tutorial for finding out how you can use the@NamedQuery or @Query annotation to implement the actual query method.

Sorting with Sort Class

The second way to sort query results with Spring Data JPA is to use theorg.springframework.data.domain.Sort class. You can use this approach for sorting the query results by following these steps:

  • Create an instance of the Sort class.
  • Pass the created instance as a parameter to the correct method.

I am going to describe to you three different ways for sorting the query results by using the Sort class. After that I will choose one of those methods and implement my first requirement with the selected method.

Query Generation by Method Name

You can pass the Sort object as a parameter to a query method, which uses the query generation by method name strategy. For instance, if you want search persons by last name and sort the results of the created query, you should add following method to your repository interface:

public  List <Person >  findByLastName ( String  lastName, Sort sort ) ;

If you are interested of finding out more information about this query generation strategy, you should read the third part of this tutorial.

JpaRepository

As you might remember from the previous parts of this tutorial, Spring Data JPA repositories are just interfaces which extend other interfaces. One of those special interfaces is theJpaRepository<T, ID extends Serializable> interface. This interface declares the public List<T> findAll(Sort sort) method, which returns a list of all entities and sorts the entities in the order specified by the Sort object given as a parameter. If you are interested of obtaining a sorted list of all entities, using this method is the best way to do it.

The second part of my Spring Data JPA tutorial provides more information about the usage of the JpaRepository interface.

JPA Criteria API

If you are creating your queries with the JPA criteria API, your repository interface must extend the JpaSpecificationExecutor<T> interface. This interface declares the public List<T> findAll(Specification<T> spec, Sort sort) method, which returns a list of entities matching with the Specification given as a parameter. The returned entities are sorted in the order specified by the Sort object given as a parameter.

So, if you are interested of sorting the results of a query constructed by using the JPA criteria API, you can do it by using the described method. If you need more information about Spring Data JPA and the JPA criteria API, you can read the fourth part of this tutorial.

Example

I am going to describe next how you can implement the first requirement by using thefindAll(Sort sort) method provided by the JpaRepository interface.

First, my repository interface must extend the JpaRepository interface. The source code of thePersonRepository is given in following:

import   org.springframework.data.jpa.repository.JpaRepository ;

public   interface  PersonRepository   extends  JpaRepository <Person, Long >   {

}

Second, I created a private method called sortByLastNameAsc() to RepositoryPersonServiceclass. This method is used in the findAll() method to obtain an instance of the Sort class. Third, I passed the obtained Sort object to the findAll() method of the JpaRepository interface. The source code of the relevant parts of the RepositoryPersonService is given in following:

import   org.slf4j.Logger ;
import   org.slf4j.LoggerFactory ;
import   org.springframework.data.domain.Sort ;
import   org.springframework.stereotype.Service ;
import   org.springframework.transaction.annotation.Transactional ;

import   javax.annotation.Resource ;

@Service
public   class  RepositoryPersonService   implements  PersonService   {
     
      private   static   final  Logger LOGGER   =  LoggerFactory. getLogger (RepositoryPersonService. class ) ;
     
    @Resource
      private  PersonRepository personRepository ;

    @Transactional (readOnly   =   true )
    @Override
      public  List <Person >  findAll ( )   {
        LOGGER. debug ( "Finding all persons" ) ;
          //Passes the Sort object to the repository.
          return  personRepository. findAll (sortByLastNameAsc ( ) ) ;
      }

      /**
     * Returns a Sort object which sorts persons in ascending order by using the last name.
     * @return
     */

      private  Sort sortByLastNameAsc ( )   {
          return   new  Sort (Sort. Direction. ASC,   "lastName" ) ;
      }
}

The last step was to fix the unit test of the findAll() method. The source code of the fixed unit test is given in following:

import   org.junit.Before ;
import   org.junit.Test ;
import   org.mockito.ArgumentCaptor ;
import   org.springframework.data.domain.Sort ;

import   static  junit. framework. Assert. assertEquals ;
import   static  org. mockito. Mockito. *;

public   class  RepositoryPersonServiceTest   {
     
      private  RepositoryPersonService personService ;

      private  PersonRepository personRepositoryMock ;

    @Before
      public   void  setUp ( )   {
        personService   =   new  RepositoryPersonService ( ) ;

        personRepositoryMock   =  mock (PersonRepository. class ) ;
        personService. setPersonRepository (personRepositoryMock ) ;
      }

    @Test
      public   void  findAll ( )   {
        List <Person >  persons   =   new  ArrayList <Person > ( ) ;
        when (personRepositoryMock. findAll (any (Sort. class ) ) ). thenReturn (persons ) ;
         
        List <Person >  returned   =  personService. findAll ( ) ;

        ArgumentCaptor <Sort >  sortArgument   =  ArgumentCaptor. forClass (Sort. class ) ;
        verify (personRepositoryMock, times ( 1 ) ). findAll (sortArgument. capture ( ) ) ;

        verifyNoMoreInteractions (personRepositoryMock ) ;

        Sort actualSort   =  sortArgument. getValue ( ) ;
        assertEquals (Sort. Direction. ASC, actualSort. getOrderFor ( "lastName" ). getDirection ( ) ) ;

        assertEquals (persons, returned ) ;
      }
}

I have now demonstrated to you, how you can sort the persons presented in the front page of my example application in ascending order by using the last name of a person.

Sorting with Querydsl

If you are using Querydsl for building your queries, you can sort the query results by following these steps:

I will now describe to you how you can implement my second requirement with Querydsl.

First, I am ensuring that my repository extends the QueryDslPredicateExecutor interface. The source code of the PersonRepository is given in following:

import   org.springframework.data.jpa.repository.JpaRepository ;
import   org.springframework.data.querydsl.QueryDslPredicateExecutor ;

public   interface  PersonRepository   extends  JpaRepository <Person, Long >, QueryDslPredicateExecutor <Person >   {

}

Second, I created a class called PersonPredicates which has one method: lastNameIsLike(). This method is used to build the required query predicate. The source code of this class is given in following:

import   com.mysema.query.types.Predicate ;
import   net.petrikainulainen.spring.datajpa.model.QPerson ;

public   class  PersonPredicates   {

      public   static  Predicate lastNameIsLike ( final   String  searchTerm )   {
        QPerson person   =  QPerson. person ;
          return  person. lastName. startsWithIgnoreCase (searchTerm ) ;
      }
}

The next step was to made some changes to the search() method of the RepositoryPersonServiceclass. This method is explained with more details in following:

  • The predicate is still obtained by calling the lastNameIsLike() method of the PersonPredicatesclass.
  • The OrderSpecifier instance is obtained by calling a private orderByLastNameAsc() method ofRepositoryPersonService class.
  • The predicate and the OrderSpecifier instance are passed as a parameter to thefindAll(Predicate predicate, OrderSpecifier order) method of my repository implementation.

The source code of the relevant parts of the RepositoryPersonService class is given in following:

import   com.mysema.query.types.OrderSpecifier ;
import   net.petrikainulainen.spring.datajpa.model.QPerson ;
import   org.slf4j.Logger ;
import   org.slf4j.LoggerFactory ;
import   org.springframework.stereotype.Service ;
import   org.springframework.transaction.annotation.Transactional ;

import   javax.annotation.Resource ;

@Service
public   class  RepositoryPersonService   implements  PersonService   {
     
      private   static   final  Logger LOGGER   =  LoggerFactory. getLogger (RepositoryPersonService. class ) ;
     
    @Resource
      private  PersonRepository personRepository ;
     
    @Transactional (readOnly   =   true )
    @Override
      public  List <Person >  search ( String  searchTerm )   {
        LOGGER. debug ( "Searching persons with search term: "   +  searchTerm ) ;

          //Passes the specification created by PersonPredicates class and the OrderSpecifier object to the repository.
        Iterable <Person >  persons   =  personRepository. findAll (lastNameIsLike (searchTerm ), orderByLastNameAsc ( ) ) ;

          return  constructList (persons ) ;
      }

      /**
     * Returns an OrderSpecifier object which sorts person in ascending order by using the last name.
     * @return
     */

      private  OrderSpecifier <String >  orderByLastNameAsc ( )   {
          return  QPerson. person. lastName. asc ( ) ;
      }

      private  List <Person >  constructList (Iterable <Person >  persons )   {
        List <Person >  list   =   new  ArrayList <Person > ( ) ;
          for   (Person person :  persons )   {
            list. add (person ) ;
          }
          return  list ;
      }
}

The last step was to the fix the unit test of the search() method. The source code of the fixed unit test is given in following:

import   com.mysema.query.types.Order ;
import   com.mysema.query.types.OrderSpecifier ;
import   com.mysema.query.types.expr.BooleanExpression ;
import   net.petrikainulainen.spring.datajpa.model.QPerson ;
import   org.junit.Before ;
import   org.junit.Test ;
import   org.mockito.ArgumentCaptor ;

import   static  junit. framework. Assert. assertEquals ;
import   static  org. mockito. Mockito. *;

public   class  RepositoryPersonServiceTest   {
      private   static   final   String  SEARCH_TERM   =   "foo" ;     

      private  RepositoryPersonService personService ;

      private  PersonRepository personRepositoryMock ;

    @Before
      public   void  setUp ( )   {
        personService   =   new  RepositoryPersonService ( ) ;

        personRepositoryMock   =  mock (PersonRepository. class ) ;
        personService. setPersonRepository (personRepositoryMock ) ;
      }

    @Test
      public   void  search ( )   {
        List <Person >  expected   =   new  ArrayList <Person > ( ) ;
        when (personRepositoryMock. findAll (any (BooleanExpression. class ), any (OrderSpecifier. class ) ) ). thenReturn (expected ) ;
         
        List <Person >  actual   =  personService. search (SEARCH_TERM ) ;

        ArgumentCaptor <OrderSpecifier >  orderArgument   =  ArgumentCaptor. forClass (OrderSpecifier. class ) ;
        verify (personRepositoryMock, times ( 1 ) ). findAll (any (BooleanExpression. class ), orderArgument. capture ( ) ) ;

        verifyNoMoreInteractions (personRepositoryMock ) ;

        OrderSpecifier actualOrder   =  orderArgument. getValue ( ) ;
        assertEquals (Order. ASC, actualOrder. getOrder ( ) ) ;
        assertEquals (QPerson. person. lastName, actualOrder. getTarget ( ) ) ;

        assertEquals (expected, actual ) ;
      }
}

I have now demonstrated to you how you can you use Querydsl to sort the person search results in ascending order by using the last name of a person.

What is Next?

I have now described to you three different ways to sort the query results with Spring Data JPA. I have also implemented a fully functional example application, which is available at Github. The next part of my tutorial describes how you can implement pagination with Spring Data JPA.

你可能感兴趣的:(spring)