Are you looking for an advent calendar? You found it!

Spring Data Solr Tutorial: Query Methods

Open index drawer

We have learned how we can configure Spring Data Solr. We have also learned how we can add new documents to the Solr index, update the information of existing documents and delete documents from the Solr index. Now it is time to move forward and learn how we can search information from the Solr index by using Spring Data Solr.

The requirements of our search function are given in the following:

  • The search function must return all todo entries which title or description contains the given search term.
  • The search must be case insensitive.

We can implement the search function by following these steps:

  1. Create a query method.
  2. Use the created query method.

Let’s move on and find out how we can implement the search function by using query methods.

These blog entries provide additional information which helps us to understand the concepts described in this blog entry:

Creating the Query Method

Query methods are methods which are

  1. added to the repository interface.
  2. used to specify the search query which is executed when the query method is called.
  3. used to build static queries (queries which have always the same amount of query parameters).

We can create query methods with Spring Data Solr by using the following techniques:

  • Query generation from the method name
  • Named queries
  • @Query annotation

These techniques are described with more details in the following subsections.

Query Generation from the Method Name

The query generation from the method name is a query generation strategy where the executed query is parsed from the name of the query method.

  1. The name of the query method must start with a special prefix which identifies the query method. These prefixes are: find, findBy, get, getBy, read and readBy. This prefix is stripped from the method name when the executed query is parsed.
  2. Property expressions are used to refer to the properties of our document class.
  3. Special keywords are used together with property expressions to specify the operators used in the created query. These keywords are added to the name of the query method after a property expression.
  4. We can combine property expressions by adding either And or Or keyword between them.
  5. The parameter count of the query method must be equal with the number of property expressions used in its name.

We can get more information about the property expressions and the repository keywords by reading the following resources:

As we remember, our search function must return all todo entries which title or description contains the given search term. Also, our document class has two properties which we are using in this query. These properties are called title and description. We can create the method name which fulfils the requirements of our search function by following these steps:

  1. Add the findBy prefix to start of the method name.
  2. Add the property expression of the title property after the prefix.
  3. Add the Contains keyword after the property expression.
  4. Add the Or keyword to the method name.
  5. Add the property expression of the description property after the Or keyword.
  6. Add the Contains keyword to the method name.
  7. Add two method parameters to our query method. The first parameter matched against the title property and second one is matched against the description property.

The source code of the TodoDocumentRepository interface looks as follows:

import org.springframework.data.solr.repository.SolrCrudRepository;
import java.util.List;

public interface TodoDocumentRepository extends SolrCrudRepository<TodoDocument, String> {

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

Note: This query method will not work if the search term contains more than one word.

Named Queries

Named queries are queries which are declared in a separate properties file and wired to the correct query method. The rules concerning the properties file used to declare the named queries are described in the following:

  • The default location of the properties file is META-INF/solr-named-queries.properties but we can configure the location by using the namedQueriesLocation property of the @EnableSolrRepositories annotation.
  • The key of each named query is created by using the following formula: [The name of the document class].[The name of the named query].

Named queries which are configured in the properties files are matched with the query methods of our repository interface by using the following rules:

  • The name of query method which executes the named query must be same than the name of the named query.
  • If the name of the query method is not the same than the name of the named query, the query method must be annotated with the @Query annotation and the name of the named query must be configured by using the name property of the @Query annotation.

We can implement query method with named queries by following these steps:

  1. Specify the named query.
  2. Create the query method.

These steps are described with more details in the following.

Specifying the Named Query

We can create a named query which fulfils the requirements of our search function by following these steps:

  1. Create the properties file which contains the named queries.
  2. Create a key for the named query by using the formula described earlier. Since the name of our document class is TodoDocument, the key of our named query is TodoDocument.findByNamedQuery.
  3. Create the named query by using the Lucene query parser syntax. Because our query must return documents which title or description contains the given search term, our query is: title:*?0* OR description:*?0* (The ?0 is replaced with the value of the query method’s first parameter).

The content of the META-INF/solr-named-query.properties file looks as follows:

TodoDocument.findByNamedQuery=title:*?0* OR description:*?0*

Creating the Query Method

We can create the query method which uses the created named query by following these steps:

  1. Add findByNamedQuery() method to the TodoDocumentRepository interface. This method returns a list of TodoDocument objects and takes a single String parameter called searchTerm.
  2. Annotate the method with the @Query annotation and set the value of its name property to ‘TodoDocument.findByNamedQuery’. This step is not required since the name of the query method is the same than the name of the named query but I wanted to demonstrate the usage of the @Query annotation here.

The source code of the TodoDocumentRepository interface looks as follows:

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

import java.util.List;

public interface TodoDocumentRepository extends SolrCrudRepository<TodoDocument, String> {

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

@Query Annotation

The @Query annotation can be used to specify the query which is executed when a query method is called. We can create the query method which fulfils the requirements of our search function by following these steps:

