Spring Data Solr Tutorial: Sorting

When we are implementing a word search function, we typically want to sort the search results in descending order by using the relevancy of each search result. This is also the default behaviour of Solr.

However, there are situations when it makes to sense to specify the sort order manually. One such situation is an implementation of a "regular" search function which was discussed in the previous part of my Spring Data Solr tutorial.

This blog post describes how we can sort our query results with Spring Data Solr. To be more specific, we have to modify the search function of our example application to sort the search results in descending order by using the value of the id field.

This blog post is divided into three sections:

  • The first section describes how we can specify the sorting options used in our queries.
  • The second section describes how we can sort our query results when we are building our queries by using query methods.
  • The third section teaches us to sort the query results of dynamic queries.

Let's get started.

Specifying the Sort Options of a Query

The sort options of a query are specified by using the Sort class. The typical requirements for sorting query results are given in the following:

  • Sort the query results by using the value of a single field.
  • Sort the query results by using the values of multiple fields when the sort order of different fields is the same.
  • Sort the query results by using the values of multiple fields when sort order of different fields is not the same.

Let’s take a look how we can create a Sort object which fulfills the given requirements.

First, we must create a Sort object which specifies that query results are sorted by using a single field. Lets assume that we want to sort the query results in ascending order by using the id field. We can create the Sort object by using the following code:

new Sort(Sort.Direction.ASC, "id")

Second, we must create a Sort object which states that query results are sorted by using the values of multiple fields when the sort order of different fields is the same. Lets assume that we have to sort the query results in descending order by using the id and description fields. We can create the Sort object by using the following code:

new Sort(Sort.Direction.DESC, "id", "description")

Third, we want to sort the query results by using the values of multiple fields when the sort order of different fields is not the same. Lets assume that we want to sort the query results in descending order by using the description field and in ascending order by using the id field. We can create the Sort object by using the following code:

new Sort(Sort.Direction.DESC, "description").and(new Sort(Sort.Direction.ASC, "id"))

We now know how we can create new Sort objects. Let's move on and put this theory into practice.

Sorting the Query Results of Query Methods

When we are building our queries by using query methods, we can sort the query results by following these steps:

  1. Add a new Sort parameter to the query method. This method parameter specifies the used sort options.
  2. Create a new Sort object in the service layer and pass it as a method parameter when the query method is called.

Let's move on and find out how this is done.

Modifying the Repository Interface

We can sort the query results of our query by adding a new Sort parameter to the our query method. This method parameter specifies the sort options of the executed query. Let's move on and take a look at the declarations of our query methods.

Query Generation From Method Name

When the executed query is created by using the query generation from method name strategy, we have to add a Sort parameter to the findByTitleContainsOrDescriptionContains() method of the TodoDocumentRepository interface. The source code of our repository interface looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;

import java.util.List;

public interface TodoDocumentRepository extends PartialUpdateRepository, SolrCrudRepository<TodoDocument, String> {

    public List<TodoDocument> findByTitleContainsOrDescriptionContains(String title, String description, Sort sort);
}

Named Queries

When the executed query is created by using named queries, we have to add a Sort parameter to the findByNamedQuery() method of the TodoDocumentRepository interface. The source code of our repository interface looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;

import java.util.List;

public interface TodoDocumentRepository extends PartialUpdateRepository, SolrCrudRepository<TodoDocument, String> {

    @Query(name = "TodoDocument.findByNamedQuery")
    public List<TodoDocument> findByNamedQuery(String searchTerm, Sort sort);
}

Note: This approach does not work if we are using Spring Data Solr RC1 because of a known bug. We have to either use the build snapshot dependency or wait for the release of RC2.

The @Query Annotation

When the executed query is created by using the @Query annotation, we have to add a Sort parameter to the findByQueryAnnotation() method of the TodoDocumentRepository interface. The source code of our repository interface looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;

import java.util.List;

public interface TodoDocumentRepository extends PartialUpdateRepository, SolrCrudRepository<TodoDocument, String> {

    @Query("title:*?0* OR description:*?0*")
    public List<TodoDocument> findByQueryAnnotation(String searchTerm, Sort sort);
}

Note: This approach does not work if we are using Spring Data Solr RC1 because of a known bug. We have to either use the build snapshot dependency or wait for the release of RC2.

Using the Query Method

We can use the modified query method by making the following changes to the search() method of the RepositoryIndexService class:

  1. Create a private sortByIdDesc() method which specifies that the query results are sorted in descending order by using the id of the document.
  2. Get the sorted query results by calling the query method declared in the TodoDocumentRepository interface.
  3. Return the query results.

Let's move on and take a look at the different implementations of the search() method.

Query Generation From Method Name

When we are building our queries by using the query generation from method name strategy, we can get the query results by using the findByTitleContainsOrDescriptionContains() method of the TodoDocumentRepository interface.

