目标
- 完成solr环境安装、中文分析器和业务域的配置
- 会使用Spring Data Solr 完成增删改查操作
- 完成批量数据导入功能
- 完成关键字搜索功能
- 完成高亮显示关键字功能
- 完成更新索引库的功能
1. solr安装与配置
1. 什么是solr
大多数搜索引擎应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能。
这就是为什么转移负载到一个外部的搜索服务器是一个不错的主意,Apache Solr是一个流行的开源搜索服务器,它通过使用类似REST的HTTP API,这就确保你能从几乎任何编程语言来使用solr。
Solr是一个开源搜索平台,用于构建搜索应用程序。它建立在Lucene(全文搜索引擎)之上。 Solr是企业级的,快速的和高度可扩展的。使用Solr构建的应用程序非常复杂,可提供高性能。
为了在CNET网络的公司网站上添加搜索功能,YonikSeely于2004年创建了Solr。并在2006年1月,它成为Apache软件基金会下的一个开源项目。并于2016年发布最新版本Solr 6.0,支持并行SQL查询的执行。
Solr可以和Hadoop一起使用。由于Hadoop处理大量数据,Solr帮助我们从这么大的源中找到所需的信息。不仅限于搜索,Solr也可以用于存储目的。像其他NoSQL数据库一样,它是一种非关系数据存储和处理技术。
总之,Solr是一个可扩展的,可部署,搜索/存储引擎,优化搜索大量以文本为中心的数据。
2. solr安装
- 安装tomcat,解压缩即可。
- 解压solr
- 把solr下的dist目录solr-4.10.3.war更改名字为solr.war部署到tomcat的webapps下。
- 启动tomcat解压缩war包
- 把solr下的example/lib/ext目录下的所有的jar包,添加到solr的工程中(WEB-INF\lib目录下)
- 创建一个solrhome,solr下的/example/solr目录就是一个solrhome。复制该目录到D盘改名为solrhome
- 关联solr及solrhome。需要修改solr工程的web.xml
<env-entry>
<env-entry-name>solr/home</env-entry-name>
<env-entry-value>d:\solrhome</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
- 启动tomcat
浏览器输入:http://localhost:8080/solr
3. 中文分词
1. IK Analyzer简介
之前lucene介绍过
2. IK Analyzer配置
步骤:
- 把
IKAnalyzer2012FF_u1.jar
添加到solr工程的lib目录下 - 创建
WEB-INF/classes
文件夹,把扩展词典、停用词词典、配置文件放到solr工程的WEB-INF/classes
- 修改solrhome的schema.xml文件。配置一个FieldType,使用IKAnalyzer
<fieldType name="text_ik" class="solr.TextField">
<analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
4. 配置域
域相当于数据库的表字段,lucene讲过,用户根据业务需求定义相关的Field,一般来说,每一种对应一种数据,用户对同一种数据进行相同的操作。
域的常用属性
- name:指定域的名称
- type:指定域的类型
- indexed:是否索引
- stored:是否存储
- required:是否必须
- multiValued:是否多值 eg. [“sss”,“vvv”]
1. 域
修改solrhome的schema.xml文件,设置业务系统Field
<field name="item_goodsid" type="long" indexed="true" stored="true"/>
<field name="item_title" type="text_ik" indexed="true" stored="true"/>
<field name="item_price" type="double" indexed="true" stored="true"/>
<field name="item_image" type="string" indexed="false" stored="true"/>
<field name="item_category" type="string" indexed="true" stored="true"/>
<field name="item_seller" type="text_ik" indexed="true" stored="true"/>
<field name="item_brand" type="string" indexed="true" stored="true"/>
2. 复制域
复制域的作用在于将某一个Field中的数据复制到另一个域中,作用:搜索时可以将多个域组合
<field name="item_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
<copyField source="item_title" dest="item_keywords"/>
<copyField source="item_category" dest="item_keywords"/>
<copyField source="item_seller" dest="item_keywords"/>
<copyField source="item_brand" dest="item_keywords"/>
3. 动态域
当我们需要动态扩充字段时,需要使用动态域
<dynamicField name="item_spec_*" type="string" indexed="true" stored="true" />
2. Spring Data Solr入门
1. 简介
如何操作solr?
- 官方类库 solrJ (原理:http请求和响应)
- spring data solr (原理:对官方类库的封装)
- 手动请求solr 手动处理响应 httpClient
2. 入门小demo
1. 搭建工程
- 创建maven工程,pom.xml引入依赖
<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>
- 在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. @Field
注解
创建cn.itcast.pojo包,将品优购的TbItem实体类拷入本工程,属性使用@Field
注解,如果属性和配置文件定义的域名称不一致,需要在注解中指定域名称。
public class TbItem implements Serializable {
@Field
private Long id;
@Field("item_title")
private String title;
private String sellPoint;
@Field("item_price")
private BigDecimal price;
private Integer stockCount;
private Integer num;
private String barcode;
@Field("item_image")
private String image;
private Long categoryid;
private String status;
private Date createTime;
private Date updateTime;
private String itemSn;
private BigDecimal costPirce;
private BigDecimal marketPrice;
private String isDefault;
@Field("item_goodsid")
private Long goodsId;
private String sellerId;
private String cartThumbnail;
@Field("item_category")
private String category;
@Field("item_brand")
private String brand;
private String spec;
@Field("item_seller")
private String seller;
// ...
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.setTitle("华为MATE10");
item.setCategory("手机");
item.setBrand("华为");
item.setSeller("华为旗舰店");
item.setGoodsId(1L);
item.setPrice(new BigDecimal(3002.01));
solrTemplate.saveBean(item);
solrTemplate.commit();
}
4. 按主键查询
@Test
public void testFindById(){
TbItem item = solrTemplate.getById(1L, TbItem.class);
System.out.println(item.getBrand());
}
5. 按主键删除
@Test
public void testDeleteById(){
solrTemplate.deleteById("1");
solrTemplate.commit();
}
6. 分页查询
先循环插入100条测试数据
@Test
public void testAddList(){
List list = new ArrayList();
for (int i = 0; i < 100; i++) {
TbItem item = new TbItem();
item.setId(i+1L);
item.setTitle("华为MATE"+i);
item.setCategory("手机");
item.setBrand("华为"+i);
item.setSeller("华为旗舰店");
item.setGoodsId(1L);
item.setPrice(new BigDecimal(3000.01+i));
list.add(item);
}
solrTemplate.saveBeans(list);
solrTemplate.commit();
}
编写分页查询测试代码
@Test
public void testPageQuery(){
Query query = new SimpleQuery("*:*");
query.setOffset(20);//开始索引
query.setRows(20);//每页记录数
ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class);
for (TbItem item : page.getContent()) {
System.out.println(item.getTitle()+" "+item.getPrice()+" "+item.getBrand());
}
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
}
7.条件查询
Criteria用于对条件的封装
@Test
public void testPageQueryMulti(){
Query query = new SimpleQuery("*:*");
Criteria criteria = new Criteria("item_category").contains("手机");
criteria = criteria.and("item_brand").contains("2");
query.addCriteria(criteria);
/*query.setOffset(20);//开始索引
query.setRows(20);//每页记录数*/
ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class);
for (TbItem item : page.getContent()) {
System.out.println(item.getTitle()+" "+item.getPrice()+" "+item.getBrand());
}
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
}
8. 删除全部数据
@Test
public void deleteAll(){
Query query = new SimpleQuery("*:*");
solrTemplate.delete(query);
solrTemplate.commit();
}
3. 品优购-批量数据导入
1. 需求分析
编写专门的导入程序,将商品数据导入到solr系统
2. 查询商品数据列表
1. 工程搭建
- 创建pinyougou-solr-util,引入pinyougou-dao以及spring相关依赖
- 创建spring配置文件,包括applicationContext.xml
内容为
<context:component-scan base-package="com.pinyougou.solrutil"/>
2. 代码编写
创建com.pinyougou.solrutil包,创建类SolrUtil,实现商品数据的查询(已审核的商品)
@Component
public class SolrUtil {
@Autowired
private TbItemMapper itemMapper;
@Autowired
private SolrTemplate solrTemplate;
// 导入商品数据
public void importItemData(){
TbItemExample example = new TbItemExample();
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("---------结束------------");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring/applicationContext*.xml");
SolrUtil solrUtil = (SolrUtil) context.getBean("solrUtil");
solrUtil.importItemData();
}
}
3. 数据导入solr索引库
1. 实体类
- 将demo工程中添加的@Field注解的实体类拷贝到pinyougou-pojo
- 在pojo中引入依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<version>1.5.5.RELEASE</version>
</dependency>
2. 添加solr配置文件
添加applicationContext-solr.xml到spring目录
<?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>
3. 调用模板类导入solr
修改solr-util的SolrUtil.java
见之前的代码
4. 规格导入动态域
1. @Dynamic
注解
修改TbItem.java,添加属性
代码见之前的
1. 修改导入工具
修改solr-util的SolrUtil.java,引入fastjson依赖
4. 品优购-关键字搜索
1. 需求分析
打开搜索页面,在搜索框中输入要搜索的关键字,点击搜索按钮即可搜索,展示搜索结果
2. 后端代码
1. 服务接口层
- 创建pinyougou-search-interface模块(搜索服务的接口),依赖pinyougou-pojo
- 创建com.pinyougou.search.service包,创建业务接口
public interface ItemSearchService {
// 搜索
public Map search(Map searchMap);
}
2. 服务实现层
- 创建war工程pinyougou-search-service,引入search-interface spring 等依赖,tomcat端口改为9004
- 添加web.xml
- 在src/main/resources/spring下applicationContext-service.xml dubbo端口改为20884
- 在src/main/resources/spring下创建spring配置文件applicationContext-solr.xml
- 编写服务实现类ItemSearchServiceImpl.java
注意:超时时间的设置,因为有些图片等资源通过网络从service到controller需要时间,dubbo默认超时时间为1秒
@Service(timeout = 5000)
public class ItemSearchServiceImpl implements ItemSearchService {
@Autowired
private SolrTemplate solrTemplate;
@Override
public Map search(Map searchMap) {
Map map = new HashMap();
Query query = new SimpleQuery("*:*");
// 添加查询条件
Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class);
map.put("rows",page.getContent());
return null;
}
}
3. 控制层
- 创建pinyougou-search-web工程,引入依赖,tomcat运行端口为9104
- 添加web.xml
- 添加配置文件
- 创建包com.pinyougou.search.controller编写控制器类
@RestController
@RequestMapping("/itemSearch")
public class ItemSearchController {
@Reference
private ItemSearchService itemSearchService;
@RequestMapping("/search")
public Map search(@RequestBody Map searchMap){
return itemSearchService.search(searchMap);
}
}
3. 前端
1. 拷贝资源
将相关静态资源拷贝到pinyougou-search-web,包括css、fonts、img、js、plugins、search.html
将angulaJS拷贝到plugins里面
拷贝base.js到js文件夹
2. 服务层
pinyougou-search-web工程创建searchService.js
// 搜索服务层
app.service("searchService",function ($http) {
this.search = function (searchMap) {
return $http.post("itemSearch/search.do",searchMap);
}
});
3. 控制层
pinyougou-search-web工程searchController.js
app.controller("searchController",function ($scope, searchService) {
// 搜索
$scope.search = function () {
searchService.search($scope.searchMap).success(
function (response) {
$scope.resultMap = response; // 搜索返回的结果
}
);
}
});
4. 页面部分
search.html
引入js
<script src="plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="js/base.js"></script>
<script type="text/javascript" src="js/service/searchService.js"></script>
<script type="text/javascript" src="js/controller/searchController.js"></script>
指定控制器
<body ng-app="pinyougou" ng-controller="searchController">
绑定搜索框
<!--searchAutoComplete-->
<div class="input-append">
<input ng-model="searchMap.keywords" type="text" id="autocomplete" type="text" class="input-error input-xxlarge" />
<button ng-click="search()" class="sui-btn btn-xlarge btn-danger" type="button">搜索</button>
</div>
循环显示数据
<li class="yui3-u-1-5" ng-repeat="item in resultMap.rows">
<div class="list-wrap">
<div class="p-img">
<a href="item.html" target="_blank"><img src="{{item.image}}" /></a>
</div>
<div class="price">
<strong>
<em>¥</em>
<i>{{item.price}}</i>
</strong>
</div>
<div class="attr">
<em>{{item.title}}</em>
</div>
<div class="cu">
<em><span>促</span>满一件可参加超值换购</em>
</div>
<div class="commit">
<i class="command">已有2000人评价</i>
</div>
<div class="operate">
<a href="success-cart.html" target="_blank" class="sui-btn btn-bordered btn-danger">加入购物车</a>
<a href="javascript:void(0);" class="sui-btn btn-bordered">对比</a>
<a href="javascript:void(0);" class="sui-btn btn-bordered">关注</a>
</div>
</div>
</li>