  1. Add findByQueryAnnotation() method to the TodoDocumentRepository interface. This method returns a list of TodoDocument objects and it has a single String parameter called searchTerm.
  2. Annotate the method with the @Query annotation.
  3. Set the executed query as the value of the @Query annotation. We can create the query by using the Lucene query parser syntax. Because our query must return documents which title or description contains the given search term, the correct query is: title:*?0* OR description:*?0* (The ?0 is replaced with the value of the query method’s first parameter).

The source code of the TodoDocumentRepository interface looks as follows:

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

import java.util.List;

public interface TodoDocumentRepository extends SolrCrudRepository<TodoDocument, String> {

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

Using the Created Query Method

We can use the created query method by following these steps:

  1. Declare the search() method in the TodoIndexService interface.
  2. Add the implementation of the search() method to the RepositoryTodoIndexService class.

These steps are described with more details in the following subsections.

Declaring the Search Method

Our first step is to declare the search method in the TodoIndexService interface. The relevant part of the TodoIndexService interface looks as follows:

import java.util.List;

public interface TodoIndexService {

    public List<TodoDocument> search(String searchTerm);
}

Implementing the Search Method

Our second step is to implement the search() method. Our implementation is rather simple. It obtains a list of TodoDocument objects by calling the correct method of the TodoDocumentRepository interface and returns that list.

The name of the query method depends from the technique used to create the query method. The different implementations are described in the following.

Query Generation from the Method Name

When we are generating the query from the name of the query method, our implementation obtains a list of TodoDocument objects by calling the findByTitleContainsOrDescriptionContains() method of the TodoDocumentRepository interface and returns that list.

The relevant part of the RepositoryTodoIndexService class looks as follows:

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);
    }
}

Named Queries

If we are using named queries, our implementation gets a list of TodoDocument objects by calling the findByNamedQuery() method of the TodoDocumentRepository interface and returns that list.

The relevant part of the RepositoryTodoIndexService class looks as follows:

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);
    }
}

@Query Annotation

When we are using the @Query annotation, our implementation obtains a list of TodoDocument objects by calling the findByQueryAnnotation() method of the TodoDocumentRepository interface and returns that list.

The relevant part of the RepositoryTodoIndexService class looks as follows:

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);
    }
}

Selecting the Correct Query Creation Technique

The obvious question is:

What is the best way to add query methods to our Spring Data Solr repositories?

We can use the following guidelines when we are deciding the correct query creation technique for our query method:

  • If the created query is very simple, query generation from the method name is often the best choice. The problem of this approach is that implementing “complex” queries with this approach leads into long and ugly method names.
  • It is a good idea to keep our queries near our query methods. The benefit of using the @Query annotation is that we can see the executed query and our query method by reading the source code of our repository interface.
  • If we want to separate the executed queries from our repository interface, we should use named queries. The problem of this approach is that we have to check the executed query from the properties file which is quite cumbersome.

Summary

We have now learned how we can create static queries with Spring Data Solr. This blog entry has taught us two things:

  • We know what query method methods are and how we can create them.
  • We are familiar with the different query creation techniques and we know when to use them.

The example application which demonstrates the concepts described in this blog entry is available at Github. In the next part of this tutorial we will learn how we can add custom functionality to a single repository.

If you want to learn more about Spring Data Solr, you should read my Spring Data Solr tutorial.
If you enjoyed reading this blog post, you should follow me on Twitter:

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