The source code of the relevant part of RepositoryTodoIndexService class looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class RepositoryTodoIndexService implements TodoIndexService {

    @Resource
    private TodoDocumentRepository repository;

    @Override
    public List<TodoDocument> search(String searchTerm) {
        return repository.findByTitleContainsOrDescriptionContains(searchTerm, searchTerm, sortByIdDesc());
    }

	private Sort sortByIdDesc() {
		return new Sort(Sort.Direction.DESC, "id");
	}
	
	//Other methods are omitted
}

Named Queries

When we are building our queries by using named queries, we can get the query results by using the findByNamedQuery() method of the TodoDocumentRepository interface.

The relevant part of the RepositoryTodoIndexService looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class RepositoryTodoIndexService implements TodoIndexService {

    @Resource
    private TodoDocumentRepository repository;

    @Override
    public List<TodoDocument> search(String searchTerm) {
        return repository.findByNamedQuery(searchTerm, sortByIdDesc());
    }

	private Sort sortByIdDesc() {
		return new Sort(Sort.Direction.DESC, "id");
	}
	
	//Other methods are omitted
}

The @Query annotation

When we are building our queries by using the @Query annotation, we can get the query results by using the findByQueryAnnotation() method of the TodoDocumentRepository interface.

The relevant part of the RepositoryTodoIndexService class looks as follows:

import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class RepositoryTodoIndexService implements TodoIndexService {

    @Resource
    private TodoDocumentRepository repository;

    @Override
    public List<TodoDocument> search(String searchTerm) {
        return repository.findByQueryAnnotation(searchTerm, sortByIdDesc());
    }

	private Sort sortByIdDesc() {
		return new Sort(Sort.Direction.DESC, "id");
	}
	
	//Other methods are omitted
}

Sorting the Query Results of Dynamic Queries

Because dynamic queries are created by adding a custom method to a repository interface, the steps required to sort the query results of a dynamic query have no effect to the service layer of our example application.

We can sort the query results of dynamic queries by making the following changes to the implementation of our custom repository interface:

  1. Add a private sortByIdDesc() method to the TodoDocumentRepositoryImpl class. This method returns a Sort object which specifies that the query results are sorted in descending order by using the id of the document.
  2. Modify the search() method of the TodoDocumentRepositoryImpl class. Set the sort options to the executed query by using the addSort() method of the Query interface and pass the created Sort object as a method parameter.

The relevant part of the TodoDocumentRepositoryImpl class looks as follows:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.solr.core.SolrTemplate;
import org.springframework.data.solr.core.query.Criteria;
import org.springframework.data.solr.core.query.SimpleQuery;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.List;

@Repository
public class TodoDocumentRepositoryImpl implements CustomTodoDocumentRepository {

    @Resource
    private SolrTemplate solrTemplate;

    @Override
    public List<TodoDocument> search(String searchTerm) {
        String[] words = searchTerm.split(" ");

        Criteria conditions = createSearchConditions(words);
        SimpleQuery search = new SimpleQuery(conditions);
        
		//SET SORT OPTIONS
		search.addSort(sortByIdDesc());

        Page results = solrTemplate.queryForPage(search, TodoDocument.class);
        return results.getContent();
    }

    private Criteria createSearchConditions(String[] words) {
        Criteria conditions = null;

        for (String word: words) {
            if (conditions == null) {
                conditions = new Criteria("id").contains(word)
                        .or(new Criteria("description").contains(word));
            }
            else {
                conditions = conditions.or(new Criteria("id").contains(word))
                        .or(new Criteria("description").contains(word));
            }
        }

        return conditions;
    }

    private Sort sortByIdDesc() {
        return new Sort(Sort.Direction.DESC, "id");
    }

	//Other methods are omitted
}

Summary

We have now learned how we can sort query results with Spring Data Solr. This tutorial has taught us three things:

  • We know that we can specify the used sort options by using the Sort class.
  • We learned that we can sort the query result of query methods by adding a new method parameter to the query method.
  • We learned that we can set the sort options to a dynamic query by using the addSort() method of the Query interface.

The next part of my Spring Data Solr tutorial describes how we can paginate the query results of our queries.

P.S. The example applications of this blog posts are available at Github (query methods and dynamic queries).

