2.1 Spring Data Solr简介
虽然支持任何编程语言的能力具有很大的市场价值,你可能感兴趣的问题是:我如何将Solr的应用集成到Spring中?可以,Spring Data Solr就是为了方便Solr的开发所研制的一个框架,其底层是对SolrJ(官方API)的封装。
2.2 Spring Data Solr入门小Demo
2.2.1 搭建工程
- 创建maven工程,pom.xml中引入依赖
<dependencies> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> <version>1.5.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> </dependency> </dependencies> |
(2)在src/main/resources下创建 applicationContext-solr.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.2.2 @Field 注解
创建 cn.itcast.pojo 包,将品优购的TbItem实体类拷入本工程 ,属性使用@Field注解标识 。 如果属性与配置文件定义的域名称不一致,需要在注解中指定域名称。
public class TbItem implements Serializable{
@Field private Long id;
@Field("item_title") private String title;
@Field("item_price") private BigDecimal price;
@Field("item_image") private String image;
@Field("item_goodsid") private Long goodsId;
@Field("item_category") private String category;
@Field("item_brand") private String brand;
@Field("item_seller") private String seller; ....... } |
2.2.3 增加(修改)
创建测试类TestTemplate.java
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:applicationContext-solr.xml") public class TestTemplate {
@Autowired private SolrTemplate solrTemplate;
@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(); } } |
2.2.4 按主键查询
@Test public void testFindOne(){ TbItem item = solrTemplate.getById(1, TbItem.class); System.out.println(item.getTitle()); } |
2.2.5 按主键删除
@Test public void testDelete(){ solrTemplate.deleteById("1"); solrTemplate.commit(); } |
2.2.6 分页查询
首先循环插入100条测试数据
@Test public void testAddList(){ List<TbItem> list=new ArrayList();
for(int i=0;i<100;i++){ TbItem item=new TbItem(); item.setId(i+1L); item.setBrand("华为"); item.setCategory("手机"); item.setGoodsId(1L); item.setSeller("华为2号专卖店"); item.setTitle("华为Mate"+i); item.setPrice(new BigDecimal(2000+i)); list.add(item); }
solrTemplate.saveBeans(list); solrTemplate.commit(); } |
编写分页查询测试代码:
@Test public void testPageQuery(){ Query query=new SimpleQuery("*:*"); query.setOffset(20);//开始索引(默认0) query.setRows(20);//每页记录数(默认10) ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class); System.out.println("总记录数:"+page.getTotalElements()); List<TbItem> list = page.getContent(); showList(list); } //显示记录数据 private void showList(List<TbItem> list){ for(TbItem item:list){ System.out.println(item.getTitle() +item.getPrice()); } } |
2.2.7 条件查询
Criteria 用于对条件的封装:
@Test public void testPageQueryMutil(){ Query query=new SimpleQuery("*:*"); Criteria criteria=new Criteria("item_title").contains("2"); criteria=criteria.and("item_title").contains("5"); query.addCriteria(criteria); //query.setOffset(20);//开始索引(默认0) //query.setRows(20);//每页记录数(默认10) ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class); System.out.println("总记录数:"+page.getTotalElements()); List<TbItem> list = page.getContent(); showList(list); } |
2.2.8 删除全部数据
@Test public void testDeleteAll(){ Query query=new SimpleQuery("*:*"); solrTemplate.delete(query); solrTemplate.commit(); } |
3.批量数据导入
3.1需求分析
编写专门的导入程序,将商品数据导入到Solr系统中
3.2查询商品数据列表
3.2.1 工程搭建
(1)创建pinyougou-solr-util(jar) ,引入pinyougou-dao 以及spring 相关依赖
(2)创建spring 配置文件
内容为:
<context:component-scan base-package="com.pinyougou.solrutil"> </context:component-scan> |
3.2.2 代码编写
创建com.pinyougou.solrutil包,创建类SolrUtil ,实现商品数据的查询(已审核商品)
@Component public class SolrUtil { @Autowired private TbItemMapper itemMapper;
/** * 导入商品数据 */ public void importItemData(){ TbItemExample example=new TbItemExample(); Criteria criteria = example.createCriteria(); criteria.andStatusEqualTo("1");//已审核 List<TbItem> itemList = itemMapper.selectByExample(example); System.out.println("===商品列表==="); for(TbItem item:itemList){ System.out.println(item.getTitle()); } System.out.println("===结束==="); }
public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("classpath*:spring/applicationContext*.xml"); SolrUtil solrUtil= (SolrUtil) context.getBean("solrUtil"); solrUtil.importItemData(); } } |
3.3数据导入Solr索引库
3.3.1实体类
- 将demo工程中添加了@Field注解的实体类拷贝到pinyougou-pojo中
- 在pinyougou-pojo中引入依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-solr</artifactId> <version>1.5.5.RELEASE</version> </dependency> |
3.3.2添加Solr配置文件
添加applicationContext-solr.xml到spring目录
<!-- 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> |
3.2.3调用模板类导入solr
修改pinyougou-solr-util的SolrUtil.java
@Autowired private SolrTemplate solrTemplate;
/** * 导入商品数据 */ public void importItemData(){
TbItemExample example=new TbItemExample(); Criteria criteria = example.createCriteria(); criteria.andStatusEqualTo("1");//已审核 List<TbItem> itemList = itemMapper.selectByExample(example); System.out.println("===商品列表==="); for(TbItem item:itemList){ System.out.println(item.getTitle()); } solrTemplate.saveBeans(itemList); solrTemplate.commit(); System.out.println("===结束==="); } |
3.4规格导入动态域
3.4.1@Dynamic注解
修改TbItem.java ,添加属性
@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; } |
3.4.2修改导入工具
修改pinyougou-solr-util的SolrUtil.java ,引入fastJSON依赖
/** * 导入商品数据 */ public void importItemData(){ TbItemExample example=new TbItemExample(); Criteria criteria = example.createCriteria(); criteria.andStatusEqualTo("1");//已审核 List<TbItem> itemList = itemMapper.selectByExample(example); System.out.println("===商品列表==="); for(TbItem item:itemList){ Map specMap= JSON.parseObject(item.getSpec());//将spec字段中的json字符串转换为map item.setSpecMap(specMap);//给带注解的字段赋值 System.out.println(item.getTitle()); } solrTemplate.saveBeans(itemList); solrTemplate.commit(); System.out.println("===结束==="); } |
高亮显示
1.1需求分析
将用户输入的关键字在标题中以红色的字体显示出来,就是搜索中常用的高亮显示.
1.2后端代码
修改服务层代码ItemSearchServiceImpl.java
创建私有方法,用于返回查询列表的结果(高亮)
/** * 根据关键字搜索列表 * @param keywords * @return */ private Map searchList(Map searchMap){ Map map=new HashMap(); HighlightQuery query=new SimpleHighlightQuery(); HighlightOptions highlightOptions=new HighlightOptions().addField("item_title");//设置高亮的域 highlightOptions.setSimplePrefix("<em style='color:red'>");//高亮前缀 highlightOptions.setSimplePostfix("</em>");//高亮后缀 query.setHighlightOptions(highlightOptions);//设置高亮选项 //按照关键字查询 Criteria criteria=new Criteria("item_keywords").is(searchMap.get("keywords")); query.addCriteria(criteria); HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query, TbItem.class); for(HighlightEntry<TbItem> h: page.getHighlighted()){//循环高亮入口集合 TbItem item = h.getEntity();//获取原实体类 if(h.getHighlights().size()>0 && h.getHighlights().get(0).getSnipplets().size()>0){ item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));//设置高亮的结果 } } map.put("rows",page.getContent()); return map; } |
修改ItemSearchServiceImpl 的search方法,调用刚才编写的私有方法
@Override public Map<String, Object> search(Map searchMap) { Map<String,Object> map=new HashMap<>(); //1.查询列表 map.putAll(searchList(searchMap)); return map; } |
1.3前端代码
我们测试后发现高亮显示的html代码原样输出,这是angularJS为了防止html攻击采取的安全机制。我们如何在页面上显示html的结果呢?我们会用到$sce服务的trustAsHtml方法来实现转换。
因为这个功能具有一定通用性,我们可以通过angularJS的过滤器来简化开发,这样只写一次,调用的时候就非常方便了,看代码:
- 修改base.js
// 定义模块: var app = angular.module("pinyougou",[]); /*$sce服务写成过滤器*/ app.filter('trustHtml',['$sce',function($sce){ return function(data){ return $sce.trustAsHtml(data); } }]); |
- 使用过滤器
ng-bind-html指令用于显示html内容
竖线 |用于调用过滤器,竖线前是要过滤的数据,后是过滤器名称
<div class="attr" ng-bind-html="item.title | trustHtml"></div> |
|就是竖线,看起来有点斜是因为字体原因。
2.搜索业务规则分析
2.1需求分析
我们今天要完成的目标是在关键字搜索的基础上添加面板搜索功能。
面板上有商品分类、品牌、各种规格和价格区间等条件
业务规则:
- 当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。
- 根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
- 根据第一个商品分类查询对应的模板,根据模板查询出规格列表
- 当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
- 当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
- 当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
- 当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
- 当用户点击搜索面板的相应条件时,隐藏已点击的条件。
2.2实现思路
- 搜索面板的商品分类需要使用Spring Data Solr的分组查询来实现
- 为了能够提高查询速度,我们需要把查询面板的品牌、规格数据提前放入redis
- 查询条件的构建、面板的隐藏需要使用angularJS来实现
- 后端的分类、品牌、规格、价格区间查询需要使用过滤查询来实现
3.查询分类列表
3.1需求分析
根据搜索关键字查询商品分类名称列表
3.2后端代码
修改SearchItemServiceImpl.java创建方法
/** * 查询分类列表 * @param searchMap * @return */ private List searchCategoryList(Map searchMap){ List<String> list=new ArrayList(); Query query=new SimpleQuery(); //按照关键字查询 Criteria criteria=new Criteria("item_keywords").is(searchMap.get("keywords")); query.addCriteria(criteria); //设置分组选项 GroupOptions groupOptions=new GroupOptions().addGroupByField("item_category"); query.setGroupOptions(groupOptions); //得到分组页 GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query, TbItem.class); //根据列得到分组结果集 GroupResult<TbItem> groupResult = page.getGroupResult("item_category"); //得到分组结果入口页 Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries(); //得到分组入口集合 List<GroupEntry<TbItem>> content = groupEntries.getContent(); for(GroupEntry<TbItem> entry:content){ list.add(entry.getGroupValue());//将分组结果的名称封装到返回值中 } return list; } |
search方法调用
@Override public Map<String, Object> search(Map searchMap) { Map<String,Object> map=new HashMap<>(); //1.按关键字查询(高亮显示) ...... //2.根据关键字查询商品分类 List categoryList = searchCategoryList(searchMap); map.put("categoryList",categoryList); return map; } |
3.3前端代码
修改search.html
<div class="type-wrap" ng-if="resultMap.categoryList!=null"> <div class="fl key">商品分类</div> <div class="fl value"> <span ng-repeat="category in resultMap.categoryList"> <a href="#">{{category}}</a> </span> </div> <div class="fl ext"></div> </div> |
7.过滤查询
7.1需求分析
根据上一步构建的查询条件,实现分类、品牌和规格的过滤查询
7.2代码实现
7.2.1分类过滤
修改pinyougou-search-service工程的SearchItemServiceImpl.java
/** * 根据关键字搜索列表 * @param keywords * @return */ private Map searchList(Map searchMap){ ....... //1.1关键字查询...... //1.2按分类筛选 if(!"".equals(searchMap.get("category"))){ Criteria filterCriteria=new Criteria("item_category").is(searchMap.get("category")); FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria); query.addFilterQuery(filterQuery); } //高亮显示处理..... } |
7.2.2品牌过滤
修改pinyougou-search-service工程的SearchItemServiceImpl.java
/** * 根据关键字搜索列表 * @param keywords * @return */ private Map searchList(Map searchMap){ ....... //1.1关键字查询 ....... //1.2按分类筛选 ....... //1.3按品牌筛选 if(!"".equals(searchMap.get("brand"))){ Criteria filterCriteria=new Criteria("item_brand").is(searchMap.get("brand")); FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria); query.addFilterQuery(filterQuery); } //高亮显示处理............... } |
7.2.3规格过滤
实现思路:规格有多项,需要循环过滤。循环规格查询条件,根据key得到域名城,根据value设置过滤条件。
修改pinyougou-search-service工程的SearchItemServiceImpl.java
/** * 根据关键字搜索列表 * @param keywords * @return */ private Map searchList(Map searchMap){ ...... //1.1关键字查询 .... //1.2按分类筛选 ..... //1.3按品牌筛选 ...... //1.4过滤规格 if(searchMap.get("spec")!=null){ Map<String,String> specMap= (Map) searchMap.get("spec"); for(String key:specMap.keySet() ){ Criteria filterCriteria=new Criteria("item_spec_"+key).is( specMap.get(key) ); FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria); query.addFilterQuery(filterQuery); } } //高亮显示处理..... } |
7.2.4根据分类查询品牌规格列表
@Override public Map<String, Object> search(Map searchMap) { Map<String,Object> map=new HashMap<>(); //1.按关键字查询(高亮显示) ...... //2.根据关键字查询商品分类 ...... //3.查询品牌和规格列表 String categoryName=(String)searchMap.get("category"); if(!"".equals(categoryName)){//如果有分类名称 map.putAll(searchBrandAndSpecList(categoryName)); }else{//如果没有分类名称,按照第一个查询 if(categoryList.size()>0){ map.putAll(searchBrandAndSpecList(categoryList.get(0))); } } return map; } |