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:
- Create a query method.
- Use the created query method.
Let’s move on and find out how we can implement the search function by using query methods.
Creating the Query Method
Query methods are methods which are
- added to the repository interface.
- used to specify the search query which is executed when the query method is called.
- 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.
- 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.
- Property expressions are used to refer to the properties of our document class.
- 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.
- We can combine property expressions by adding either And or Or keyword between them.
- 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:
- Spring Data Solr Reference Manual: Query Creation
- Spring Data Solr Reference Manual: Repository Query Keywords
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:
- Add the findBy prefix to start of the method name.
- Add the property expression of the title property after the prefix.
- Add the Contains keyword after the property expression.
- Add the Or keyword to the method name.
- Add the property expression of the description property after the Or keyword.
- Add the Contains keyword to the method name.
- 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:
- Specify the named query.
- 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:
- Create the properties file which contains the named queries.
- 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.
- 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:
- Add findByNamedQuery() method to the TodoDocumentRepository interface. This method returns a list of TodoDocument objects and takes a single String parameter called searchTerm.
- 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:
- Add findByQueryAnnotation() method to the TodoDocumentRepository interface. This method returns a list of TodoDocument objects and it has a single String parameter called searchTerm.
- Annotate the method with the @Query annotation.
- 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:
- Declare the search() method in the TodoIndexService interface.
- 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.
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
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:
I hope that this answered to your question.
Thanks! I'll do this.
You are welcome!
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
Hi Ankur,
If you want to get search term suggestions, you should read this Jira issue. When I created that Jira issue, the only way to get search term suggestions was to extend the
SolrTemplate
class.Because that issue is fixed in Spring Data 1.0.0.RELEASE, you can get search term suggestions by following these steps:
TermsQuery
by using theSolrTemplate
class.I hope that this answered to your question.
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?
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.
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?
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 theSolrCrudRepository
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.
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 theclearCache()
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.
Hi Nicole,
That is a very good question. You can configure the location of the properties file, which contains your named queries, when you configure Spring Data Solr.
named-queries-location
attribute of therepositories
element.namedQueriesLocation
attribute of the@EnableSolrRepositories
annotation.Thanks much for the reply.
Can't say enough about this very awesome tutorial!
Hi Nicole,
Thank you for your kind words. I really appreciate them!
hi,
The article mentions case-insensitive as a requirement but it doesn't specify when is that requirement handled. I tried and the queries are case-sensitive actually.
Good question. I have configured Solr to transform indexed words into lowercase before they are added to the Solr index. Also, I have configured Solr to transform search conditions into lowercase before it performs the search.
If you want to see how this is done, you can take look at the
schema.xml
file.I have a @Query with over 10 params, the problem is that when Spring is doing param replacement (in AbstractSolrQuery) that it is messing with my 10th param ?10 ... i.e. it is putting param value for ?1 into ?10.. because ?1 is part of '?10'
tried using findXXX etc, but i really need the custom lucene query
@Highlight(prefix = "", postfix = "")
@Query("""text:( ?0 ) AND moduleLevel_s:( ?1 ) AND programOfStudy_s:?2 AND yearOfEntry_i:?3 AND yearOfStudy_i:?4 AND unitValue_d:?5 AND department_s:( ?6 ) AND teachers_ss:( ?7 ) AND cappedAccess_b:?8 AND terms_ss:( ?9 ) AND days_ss:( ?10 )""")
HighlightPage advancedSearch(@Param(value = "query") List query,
@Param(value = "moduleLevel") List moduleLevel,
@Param(value = "programOfStudy") String programOfStudy,
@Param(value = "yearOfEntry") def yearOfEntry,
@Param(value = "yearOfStudy") def yearOfStudy,
@Param(value = "unitValue") def unitValue,
@Param(value = "department") List department,
@Param(value = "teachers") List teachers,
@Param(value = "cappedAccess") def cappedAccess,
@Param(value = "terms") List terms,
@Param(value = "days") List days,
Pageable pageable)
any idea what I could do ? I am using Spring Data Solr
Hi,
I think that you have two options:
Hi Petri,
I downloaded the code for this tutorial and try to run but I'm getting this error and I'm stuck.(https://github.com/pkainulainen/spring-data-solr-examples/tree/master/query-methods)
Please take a look at the error.
Thank You.
ERROR - ContextLoader - Context initialization failed
java.lang.IllegalArgumentException
at org.springframework.asm.ClassReader.(Unknown Source)
at org.springframework.asm.ClassReader.(Unknown Source)
at org.springframework.asm.ClassReader.(Unknown Source)
at org.springframework.core.type.classreading.SimpleMetadataReader.(SimpleMetadataReader.java:52)
I was able to solve it.
The answer was here : http://stackoverflow.com/questions/24128045/spring-context-initialization-failed-with-java-lang-illegalargumentexception-whi
Thank you very much for these wonderful tutorials. :-)
Hi Tharindu,
It's good to hear that you were able to solve your problem. Also, you might want to update the Spring Data Solr version since my examples use a quite old version and new one has a lot of interesting improvements.
hi i want to know how to join two collection in spring boot using @query method. hardly need for the solution
have two solr collections
Collection-A
Collection-B
I need to join these two collections and get one final result with both collections data.
inner join or join query in java code