君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

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.

posted on 2013-06-19 11:52  刺猬的温驯  阅读(5624)  评论(0编辑  收藏  举报