Spring Data Solr Tutorial: Adding Custom Methods to All Repositories
If we are using Spring Data Solr in a real life software project, the odds are that sooner or later we will face a requirement which states that our application must be able to communicate with both a local Solr server and a SolrCloud. At the moment, fulfilling this requirement means that we have to add custom methods to all Spring Data Solr repositories. This blog post describes how that is done.
As an example, we will modify the example application of the previous part of my Spring Data Solr tutorial. During this blog post we will change the custom repository implementation of that application in a such way that all its methods are added to all repositories.
Note: This is of course a bit naive example because the custom interface and its implementation are both tied to the TodoDocument class.
We can add custom methods to all repositories by following these steps:
- Get the required dependencies with Maven
- Create an interface which declares the custom methods.
- Implement the created interface.
- Create a custom repository factory bean.
- Configure Spring Data Solr to use the custom repository factory bean.
Note: These blog posts provides additional information which helps us to understand the concepts of described in this blog post:
- Running Solr with Maven
- Spring Data Solr Tutorial: Introduction to Solr
- Spring Data Solr Tutorial: Configuration
- Spring Data Solr Tutorial: Query Methods
- Spring Data Solr Tutorial: Adding Custom Methods to a Single Repository
- Spring Data Solr Tutorial: Sorting
- Spring Data Solr Tutorial: Pagination
Enough with chit chat. Let’s get started.
Getting the Required Dependencies with Maven
The example application of this blog post uses a build snapshot of Spring Data Solr because it provides a better support for implementing custom repository factory beans. We can get the required dependencies by making the following changes to our POM file:
- Add the Spring snapshot repository to the repositories section of the pom.xml file.
- Change the version of the Spring Data Solr dependency.
These steps are described with more details in the following subsections.
Using Spring Snapshot Repository
We can use the Spring snapshot Maven repository by adding the following repository configuration to our POM file:
<repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshot Maven Repository</name> <url>http://repo.springsource.org/libs-snapshot</url> </repository> </repositories>
Updating Spring Data Solr Version
We can use the build snapshot of Spring Data Solr by adding the following dependency declaration to the pom.xml file.
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> <version>1.0.0.BUILD-SNAPSHOT</version> </dependency>
Creating the Custom Repository Interface
We can create a custom interface for our repositories by following these steps:
- Create an interface called CustomBaseRepository which has two type parameters: The type of document (T) and the id of the document (ID).
- Ensure that the CustomBaseRepository interface extends the SolrCrudRepository interface.
- Annotate the interface with the @NoRepositoryBean annotation. This ensures that Spring Data Solr will not try to create an implementation for our interface.
- Add the method declarations of the count() and update() methods to the CustomBaseRepository interface.
The source code of the CustomBaseRepository interface looks as follows:
import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.solr.repository.SolrCrudRepository; import java.io.Serializable; @NoRepositoryBean public interface CustomBaseRepository<T, ID extends Serializable> extends SolrCrudRepository<T, ID> { public long count(String searchTerm); public void update(Todo todoEntry); }
Our next step is to implement the created interface. Let’s find out how this is done.
Implementing the Custom Repository Interface
We can implement the custom repository by following these steps:
- Create a class called CustomBaseRepositoryImpl. This class has two type parameters: the type of the document (T) and the type of the document’s id (ID).
- Ensure that the created class extends the SimpleSolrRepository class and implements the CustomBaseRepository interface.
- Create a constructor which takes a SolrOperations object and the type of the document class as constructor arguments. The implementation of this constructor simply calls the constructor of the superclass.
- Implement the update() method. Because the implementation of this method has been described in this blog post, I will not go into details here.
- Implement the count() method. Again, I will not go into details here because the implementation of this method has been described earlier.
The source code of the CustomBaseRepositoryImpl class looks as follows:
import org.springframework.data.solr.core.SolrOperations; import org.springframework.data.solr.core.query.Criteria; import org.springframework.data.solr.core.query.PartialUpdate; import org.springframework.data.solr.core.query.SimpleQuery; import org.springframework.data.solr.repository.support.SimpleSolrRepository; import java.io.Serializable; public class CustomBaseRepositoryImpl<T, ID extends Serializable> extends SimpleSolrRepository<T, ID> implements CustomBaseRepository<T, ID> { public CustomBaseRepositoryImpl(SolrOperations solrOperations, Class<T> entityClass) { super(solrOperations, entityClass); } @Override public long count(String searchTerm) { String[] words = searchTerm.split(" "); Criteria conditions = createSearchConditions(words); SimpleQuery countQuery = new SimpleQuery(conditions); return getSolrOperations().count(countQuery); } private Criteria createSearchConditions(String[] words) { Criteria conditions = null; for (String word: words) { if (conditions == null) { conditions = new Criteria("title").contains(word) .or(new Criteria("description").contains(word)); } else { conditions = conditions.or(new Criteria("title").contains(word)) .or(new Criteria("description").contains(word)); } } return conditions; } @Override public void update(Todo todoEntry) { PartialUpdate update = new PartialUpdate("id", todoEntry.getId().toString()); update.add("description", todoEntry.getDescription()); update.add("title", todoEntry.getTitle()); getSolrOperations().saveBean(update); getSolrOperations().commit(); } }
Let’s move and find out how we can create a custom repository factory bean.
Creating the Custom Repository Factory Bean
The repository factory bean is a component which is responsible of creating implementations for the repository interfaces. Because we want to use the CustomBaseRepositoryImpl class as an implementation of our Spring Data Solr repositories, we have to create a custom repository factory bean.
We can create a new repository factory bean by following these steps:
- Create a class called CustomSolrRepositoryFactoryBean which extends the SolrRepositoryFactoryBean class.
- Add a private CustomSolrRepositoryFactory class to the CustomSolrRepositoryFactory bean class. This class extends the SolrRepositoryFactory class and it has two type parameters: the type of document (T) and the type of the document’s id (ID).
- Override the doCreateRepositoryFactory() method of the SolrRepositoryFactoryBean class. The implementation of this method returns a new CustomSolrRepositoryFactory object.
Let’s take a closer look at the implementation of the CustomSolrRepositoryFactory class. We can implement it by following these steps:
- Add a SolrOperations field to the CustomSolrRepositoryFactory class.
- Add a constructor to the CustomSolrRepositoryFactory class. This class takes the used SolrOperations object as a constructor argument. Its implementation will simply call the constructor of the superclass and set the received SolrOperations object to the field which we created in the step one.
- Override the getTargetRepository() method of the SolrRepositoryFactory class and return a new CustomBaseRepositoryImpl object.
- Override the getRepositoryBaseClass() method of the SolrRepositoryFactory class and return the type of our custom interface.
That’s it. The source code of our custom repository factory bean looks as follows:
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.solr.core.SolrOperations; import org.springframework.data.solr.repository.support.SolrRepositoryFactory; import org.springframework.data.solr.repository.support.SolrRepositoryFactoryBean; import java.io.Serializable; public class CustomSolrRepositoryFactoryBean extends SolrRepositoryFactoryBean { @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { return new CustomSolrRepositoryFactory(getSolrOperations()); } private static class CustomSolrRepositoryFactory<T, ID extends Serializable> extends SolrRepositoryFactory { private final SolrOperations solrOperations; public CustomSolrRepositoryFactory(SolrOperations solrOperations) { super(solrOperations); this.solrOperations = solrOperations; } @Override protected Object getTargetRepository(RepositoryMetadata metadata) { return new CustomBaseRepositoryImpl<T, ID>(solrOperations, (Class<T>) metadata.getDomainType()); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return CustomBaseRepository.class; } } }
Our next is to configure Spring Data Solr to use the repository factory bean which we just created. Let’s get started.
Configuring Spring Data Solr
Our last step is to configure Spring Data Solr to use the new repository factory bean which we created in the previous step. We can do this by using either a Java configuration class or an XML configuration file. Both of these options are described in the following subsections.
Note: The different configuration files presented in the following subsections are simplified for the sake of clarity. In reality, our example application has different configuration for development and production environment.
Java Configuration
If we are using Java configuration, we can configure Spring Data Solr to use a custom repository factory bean by using the repositoryFactoryBeanClass attribute of the @EnableJpaRepositories annotation. The source code of configuration class looks as follows:
import org.springframework.context.annotation.Configuration; import org.springframework.data.solr.repository.config.EnableSolrRepositories; @Configuration @EnableSolrRepositories( basePackages = "net.petrikainulainen.spring.datasolr.todo.repository.solr", repositoryFactoryBeanClass = CustomSolrRepositoryFactoryBean.class ) public class SolrContext { //Configuration is omitted. }
XML Configuration
When we are using XML configuration, we can configure Spring Data Solr to use a custom repository factory bean by using the factory-class attribute of the repositories namespace element. The XML configuration file of our application context looks as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:solr="http://www.springframework.org/schema/data/solr" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/data/solr http://www.springframework.org/schema/data/solr/spring-solr.xsd"> <!-- Enable Solr repositories and configure repository base package --> <solr:repositories base-package="net.petrikainulainen.spring.datasolr.todo.repository.solr" factory-class="net.petrikainulainen.spring.datasolr.todo.repository.solr.CustomSolrRepositoryFactoryBean"/> <!-- The configuration is omitted. --> </Beans>
Summary
We have now created two custom methods which are added to all repositories to our example application. Of course, like we learned earlier, this example does not make any sense because our custom repository interface and its implementation is tied to the TodoDocument class.
This tutorial has taught us two things:
- We can use the @NoRepositoryBean annotation to signal Spring Data Solr that it should not create an implementation for the interface which is annotated with the @NoRepositoryBean annotation.
- We can configure a custom repository factory bean by using either the repositoryFactoryBeanClass attribute of the @EnableSolrRepositories annotation or the factory-class attribute of the repositories namespace element.
As always, the example application of this blog is available at Github.