16 comments… add one

  • Hello Petri Kainulainen, I’ve been following all your posts for a while and Congratulations for an awesome topic. Leveraging i would like to take a doubt with you, Are you know if i can use two database in one application relational and non relational with Spring Data? For example the whole class annotated with @Document connects the “MongoDB” and class with the annotation @Entity in a “PostgreSQL”? This is something I’ve been working for a while. I read your book on the Spring Data and did not believe anyone better than you to make me doubt this.

    Regards
    Tiare Balbi Bonamini

    Reply
    • Thank you for your kind words. I really appreciate them.

      It is possible to implement an application which uses multiple data storage technologies by using Spring Data. You can do this by following these steps:

      1. Configure Spring Data JPA.
      2. Configure Spring Data MongoDB.
      3. Create an entity class which is stored to relational database.
      4. Create a Spring Data JPA repository for your entity class.
      5. Create a document class which is stored to MongoDB.
      6. Create a Spring Data MongoDB repository for your document class.
      7. Implement the rest of your application.

      I hope that this answered to your question.

      Reply
      • Thanks! I’ll do this.

        Reply
        • You are welcome!

          Reply
  • Hi Petri,

    Thanks for this wonderful tutorial. It helped me a lot.

    Now i have integrated the spellchecker into Solr.

    But i didn’t know how do i get and show the suggestions when user enters the incorrect word.

    Can you please guide me.

    Thanks,
    Ankur

    Reply
  • I am having same requirement for my spring mvc webapp.
    I have coded the classes according to your example but I am not able to use the service in my spring controller.
    I created the repository as:
    public interface ICaseRepository extends SolrCrudRepository {
    @Query(“case_id_no:*?0* OR title:*?0*”)
    public List findByQueryAnnotation(String queryString);
    }

    Then search service as:
    public interface CaseLogSearchService {
    public List search(String queryString);
    }

    Then service implementation as:
    @Service
    public class CaseLogSearchServiceImpl implements CaseLogSearchService {
    @Resource
    private ICaseRepository repository;
    @Transactional
    public List search(String queryString){
    // TODO Auto-generated method stub
    return repository.findByQueryAnnotation(queryString);
    }
    }

    Then I am creating service reference in my controller to use as:
    @Autowired
    public CaseLogSearchService caseSearchervice;

    But when I try to run the project it gives following error:
    SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘baseController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: public com.cdac.egov.ab.service.CaseLogSearchService com.cdac.egov.ab.controller.BaseController.caseSearchervice; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘caseLogSearchServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘ICaseRepository': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)

    In above example do we need to provide implementation class for interface ICaseRepository?

    Reply
    • Have you configured the application context of your application by following the instructions given in this blog post? If you have done this, check that the root package of your Spring Data Solr repositories is correct.

      Reply
      • Yes I have configured that accordingly as per the tutorial.
        Code is as follows:

        package com.cdac.egov.ab.solr.context;

        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.PropertySource;
        import org.springframework.core.env.Environment;
        import org.springframework.data.solr.core.SolrTemplate;
        import org.springframework.data.solr.repository.config.EnableSolrRepositories;
        import org.springframework.data.solr.server.support.HttpSolrServerFactoryBean;

        import javax.annotation.Resource;

        @Configuration
        @EnableSolrRepositories(“com.cdac.egov.ab.solr.repository”)
        @PropertySource(“classpath:solr.properties”)
        public class HttpSolrContext {

        @Resource
        private Environment environment;

        @Bean
        public HttpSolrServerFactoryBean solrServerFactoryBean() {
        HttpSolrServerFactoryBean factory = new HttpSolrServerFactoryBean();

        factory.setUrl(environment.getRequiredProperty(“solr.server.url”));

        return factory;
        }

        @Bean
        public SolrTemplate solrTemplate() throws Exception {
        return new SolrTemplate(solrServerFactoryBean().getObject());
        }
        }

        I have skipped the @profile annotation in my application because am connectiong to stand alone solr server only.
        As per the tutorial spring should provide a proxy implementation of my repository interface. Right?
        Is there any strict directory structure or class naming to follow?

        Reply
        • Your configuration seems correct (assuming that your repository interface is in package com.cdac.egov.ab.solr.repository and you provided the correct type parameters when you extended the SolrCrudRepository interface).

          Spring Data Solr should provide proxy implementations for your repository, and I am now aware of any naming convention concerning repository interface names (at least in this case).

          Which version of Spring Data Solr are you using? If you are not using the latest version (1.2.0.RELEASE), I suggest that you update your Spring Data Solr version. Also, it seems that the configuration of Spring Data Solr has changed a bit. You might want to update it as well.

          If this doesn’t help, I am not sure what your problem is. I might be able to solve it if I can see whole source code of your application, but I assume that this isn’t possible. If it is possible, I can take a look at it.

          Reply
          • I changed the Spring Data Solr version to latest i.e. 1.2.0.Release and changed configuration for solr server too.
            Now getting following error:

            Jun 12, 2014 2:07:44 PM org.apache.catalina.core.ApplicationContext log
            INFO: Initializing Spring root WebApplicationContext
            Jun 12, 2014 2:07:45 PM org.apache.catalina.core.StandardContext listenerStart
            SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
            java.lang.NoSuchMethodError: org.springframework.core.type.classreading.CachingMetadataReaderFactory.clearCache()V

          • Which version of Spring Framework are you using?

            The root cause of your problem is that the CachingMetadataReaderFactory class doesn’t have the clearCache() method. This method was added when Spring Framework 3.2 was released.

            In other words, if you want to use Spring Data Solr 1.2.0, you have to update Spring Framework to version 3.2.9.RELEASE. If you have to use Spring Framework 3.1, you have to use Spring Data Solr version 1.0.0.

            Check out the following webpages for more details about this:

  • Hello Petri,

    This is a great tutorial! I have a question about the named queries properties file:
    solr-named-query.properties

    where is this properties file specified within the configuration? how does spring know where it is? when/where is it called? asking because i get a ‘property named not found’ error and was thinking this may tie into my problem. ‘named’ as in seems like it’s trying to pick up the query method by name instead of by the parameters specified in the named queries properties file.

    Reply

Leave a Comment