1. 简介
虽然支持任何编程语言的能力具有很大的市场价值,你可能感兴趣的问题是:我如何将Solr的应用集成到Spring中?可以,Spring Data Solr就是为了方便Solr的开发所研制的一个框架,其底层是对SolrJ(官方API)的封装。
2. Spring Data Solr的使用
在Spring Data Solr中,对solr的库的操作都是通过solrTemplate来完成的。
2.1 依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<version>1.5.5.RELEASE</version>
</dependency>
2.2 配置
我们需要在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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/data/solr
http://www.springframework.org/schema/data/solr/spring-solr-1.0.xsd
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.xsd">
<!-- solr服务器地址 -->
<solr:solr-server id="solrServer" url="http://127.0.0.1:8080/solr" />
<!-- solr模板,使用solr模板可对索引库进行CRUD的操作 -->
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrServer" />
</bean>
</beans>
2.4 API
2.4.1 增加
1)增加1条数据——saveBean
@Test
public void testAdd(){
TbItem item=new TbItem();
item.setId(1L);
item.setBrand("华为");
item.setCategory("手机");
item.setGoodsId(1L);
item.setSeller("华为2号专卖店");
item.setTitle("华为Mate9");
item.setPrice(new BigDecimal(2000));
solrTemplate.saveBean(item);
solrTemplate.commit();
}
执行之后,在solr的管理台查询,如图:
可以知道添加成功。
1)批量插入——saveBeans
@Test
public void testAddList(){
List<TbItem> list = new ArrayList<TbItem>();
for(int i = 0 ; i < 100 ; i ++){
TbItem item=new TbItem();
item.setId(i+1L);
item.setBrand("华为_" + i);
item.setCategory("手机_" + i);
item.setGoodsId(i+1L);
item.setSeller("华为"+i+1+"号专卖店");
item.setTitle("华为Mate"+i);
item.setPrice(new BigDecimal(2000+1));
list.add(item);
}
solrTemplate.saveBeans(list);
solrTemplate.commit();
}
2.4.2 修改
在Spring Data Solr中,对数据的增加和修改都是save方法,如果数据存在,就是修改,如果数据不存在就是添加
例如:我把刚才的例子中的代码改一下
@Test
public void testAdd(){
TbItem item=new TbItem();
item.setId(1L);
item.setBrand("华为123");
item.setCategory("手机");
item.setGoodsId(1L);
item.setSeller("华为2号专卖店");
item.setTitle("华为Mate9");
item.setPrice(new BigDecimal(2000));
solrTemplate.saveBean(item);
solrTemplate.commit();
}
在执行,然后查询solr管理台,如图:
发现,solr库中还是只有一条记录,且记录的值发生了变化
2.4.3 删除
1)按主键删除
@Test
public void testDeleteById(){
solrTemplate.deleteById("1");
solrTemplate.commit();
}
执行成功,查看前台,如图;
数据被成功删除
2)按条件删除
(1)删除所有的数据
@Test
public void deleteAllTest(){
Query query = new SimpleQuery("*:*");
solrTemplate.delete(query);
solrTemplate.commit();
}
(2)按指定条件删除
@Test
public void deleteAllTest(){
Query query = new SimpleQuery("*:*");
Criteria criteria = new Criteria("item_category");
criteria.contains("手机_1");
query.addCriteria(criteria );
solrTemplate.delete(query);
solrTemplate.commit();
}
注意:对solr增删改之后,别忘记了commit。根据这点可以推导出,solr是支持事务的。
2.4.4 查询
1)按主键查询——getById
@Test
public void testFindById(){
TbItem byId = solrTemplate.getById(1L, TbItem.class);
System.out.println(byId);
}
2)分页查询——queryForPage
该方不设置分页数据的话,默认查询前10条数据
@Test
public void testFindByPage(){
// 构建一个查询对象 参数是查询表达式
Query query = new SimpleQuery("*:*");
/* 设置分页参数 */
// 1. 设置开始位置 相当于界面中的start
query.setOffset(0);
// 2. 每页记录数 相当于界面中的rows
query.setRows(20);
// 默认查询前10条数据
ScoredPage<TbItem> queryForPage = solrTemplate.queryForPage(query, TbItem.class);
/*for(TbItem t : queryForPage){
System.out.println(t);
}*/
List<TbItem> content = queryForPage.getContent(); // 每页的结果
for(TbItem t : content){
System.out.println(t);
}
System.out.println("总记录数: " + queryForPage.getTotalElements());
System.out.println("每页记录数: " + queryForPage.getSize());
System.out.println("总页数: " + queryForPage.getTotalPages());
}
运行结果
3)条件查询——Criteria
Criteria 用于对条件的封装
对于条件查询,我们也可以直接使用solr查询表达式来做,但是这样做要对表达式很精熟,使用起来不大方便,所以,我们可以使用Criteria条件对象来完整条件查询。
@Test
public void testCriteria(){
Query query = new SimpleQuery("*:*");
Criteria criteria = new Criteria("item_category");
criteria.contains("手机_1");
query.addCriteria(criteria );
/* 设置分页参数 */
// 1. 设置开始位置 相当于界面中的start
query.setOffset(0);
// 2. 每页记录数 相当于界面中的rows
query.setRows(20);
// 默认查询前10条数据
ScoredPage<TbItem> queryForPage = solrTemplate.queryForPage(query, TbItem.class);
List<TbItem> content = queryForPage.getContent(); // 每页的结果
System.out.println("总记录数: " + queryForPage.getTotalElements());
System.out.println("每页记录数: " + queryForPage.getSize());
System.out.println("总页数: " + queryForPage.getTotalPages());
}
查询结果:
Criteria封装了大量的条件查询的方法供我们使用,我们可以根据实际需要选择不同的方法来添加查询条件。
4)排序查询——Sort
Sort对象是对排序的一个封装,通过查询对象调用addSort方法来设置排序条件,例如;
Sort sort = new Sort(Sort.Direction.ASC,"item_price"); // 按价格升序
query.addSort(sort);
3. spring data solr的注解——@Dynamic
@Dynamic注解就是用于标记动态域的,在spring data solr中对动态域的封装做的很好,使我们可以很方便的进行开发工作。
@Dynamic的使用很简单,一般情况下,我们只需要在实体类中定义一个map集合,在map集合上标注@Field与@Dynamic即可,例如:
@Dynamic
@Field("item_spec_*")
private Map<String,String> specMap;
public Map<String, String> getSpecMap() {
return specMap;
}
public void setSpecMap(Map<String, String> specMap) {
this.specMap = specMap;
}
其中,item_spec_*就是solr中配置的动态域
配置好之后,我们只需要在构建实体的时候,给该map赋值即可,例如;
List<TbItem> itemList = itemMapper.selectByExample(example);
for(TbItem item : itemList){
// 获取规格字符串
String spec = item.getSpec();
// 规格字符串转成map集合
Map specMap = JSON.parseObject(spec, Map.class);
item.setSpecMap(specMap);
}
在页面查询结果如图:
可以发现动态域添加成功了。
4. Spring Data Solr的高级查询
solr的查询结果分为几种,HighlightPage,FacetPage,ScoredPage,GroupPage,StatsPage,这几个接口最终都是由SolrResultPage实现。
4.1 高亮搜索查询——HighlightQuery
在springdataSolr中,高亮搜索使用queryForHighlightPage这个方法。
所谓的高亮显示就是给关键词加上一些样式
例如:要给手机加高亮就是给手机这个关键词加上样式
<em style="color='red'">手机</em>
根据这个原理,我们可以知道高亮显示应该有三个必要条件:
1. 在哪一列加高亮 (列指的是solr中配置的域)
2. 前缀是什么。
3. 后缀是什么。
操作步骤:
1)构建高亮查询HighlightQuery
HighlightQuery query = new SimpleHighlightQuery();
2)创建高亮选项HighlightOptions对象
HighlightOptions options = new HighlightOptions();
3)设置高亮选项
A. 设置给哪一列加高亮——addField
options.addField("item_title");
注意:可以添加多个field
B. 前缀是什么——setSimplePrefix
options.setSimplePrefix("<em style='color:red'>");
C. 后缀是什么——setSimplePostfix
options.setSimplePostfix("</em>");
4)为查询对象设置高亮选项
query.setHighlightOptions(options );
5)通过queryForHighlightPage得到高亮页对象page——HighlightPage
HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query , TbItem.class);
6)解析高亮对象page
A. 获取高亮结果入口集合:该集合中存放的是高亮入口的对象
B. 循环遍历该集合,得到每一个入口,然后获取每个入口的高亮列表
C. 循环遍历每个入口的高亮列表得到高亮“小片”集合
D. 循环高亮小片集合,获取每一个小片的高亮结果。
注意:
1. 为什么高亮入口是一个集合了?
因为集合中的每个元素都是一条需要高亮显示的记录。
2. 为什么高亮入口获取到的是一个高亮列表了?
因为在设置高亮选项的域的时候,可以设置多个域,例如:
options.addField("item_title").addField("item_brand");
所以,高亮列表的每一项都是一个域的高亮小片集合,也就是说高亮列表的个数取决于高亮域的个数。
3. 为什么高亮小片是一个集合了?
因为,域可能设置了多值,所以这里得到的是设置的所有的值(field)的高亮
代码如下:
// 获取高亮入口集合
List<HighlightEntry<TbItem>> entryList = page.getHighlighted();
for(HighlightEntry<TbItem> entry : entryList){
// 获取每个入口的高亮列表
List<Highlight> highlights = entry.getHighlights();
// 循环遍历每个入口的高亮列表
for(Highlight h : highlights){
// 得到高亮“小片”集合
List<String> sns = h.getSnipplets();
for(String str : sns){
System.out.println(sns);
}
}
}
查询结果如图:
7)将高亮小片封装到实体对象中
8)返回高亮处理之后的实体对象的集合
List<TbItem> content = page.getContent();
rtnMap.put("rows", content);
完整的搜索代码如下:
/* 搜索功能
* 注意:参数searchMap 我们定义好格式为["keywords",""]
*/
@Override
public Map<String, Object> search(Map searchMap) {
Map<String, Object> rtnMap = new HashMap<String, Object>();
HighlightQuery query = new SimpleHighlightQuery();
// 查询条件
Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
// 设置高亮选项
HighlightOptions options = new HighlightOptions();
options.addField("item_title");
options.setSimplePrefix("<em style='color:red'>");
options.setSimplePostfix("</em>");
// 将高亮设置到查询对象上
query.setHighlightOptions(options );
// 查询获取高亮对象
HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query , TbItem.class);
// 获取高亮入口集合
List<HighlightEntry<TbItem>> entryList = page.getHighlighted();
for(HighlightEntry<TbItem> entry : entryList){
// 获取每个入口的高亮列表
List<Highlight> highlights = entry.getHighlights();
// 获取实体对象
TbItem entity = entry.getEntity();
// 循环遍历每个入口的高亮列表
for(Highlight h : highlights){
// 得到高亮“小片”集合
List<String> sns = h.getSnipplets();
for(String str : sns){
entity.setTitle(str);
System.out.println(sns);
}
}
}
List<TbItem> content = page.getContent();
rtnMap.put("rows", content);
return rtnMap;
}
注意:如果我们确定高亮列表只有一个对象,域字段只设置了一个的时候,循环部分可以稍微简写一下,例如:
for(HighlightEntry<TbItem> entry : entryList){
// 获取每个入口的高亮列表
List<Highlight> highlights = entry.getHighlights();
// 获取实体对象
TbItem entity = entry.getEntity();
if(!CollectionUtils.isEmpty(highlights) && CollectionUtils.isEmpty(highlights.get(0).getSnipplets())){
String title = highlights.get(0).getSnipplets().get(0);
entity.setTitle(title);
}
}
测试结果:
可以发现,高亮代码已经生成了。如果是用的传统的JSP页面的话,前端不需要做处理,直接就是红色的显示了,但是我这边使用的是angularjs1.0,所以,前端需要做一下处理才能显示。
4.2 分组查询——GroupPage
在Spring Data Solr中执行分组查询使用的是queryForGroupPage方法。
具体操作步骤:
1)创建查询条件
Query query = new SimpleQuery("*:*");
2)设置查询条件
// 创建一个 item_keywords 域的条件
Criteria criteria = new Criteria("item_keywords");
// 设置搜索条件
criteria.is(searchMap.get("keywords"));
3)设置分组信息
GroupOptions options = new GroupOptions(); // 创建分组选项
options.addGroupByField("item_category"); // 设置分组域
4)添加查询条件到查询对象
query.setGroupOptions(options);
5)调用queryForGroupPage,执行分组查询得到分组页对象
GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query , TbItem.class);
6) 解析分组页
A. 获取分组结果对象
B. 获取分组入口页对象
C. 获取分组入口集合
D. 循环分组入口集合,得到分组的具体值
// 1. 获取分组结果对象
GroupResult<TbItem> groupResult = page.getGroupResult("item_category");
// 2. 获取分组入口页对象
Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries();
// 3. 获取分组入口集合
List<GroupEntry<TbItem>> entryList = groupEntries.getContent();
for(GroupEntry<TbItem> entry : entryList){
// 4. 得到分组具体值
String groupValue = entry.getGroupValue();
list.add(groupValue);
}
注意:为什么有了分组页对象还有一个分组结果对象了?
是因为在addGroupByField的时候,可以加入多个分组域,所以分组页对象中包含了所有设置的分组域的分组结果对象而分组结果对象就是根据上面设置的某一个分组域来获取的分组结果对象。通俗的讲就是一个分组页包含多个分组结果
完整的代码如下:
private List searchCategoryList(Map searchMap) {
List<String> list = new ArrayList<>();
// 创建查询对象
Query query = new SimpleQuery("*:*");
// 创建一个 item_keywords 域的条件
Criteria criteria = new Criteria("item_keywords");
// 设置搜索条件
criteria.is(searchMap.get("keywords"));
/*
* 下面开始设置分组
* */
GroupOptions options = new GroupOptions();
options.addGroupByField("item_category");
query.setGroupOptions(options);
// 添加查询条件到查询对象
query.addCriteria(criteria );
// 执行分组查询 得到分组页对象
GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query , TbItem.class);
// 1. 获取分组结果对象
GroupResult<TbItem> groupResult = page.getGroupResult("item_category");
// 2. 获取分组入口页对象
Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries();
// 3. 获取分组入口集合
List<GroupEntry<TbItem>> entryList = groupEntries.getContent();
for(GroupEntry<TbItem> entry : entryList){
String groupValue = entry.getGroupValue();
list.add(groupValue);
}
return list;
}
4.3 过滤查询——FilterQuery
我们过滤查询是用于对查询的结果进行筛选,所以应该用于查询之后才合理,例如;
// 1.2 按照商品分类过滤
if(!"".equals(searchMap.get("category"))){
FilterQuery filterQuery = new SimpleFilterQuery();
Criteria filterCriteria = new Criteria("item_category").is(searchMap.get("category"));
filterQuery.addCriteria(filterCriteria);
query.addFilterQuery(filterQuery );
}
通过该例子可以看出,过滤查询分为三步
1)创建过滤查询对象
2)创建过滤条件
3)通过query执行过滤查询