If you want to learn how to use Spring Data Solr, you should read my Spring Data Solr tutorial.
9 comments… add one
  • Alf Mar 28, 2014 @ 12:39

    Hi Petri!
    It is an awesome work you have done here with this tutorials!
    I have a question, I was working with the @Query annotation
    @Query(value = "id:?0", fields = {"rev"})
    is it possible to include in the query annotation the sort parameter "rev desc"?
    additionally I would like to include the start=0&rows=1 in order to find the maximum value of rev and retrieve only that document, the query that I perform in Solar is:
    http://localhost:8080/SolrDemo/Index/select?q=id%3A3&sort=rev+desc&start=0&rows=1&wt=json&indent=true
    Thanks and keep the great work!

    • Petri Mar 31, 2014 @ 9:41

      Hi Alf,

      Sorry that it took me a couple of days before I could answer to your question.

      As far as I know, you cannot specify the sort parameter by using the @Query annotation. I have always created a Sort object and passed it as a method parameter to my query method (this is the approach explained in this blog post).

      You can get the document which has the maximum value for rev field by using pagination (click the link for more details). The key is to set page size to 1 and ask Spring Data Solr to return the first page. This should return the correct document assuming that the query results are sorted correctly.

  • Swetha Ramaiah Apr 13, 2016 @ 9:53

    Hi Petri

    Im trying to add a custom parameter into the Query. But it turns out that using SolrQuery and setting individual parameters is the only way to do it.
    For eg, for timeZone:
    TZ=Asia/Kolkata
    Im using spring-data-solr 1.4.2.RELEASE version.

    Using this approach, my pagination is getting messed up. Here is a snippet

    
    SolrQuery solrQuery = new SolrQuery();
    solrQuery.setParam("TZ", timeZone);
    solrQuery.setStart(page);
    solrQuery.setRows(pageSize);
    solrQuery.addSort(FieldType.PAYMENT_DATETIME.getName(), SolrQuery.ORDER.desc);
    solrQuery.addSort(FieldType.requestId.getName(), SolrQuery.ORDER.desc);
    solrQuery.addFilterQuery(cleanseFilterQuery(filterQuery.getCriteria().toString()));
    solrQuery.setShowDebugInfo(true);
    query = query == null || query.isEmpty() ? "*:*" : query;
    solrQuery.setQuery(query);
    
    //results = solrTemplate.queryForPage(query1, SolrSearchResponse.class);
    try {
    	QueryResponse response= solrTemplate
    				.getSolrServer()
    				.query(solrQuery);
    	List searchResponses = solrTemplate.convertQueryResponseToBeans(response, 
    				SolrSearchResponse.class
    	);
    	results = new SolrResultPage(searchResponses, 
    				pageable, 
    				response.getResponse().size(), 
    				response.getResults().getMaxScore()
    	);
    } catch (SolrServerException e) {
    	logger.log(Level.SEVERE, "Error querying Solr: "+e);
    }
    
    
    • Petri Apr 15, 2016 @ 19:27

      If I remember correctly, Spring Data Solr doesn't support named parameters. However, it is possible to use indexed parameters. Have you tried using them? Unfortunately I don't find any "obvious" mistakes from your code. What kind of a pagination problem are you having?

  • DJSnoopy Aug 18, 2016 @ 1:51

    Hiho.

    You write super tutorials and i learned much about spring framework from you.
    But here i´ve got a small question with the sorting and case insensetive.

    I started sorting my results with OrderBy. So good so far, but:
    - Spring stucks on dynamic fields, cause all my fields are schemaless.
    - Fields are named name_s, description_t, etc.
    - JPA Query builder fails on FindByTypeOrderByName_sASC(String type)
    - documentaion says to use double underscores to escape (FindByTypeOrderByName__sASC), but fails also with: not property _s (Spring Data Solr 2.0.2 GA)

    So i switched to your tutorial and sorting start working, BUT:

    Sort.Order order = new Sort.Order(Sort.Direction.ASC, "name_s").ignoreCase();
    return solrContractRepository.findByType("contract", new Sort(order));

    Seems that solr or spring ignores the .ignoreCase() completly.
    So i got upperCase names first listed sorted and than followed by all lowercase entries sorted.

    Can you please give me a hint where to search further?

    And a last question: Why is there no donate function on your site? ;)

    kind regards
    Michael

    • Petri Aug 20, 2016 @ 18:29

      Hi Michael,

      I have a hunch that this might be a Solr configuration "problem". I have never used schemaless fields myself => I am not sure how you can fix this problem, but if you would be using a schema, you could create a new field type or create a copy field and sort the query results by using that field.

      And a last question: Why is there no donate function on your site? ;)

      The reason for this is that in Finland it's illegal to "ask" donations without having an official permit and I have never bothered to figure out if I could even get one (they can be hard to get). I guess this law tries to protect people from frauds.

  • Tharindu Galappaththi Oct 7, 2016 @ 11:03

    Hi Petri,
    I have this solr issue and I'm stuck with it. Could you please take a look ?
    http://stackoverflow.com/questions/39895145/solr-deleted-records-reappear-in-index

    • Petri Oct 11, 2016 @ 19:50

      Hi,

      I took a look at the StackOverflow question. Is the delete performed inside a Spring managed read-write transaction? If it is not, you could try to invoke it inside a transaction.

Leave a Reply