在我的Spring Data Solr教程的较早部分中,我们实现了一个简单的搜索功能,该功能用于搜索待办事项的信息。 我们搜索功能的当前实现将所有搜索结果显示在一个页面中。 对于大多数现实生活中的应用程序而言,这不是可行的解决方案,因为搜索结果的数量可能太大,以致搜索功能不再可用。
这篇博客文章描述了如何使用Spring Data Solr对查询结果或搜索功能进行分页,从而为我们提供了解决该问题的方法。
这篇博客文章分为五个部分:
- 第一部分描述了如何手动请求正确的页面,并讨论了查询方法的不同返回类型。
- 第二部分描述了如何通过向存储库中添加自定义方法来获取搜索结果计数。
- 第三部分介绍了如何对查询方法的搜索结果进行分页。
- 第四部分教我们如何对动态查询的搜索结果进行分页。
- 第五部分也是最后一部分描述了如何配置和使用一种称为Web分页的技术。
注意 :这些博客文章提供了其他信息,可帮助我们理解此博客文章中描述的概念:
- 使用Maven运行Solr
- Spring Data Solr教程:Solr简介
- Spring Data Solr教程:配置
- Spring Data Solr教程CRUD(几乎)
- Spring Data Solr教程:将自定义方法添加到单个存储库
- Spring Data Solr教程:动态查询
- Spring Data Solr教程:排序
让我们开始吧。
理论纪要
在开始对示例应用程序进行修改之前,我们将简要介绍分页背后的理论。 本节分为两个小节,如下所述:
- 第一部分描述了如何指定查询的分页选项。
- 第二部分描述查询方法的不同返回类型。
让我们继续。
指定想要的页面
使用的分页选项是通过使用实现Pageable接口的PageRequest类指定的。
以下是典型的分页要求:
- 获取属于单个页面的查询结果。
- 使用单个字段的值对查询结果进行排序时,获取属于单个页面的查询结果。
- 使用多个字段的值对查询结果进行排序并且不同字段的排序顺序相同时,获取属于单个页面的查询结果。
- 使用多个字段的值对查询结果进行排序并且不同字段的排序顺序不同时,获取属于单个页面的查询结果。
让我们找出如何创建满足给定要求的PageRequest对象。
首先,我们必须创建一个PageRequest对象,该对象指定我们要获取属于单个页面的查询结果。 我们可以使用以下代码创建PageRequest对象:
//Get the query results belonging to the first page when page size is 10.
new PageRequest(0, 10)
其次,我们必须创建一个PageRequest对象,该对象指定当使用单个字段的值对查询结果进行排序时,我们希望获得属于单个页面的结果。 我们可以使用以下代码创建PageRequest对象:
/Gets the query results belonging to the first page when page size is 10.
//Query results are sorted in descending order by using id field.
new PageRequest(0, 10 Sort.Direction.DESC, "id")
第三,我们必须创建一个PageRequest对象,该对象指定当使用多个字段对查询结果进行排序并且不同字段的排序顺序相同时,我们希望获取属于单个页面的结果。 我们可以使用以下代码创建PageRequest对象:
//Gets the query results belonging to the first page when page size is 10.
//Query results are sorted in descending order by using id and description fields.
new PageRequest(0, 10 Sort.Direction.DESC, "id", "description")
第四,我们必须创建一个PageRequest对象,该对象指定当使用多个字段对查询结果进行排序并且不同字段的排序顺序不同时,要获取属于单个页面的查询结果。 我们可以使用以下代码创建该对象:
//Gets the query results belonging to the first page when page size is 10.
//Query results are sorted in descending order order by using the description field
//and in ascending order by using the id field.
Sort sort = new Sort(Sort.Direction.DESC, "description").and(new Sort(Sort.Direction.ASC, "id"))
new PageRequest(0, 10, sort);
现在我们知道如何创建新的PageRequest对象。 让我们继续讨论查询方法的不同返回类型。
确定查询方法的返回类型
当查询方法使用分页时,它可以具有两种返回类型。 这些返回类型将在下面进行描述(我们将假定模型类的名称为TodoDocument ):
- 当我们对分页元数据感兴趣时,查询方法的返回类型必须为Page <TodoDocument> (获取有关Page接口的更多信息,该接口声明用于获取分页元数据的方法)。
- 当我们对分页元数据不感兴趣时,查询方法的返回类型应为List <TodoDocument> 。
获取搜索结果计数
在开始对查询的搜索结果进行分页之前,我们必须实现一个函数,该函数用于获取与给定搜索条件匹配的待办事项条目数。 此数字是必需的,以便我们可以在前端实现分页逻辑。
我们可以按照以下步骤实现此功能:
- 将自定义方法添加到我们的存储库。 此方法用于返回搜索结果计数。
- 使用我们的自定义存储库方法创建一个新的服务方法。
在以下小节中将更详细地描述这些步骤。
向我们的存储库添加自定义方法
目前,如果不向存储库中添加自定义方法,就无法创建计数查询。 我们可以按照以下步骤进行操作:
- 创建一个定制的存储库界面。
- 实现定制存储库接口。
- 修改实际的存储库界面。
让我们继续前进,找出实现方法。
创建自定义存储库界面
我们可以按照以下步骤创建自定义存储库接口:
- 创建一个名为CustomTodoDocumentRepository的接口。
- 将count()方法添加到创建的接口。 该方法将使用的搜索词作为方法参数。
CustomTodoDocumentRepository接口的源代码如下所示:
public interface CustomTodoDocumentRepository {public long count(String searchTerm);//Other methods are omitted
}
实施自定义存储库界面
我们可以按照以下步骤实现自定义存储库接口:
- 创建一个名为TodoDocumentRepositoryImpl的类,并实现CustomTodoDocumentRepository接口。
- 用@Repository批注对类进行批注。
- 将SolrTemplate字段添加到类中,并使用@Resource注释对字段进行注释。
- 实现count()方法。
让我们仔细看一下count()方法的实现。 我们可以通过执行以下步骤来实现此方法:
- 获取给定搜索词的单词。
- 通过调用私有的constructSearchConditions()方法来构造使用的搜索条件,并将搜索词的单词作为方法参数传递。
- 通过创建新的SimpleQuery对象来创建执行的查询,并将创建的Criteria对象作为构造函数参数传递。
- 通过调用SolrTemplate类的count()方法获取搜索结果计数,并将创建的SimpleQuery对象作为方法参数传递。
- 返回搜索结果计数。
TodoDocumentRepositoryImpl类的源代码如下所示:
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;@Repository
public class TodoDocumentRepositoryImpl implements CustomTodoDocumentRepository {@Resourceprivate SolrTemplate solrTemplate;@Overridepublic long count(String searchTerm) {String[] words = searchTerm.split(" ");Criteria conditions = createSearchConditions(words);SimpleQuery countQuery = new SimpleQuery(conditions);return solrTemplate.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;}//Other methods are omitted.
}
修改实际存储库界面
通过扩展CustomTodoRepositoryInterface,我们可以使自定义count()方法对我们的存储库用户可见。 TodoDocumentRepository的源代码如下所示:
public interface TodoDocumentRepository extends CustomTodoRepository, SolrCrudRepository<TodoDocument, String> {//Repository methods are omitted.
}
使用自定义存储库方法
我们可以按照以下步骤使用创建的存储库方法:
- 修改TodoIndexService接口。
- 实现修改后的接口。
下面将更详细地描述这些步骤。
注意 :我们还必须进行其他更改,但是由于它们与Spring Data Solr不相关,因此在此不再赘述。
修改服务接口
我们必须通过向其添加一个新的countSearchResults()方法来修改TodoIndexService接口。 此方法将使用的搜索词作为方法参数,并返回搜索结果计数。 TodoIndexService接口的源代码如下所示:
public interface TodoIndexService {public long countSearchResults(String searchTerm);//Other methods are omitted.
}
实施修改后的接口
通过执行以下步骤,我们可以实现countSearchResults()方法:
- 将countSearchResults()方法添加到RepositoryTodoIndexService类。
- 通过调用自定义存储库方法获取搜索结果计数,然后返回搜索结果计数。
RepositoryTodoIndexService类的相关部分如下所示:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@Service
public class RepositoryTodoIndexService implements TodoIndexService {@Resourceprivate TodoDocumentRepository repository;@Overridepublic long countSearchResults(String searchTerm) {return repository.count(searchTerm);}//Other methods are omitted.
}
分页查询方法的查询结果
使用查询方法创建查询时,可以按照以下步骤对查询结果进行分页:
- 将新的Pageable参数添加到查询方法。 此参数指定获取的页面的详细信息。
- 通过将新的Pageable参数添加到TodoIndexService接口的search()方法来修改服务层。
让我们开始吧。
修改存储库界面
我们可以通过向查询方法添加Pageable参数来向我们的存储库添加分页支持,该方法用于构建执行的查询。 让我们看一下查询方法的声明。
从方法名称查询生成
通过使用从方法名策略生成查询来创建执行的查询时,我们必须向TodoDocumentRepository接口的findByTitleContainsOrDescriptionContains()方法添加Pageable参数。 我们的存储库界面的这些源代码如下所示:
import org.springframework.data.domain.Pageable;
import org.springframework.data.solr.repository.SolrCrudRepository;import java.util.List;public interface TodoDocumentRepository extends CustomTodoDocumentRepository, SolrCrudRepository<TodoDocument, String> {public List<TodoDocument> findByTitleContainsOrDescriptionContains(String title, String description, Pageable page);
}
命名查询
使用命名查询时,我们必须在TodoDocumentRepository接口的findByNamedQuery()方法中添加Pageable参数。 TodoDocumentRepository接口的源代码如下所示:
import org.springframework.data.domain.Pageable;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;import java.util.List;public interface TodoDocumentRepository extends CustomTodoDocumentRepository, SolrCrudRepository<TodoDocument, String> {@Query(name = "TodoDocument.findByNamedQuery")public List<TodoDocument> findByNamedQuery(String searchTerm, Pageable page);
}
@Query注释
使用@Query批注创建执行的查询时,我们必须在TodoDocumentRepository接口的findByQueryAnnotation()方法中添加Pageable参数。 我们的存储库界面的源代码如下所示:
import org.springframework.data.domain.Pageable;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;import java.util.List;public interface TodoDocumentRepository extends CustomTodoDocumentRepository, SolrCrudRepository<TodoDocument, String> {@Query("title:*?0* OR description:*?0*")public List<TodoDocument> findByQueryAnnotation(String searchTerm, Pageable page);
}
修改服务层
我们必须对示例应用程序的服务层进行以下修改:
- 将一个Pageable参数添加到TodoIndexService接口的search()方法。
- 实现新的search()方法。
注意 :我们还必须进行其他更改,但是由于它们与Spring Data Solr不相关,因此在此不再赘述。
TodoIndexService接口的源代码如下所示:
import org.springframework.data.domain.Pageable;
import java.util.List;public interface TodoIndexService {public List<TodoDocument> search(String searchTerm, Pageable page);//Other methods are omitted.
}
我们可以通过对RepositoryIndexService类的search()方法进行以下更改来使用修改后的查询方法:
- 通过调用存储库的查询方法来获取分页查询结果,并将使用的搜索词和Pageable对象作为方法参数传递。
- 返回查询结果。
让我们来看一下search()方法的不同实现。
从方法名称查询生成
通过使用从方法名策略生成查询来构建查询时,可以使用TodoDocumentRepository接口的findByTitleContainsOrDescriptionContains()方法获取属于特定页面的查询结果。
RepositoryTodoIndexService类的相关部分如下所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@Service
public class RepositoryTodoIndexService implements TodoIndexService {@Resourceprivate TodoDocumentRepository repository;@Overridepublic List<TodoDocument> search(String searchTerm, Pageable page) {return repository.findByTitleContainsOrDescriptionContains(searchTerm, searchTerm, page);}//Other methods are omitted
}
命名查询
当我们使用命名查询来构建执行的查询时,可以使用TodoDocumentRepository接口的findByNamedQuery()方法获取属于特定页面的搜索结果。
RepositoryTodoIndexService类的相关部分如下所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@Service
public class RepositoryTodoIndexService implements TodoIndexService {@Resourceprivate TodoDocumentRepository repository;@Overridepublic List<TodoDocument> search(String searchTerm, Pageable page) {return repository.findByNamedQuery(searchTerm, page);}//Other methods are omitted
}
@Query注释
使用@Query批注构建查询时,可以通过调用TodoDocumentRepository接口的findByQueryAnnotation()方法来获取属于特定页面的搜索结果。
RepositoryTodoIndexService类的源代码如下所示:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;@Service
public class RepositoryTodoIndexService implements TodoIndexService {@Resourceprivate TodoDocumentRepository repository;@Overridepublic List<TodoDocument> search(String searchTerm, Pageable page) {return repository.findByQueryAnnotation(searchTerm, page);}//Other methods are omitted
}
分页动态查询的查询结果
我们可以按照以下步骤对动态查询的查询结果进行分页:
- 将Pageable参数添加到我们的自定义存储库的search()方法中。
- 通过将Pageable参数添加到TodoIndexService接口的search()方法来修改服务层。
在以下小节中将更详细地描述这些步骤。
更改自定义存储库
我们必须向我们的自定义存储库添加分页支持。 我们可以按照以下步骤进行操作:
- 通过将Pageable参数添加到其search()方法来修改自定义存储库接口。
- 通过向其添加分页支持来更改search()方法的实现。
让我们继续前进,找出实现方法。
更改自定义存储库界面
我们必须在CustomTodoDocumentRepository接口中声明的search()方法中添加Pageable参数。 我们的自定义存储库界面的源代码如下所示:
import org.springframework.data.domain.Pageable;import java.util.List;public interface CustomTodoDocumentRepository {public List<TodoDocument> search(String searchTerm, Pageable page);//Other methods are omitted.
}
实施自定义存储库方法
我们的下一步是向search()方法的实现中添加分页支持。 通过执行以下步骤,我们可以实现TodoDocumentRepositoryImpl类的search()方法:
- 获取搜索词的单词。
- 通过调用私有createSearchConditions()方法并将搜索词的单词作为方法参数来构造使用的搜索条件。
- 通过创建新的SimpleQuery对象来创建执行的查询,并将创建的Criteria对象作为构造函数参数传递。
- 通过调用SimpleQuery类的setPageRequest()方法来设置查询的分页选项。 将Pageable对象作为方法参数传递。
- 通过调用SolrTemplate类的queryForPage()方法获取搜索结果。 将创建的查询和期望的返回对象的类型作为方法参数传递。
- 通过调用Page接口的getContent()方法来返回搜索结果。
TodoDocumentRepositoryImpl类的源代码如下所示:
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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.ArrayList;
import java.util.List;@Repository
public class TodoDocumentRepositoryImpl implements CustomTodoDocumentRepository {@Resourceprivate SolrTemplate solrTemplate;@Overridepublic List<TodoDocument> search(String searchTerm, Pageable page) {String[] words = searchTerm.split(" ");Criteria conditions = createSearchConditions(words);SimpleQuery search = new SimpleQuery(conditions);search.setPageRequest(page);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("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;}//Other methods are omitted.
}
使用自定义存储库
在使用修改后的存储库方法之前,我们必须对示例应用程序的服务层进行以下更改:
- 将一个Pageable参数添加到TodoIndexService接口的search()方法。
- 实现search()方法。
下面将更详细地描述这些步骤。
注意 :我们还必须进行其他更改,但是由于它们与Spring Data Solr不相关,因此在此不再赘述。
修改服务接口
我们必须向TodoIndexService接口的search()方法添加Pageable参数。 TodoIndexService的源代码如下所示:
import org.springframework.data.domain.Pageable;
import java.util.List;public interface TodoIndexService {public List<TodoDocument> search(String searchTerm, Pageable page);//Other methods are omitted.
}
实施服务接口
当使用Spring Data Solr的标准API进行构建时,我们可以通过调用自定义存储库的search()方法并将用户搜索词和Pageable对象作为方法参数来获取查询结果。
RepositoryTodoIndexService类的源代码如下所示:
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;@Service
public class RepositoryTodoIndexService implements TodoIndexService {@Resourceprivate TodoDocumentRepository repository;@Overridepublic List<TodoDocument> search(String searchTerm, Pageable page) {return repository.search(searchTerm, page);}//Other methods are omitted.
}
使用网页分页
一个问题仍然没有答案。 问题是:
在何处指定用于对查询的查询结果进行分页的分页选项?
我们将使用称为Web pagination的技术来创建查询的分页选项。 此技术基于称为PageableArgumentResolver的自定义参数解析器类。 此类解析来自HTTP请求的分页信息,并使向控件方法添加Pageable方法参数成为可能。
本节描述了如何在示例应用程序中配置和使用此技术。 它分为三个小节:
- 第一部分描述了如何配置PageableArgumentResolver类。
- 第二小节介绍了如何使用它。
- 最后一个小节讨论了Web分页的利弊。
让我们找出如何在示例应用程序中使用此技术。
组态
本小节描述了如何配置PageableArgumentResolver类,该类将用于从HTTP请求中提取分页选项。 让我们找出如何通过使用Java配置和XML配置来做到这一点。
Java配置
我们可以通过对ExampleApplicationContext类进行以下更改来添加自定义参数自变量解析器:
- 重写WebMvcConfigurerAdapter类的addArgumentResolvers()方法。
- 通过创建新的PageableArgumentResolver对象并将创建的对象添加到作为方法参数给出的参数解析器列表中,实现addArgumentResolvers()方法。
ExampleApplicationContext类的相关部分如下所示:
import org.springframework.data.web.PageableArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter;import java.util.List;//Annotations are omitted.
public class ExampleApplicationContext extends WebMvcConfigurerAdapter {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {PageableArgumentResolver pageableArgumentResolver = new PageableArgumentResolver();argumentResolvers.add(new ServletWebArgumentResolverAdapter(pageableArgumentResolver));}//Other methods are omitted.
}
XML配置
我们可以通过对exampleApplicationContext.xml文件进行以下更改来配置自定义参数解析程序:
- 使用mvc命名空间的arguments-resolvers元素配置自定义参数解析器。
- 在arguments-resolvers元素内配置PageableArgumentResolver bean。
exampleApplicationContext.xml文件的相关部分如下所示:
<?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:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"><mvc:annotation-driven><mvc:argument-resolvers><bean id="pageagleArgumentResolver" class="org.springframework.data.web.PageableArgumentResolver"/></mvc:argument-resolvers></mvc:annotation-driven><!-- Configuration is omitted. -->
</beans>
用法
使用前面介绍的方法之一配置PageableArgumentResolver类后,可以将Pageable方法参数添加到控制器方法中。 TodoController类的search()方法就是一个很好的例子。 其源代码的相关部分如下所示:
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.List;@Controller
public class TodoController {//Fields are omitted.@RequestMapping(value = "/api/todo/search/{searchTerm}", method = RequestMethod.GET)@ResponseBodypublic List<TodoDTO> search(@PathVariable("searchTerm") String searchTerm, Pageable page) {//Implementation is omitted.}//Other methods are omitted.
}
但是,将Pageable参数添加到controller方法还不够。 我们仍然必须将分页选项添加到HTTP请求。 这是通过在请求中添加特殊的请求参数来完成的。 这些请求参数描述如下:
- page.page request参数指定所请求的页面。
- page.size请求参数指定页面大小。
- page.sort请求参数指定用于对查询结果进行排序的属性。
- page.sort.dir请求参数指定排序顺序。
让我们花点时间思考一下Web分页的利弊。
利弊
在决定在我们的应用程序中使用它之前,我们应该意识到Web分页的优缺点。 让我们找出它们是什么。
优点
使用网页分页有一个主要好处:
将分页选项从Web层转移到存储库层是一件容易的事。 我们要做的就是配置自定义参数解析器,将Pageable参数添加到控制器方法,并使用特定的请求参数发送分页选项。 这比处理代码中的分页选项和手动创建PageRequest对象要简单得多。
缺点
下面介绍了使用Web分页的缺点:
- Web分页在Web层和Spring Data之间创建依赖关系。 这意味着存储库层的实现细节会泄漏到应用程序的上层。 尽管纯粹主义者可能会声称这是一个巨大的错误,但我不同意他们的观点。 我认为抽象应该使我们的生活更轻松,而不是更艰难。 我们还必须记住, 泄漏抽象定律规定,所有非平凡抽象在某种程度上都是泄漏的。
- Web分页的一个真正的缺点是,只有在使用单个字段对搜索结果进行排序的情况下,我们才能使用它。 尽管对于大多数用例来说这是完全可以的,但在某些情况下这会成为问题。 如果发生这种情况,我们必须手动处理分页选项。
摘要
现在,我们已将分页搜索结果添加到示例应用程序中。 本教程教会了我们以下内容:
- 我们学习了创建新的PageRequest对象。
- 我们了解到可以从两个不同的选项中选择查询方法的返回类型。
- 我们学习了对查询方法和动态查询的查询结果进行分页。
- 我们知道如何使用网页分页,并且知道它的优缺点。
我的Spring Data Solr教程的下一部分描述了如何向所有Spring Data Solr存储库添加自定义方法。
PS此博客文章的示例应用程序可在Github上获得( 查询方法和动态查询 )。
翻译自: https://www.javacodegeeks.com/2013/05/spring-data-solr-tutorial-pagination.html