Spring Data Solr —— 快速入门

  Solr是基于Lucene(全文检索引擎)开发,它是一个独立系统,运行在Tomcat或Jetty(solr6以上集成了jetty,无需再部署到servlet容器上),但其原生中文的分词词功能不行,需要集成第三方分词器(如IK Analyzer)。

  Solr的安装可上网搜一下,很简单。下面开始演示,如何集成IK Analyzer、配置相关域以及使用Spring Data Solr进行操作。

一、集成 IK Analyzer 分词器

步骤:
1、把IKAnalyzer2012FF_u1.jar 添加到 solr 工程的 lib 目录下
2、创建WEB-INF/classes文件夹把扩展词典、停用词词典、配置文件放到 solr 工程的 WEB-INF/classes 目录下。

Spring Data Solr  ——  快速入门

Spring Data Solr  ——  快速入门

3、修改 Solrhome 的 schema.xml 文件,配置一个 FieldType,使用 IKAnalyzer

<fieldType name="text_ik" class="solr.TextField">
    <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

二、配置域

  域相当于数据库的表字段,用户存放数据,因此用户根据业务需要去定义相关的Field(域),一般来说,每一种对应着一种数据,用户对同一种数据进行相同的操作。

域的常用属性:
• name:指定域的名称
• type:指定域的类型
• indexed:是否索引
• stored:是否存储
• required:是否必须
• multiValued:是否多值

1、域

  Solr中默认定义唯一主键key为id域,如下:

<uniqueKey>id</uniqueKey>

  Solr在删除、更新索引时使用id域进行判断,也可以自定义唯一主键。
  注意在创建索引时必须指定唯一约束。

 <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、copyField复制域

  copyField复制域,可以将多个Field复制到一个Field中,以便进行统一的检索:
  比如,根据关键字只搜索item_keywords域的内容就相当于搜索item_title、item_category、item_seller、item_brand,即将item_title、item_category、item_seller、item_brand复制到item_keywords域中。

  目标域必须是多值的。

 <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(动态字段)

  动态字段就是不用指定具体的名称,只要定义字段名称的规则,例如定义一个 dynamicField,name 为*_i,定义它的type为text,那么在使用这个字段的时候,任何以_i结尾的字段都被认为是符合这个定义的,例如:name_i,gender_i,school_i等。

  自定义Field名为:product_title_t,“product_title_t”和scheam.xml中的dynamicField规则匹配成功。

  如:

Spring Data Solr  ——  快速入门

配置:<dynamicField name="item_spec_*" type="string" indexed="true" stored="true" />

三、使用Spring Data Solr对Solr进行操作

  Spring Data Solr就是为了方便Solr的开发所研制的一个框架,其底层是对SolrJ(官方API)的封装。

1、环境的搭建

项目目录结构:

Spring Data Solr  ——  快速入门

①搭建SSM框架,参考:http://www.cnblogs.com/gdwkong/p/8784780.html

②添加依赖

     <!-- solr客户端 -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>${solrj.version}</version>
</dependency>
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
<!--spring data solr-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<version>1.5.5.RELEASE</version>
</dependency>

③创建mysql表,通过通用mapper生成TbItemMapper.java、TbItemMapper.xml、实体类TbItem.java、TbItemExample.java

 CREATE TABLE `tb_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id,同时也是商品编号',
`title` varchar(100) NOT NULL COMMENT '商品标题',
`sell_point` varchar(500) DEFAULT NULL COMMENT '商品卖点',
`price` decimal(20,2) NOT NULL COMMENT '商品价格,单位为:元',
`stock_count` int(10) DEFAULT NULL,
`num` int(10) NOT NULL COMMENT '库存数量',
`barcode` varchar(30) DEFAULT NULL COMMENT '商品条形码',
`image` varchar(2000) DEFAULT NULL COMMENT '商品图片',
`categoryId` bigint(10) NOT NULL COMMENT '所属类目,叶子类目',
`status` varchar(1) NOT NULL COMMENT '商品状态,1-正常,2-下架,3-删除',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`item_sn` varchar(30) DEFAULT NULL,
`cost_pirce` decimal(10,2) DEFAULT NULL,
`market_price` decimal(10,2) DEFAULT NULL,
`is_default` varchar(1) DEFAULT NULL,
`goods_id` bigint(20) DEFAULT NULL,
`seller_id` varchar(30) DEFAULT NULL,
`cart_thumbnail` varchar(150) DEFAULT NULL,
`category` varchar(200) DEFAULT NULL,
`brand` varchar(100) DEFAULT NULL,
`spec` varchar(200) DEFAULT NULL,
`seller` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `cid` (`categoryId`),
KEY `status` (`status`),
KEY `updated` (`update_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1369286 DEFAULT CHARSET=utf8 COMMENT='商品表'

④修改实体类TbItem.java,添加solr注解,映射索引字段

 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; @Dynamic
@Field("item_spec_*")
private Map<String,String> specMap; private static final long serialVersionUID = 1L;   .....
}

⑤配置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"> <bean class="com.cenobitor.solr.SolrUtil" id="solrUtil"></bean> <!--solr服务器地址-->
<solr:solr-server id="solrServer" url="http://127.0.0.1:8280/solr"/>
<!-- solr模板,使用solr模板可对索引库进行CRUD的操作 -->
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrServer"/>
</bean> </beans>

⑥运行该类生成索引

 @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(),Map.class);//将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");
context.getBean(SolrUtil.class).importItemData();
} }

2、使用Spring Data Solr对Solr进行操作

①创建ItemSearchServiceImpl.java

 @Service
public class ItemSearchServiceImpl implements ItemSearchService { @Autowired
private SolrTemplate solrTemplate; /**
* 添加
* @param tbItem
*/
@Override
public void add(TbItem tbItem) {
solrTemplate.saveBean(tbItem);
solrTemplate.commit();
} /**
* 按主键查询
* @param id
* @return
*/
@Override
public TbItem searchById(int id) {
TbItem tbItem = solrTemplate.getById(id, TbItem.class);
return tbItem;
} /**
* 按主键删除
* @param id
* @return
*/
@Override
public void deleteById(String id) {
solrTemplate.deleteById(id);
} /**
* 分页查询
* @param start
* @param size
*/
@Override
public ScoredPage<TbItem> pageQuery(int start,int size){
Query query=new SimpleQuery("*:*");
query.setOffset(start);//开始索引(默认0)start:(page-1)*rows
query.setRows(size);//每页记录数(默认10)//rows:rows
return solrTemplate.queryForPage(query, TbItem.class);
} /**
* 删除所有
*/
@Override
public void deleteAll() {
Query query = new SimpleQuery("*:*");
solrTemplate.delete(query);
solrTemplate.commit();
} /**
* 搜索
* @param searchMap
* @return
*/
@Override
public Map<String, Object> search(Map searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//1.先获取从页面传递过来的参数的值 通过KEY获取
String keywords = (String)searchMap.get("keywords");//获取主查询的条件 //2.设置主查询的条件
HighlightQuery query = new SimpleHighlightQuery();
Criteria criteria = new Criteria("item_keywords");
criteria.is(keywords);
query.addCriteria(criteria);
//3.设置高亮查询的条件 设置高亮显示的域 设置前缀 设置后缀
HighlightOptions hightoptions = new HighlightOptions();
hightoptions.addField("item_title");//设置高亮显示的域
hightoptions.setSimplePrefix("<em style=\"color:red\">");
hightoptions.setSimplePostfix("</em>");
query.setHighlightOptions(hightoptions); //4.设置过滤条件 商品分类的过滤
if (searchMap.get("category") != null && !"".equals(searchMap.get("category"))) {
Criteria fiterCriteria = new Criteria("item_category").is(searchMap.get("category"));
FilterQuery filterQuery = new SimpleFilterQuery(fiterCriteria);
query.addFilterQuery(filterQuery);
} //5.设置品牌的过滤
if (searchMap.get("brand") != null && !"".equals(searchMap.get("brand"))) {
Criteria fitercriteria = new Criteria("item_brand").is(searchMap.get("brand"));
FilterQuery filterquery = new SimpleFilterQuery(fitercriteria);
query.addFilterQuery(filterquery);
} //6.设置规格的过滤条件
if (searchMap.get("spec") != null) {
Map<String,String> spec = (Map<String, String>) searchMap.get("spec"); for (String key : spec.keySet()) {
String value = spec.get(key);
Criteria fiterCriteria = new Criteria("item_spec_"+key).is(value);//item_spec_网络:3G
FilterQuery filterquery = new SimpleFilterQuery(fiterCriteria);
query.addFilterQuery(filterquery);//
}
} //7.按照价格筛选
if (StringUtils.isNotBlank((CharSequence) searchMap.get("price"))){
//item_price:[10 TO 20]
String[] split = searchMap.get("price").toString().split("-");
SimpleFilterQuery filterQuery = new SimpleFilterQuery();
Criteria itemPrice = new Criteria("item_price");
//如果有* 语法是不支持的
if(!split[1].equals("*")){
itemPrice.between(split[0],split[1],true,true);
}else {
itemPrice.greaterThanEqual(split[0]);
}
filterQuery.addCriteria(itemPrice);
query.addFilterQuery(filterQuery);
}
//8.分页查询
Integer pageNo = (Integer) searchMap.get("pageNo");//提取页面 if (pageNo==null){
pageNo =1;
}
Integer pageSize = (Integer) searchMap.get("pageSize");//每页记录数
if (pageSize==null){
pageSize=20;
}
query.setOffset((pageNo-1)*pageSize);//从第几条记录查询
query.setRows(pageSize); //9.排序
String sortValue = (String) searchMap.get("sort");
String sortField = (String) searchMap.get("sortField");//排序字段
if (StringUtils.isNotBlank(sortField)){
if (sortValue.equals("ASC")){
Sort sort = new Sort(Sort.Direction.ASC, "item_" + sortField);
query.addSort(sort);
}
if (sortValue.equals("DESC")){
Sort sort = new Sort(Sort.Direction.DESC, "item_" + sortField);
query.addSort(sort);
}
} //10.执行查询 获取高亮数据
HighlightPage<TbItem> highlightPage = solrTemplate.queryForHighlightPage(query, TbItem.class); List<HighlightEntry<TbItem>> highlighted = highlightPage.getHighlighted();
for (HighlightEntry<TbItem> tbItemHighlightEntry : highlighted) {
TbItem entity = tbItemHighlightEntry.getEntity();//实体对象 现在是没有高亮的数据的 List<HighlightEntry.Highlight> highlights = tbItemHighlightEntry.getHighlights();
//如有高亮,就取高亮
if(highlights!=null && highlights.size()>0 && highlights.get(0)!=null && highlights.get(0).getSnipplets()!=null && highlights.get(0).getSnipplets().size()>0) {
entity.setTitle(highlights.get(0).getSnipplets().get(0));
}
}
List<TbItem> tbItems = highlightPage.getContent();//获取高亮的文档的集合
//11.执行查询
System.out.println("结果"+tbItems.size());
//12.获取结果集 返回
resultMap.put("rows",tbItems);
resultMap.put("totalPages",highlightPage.getTotalPages());//返回总页数
resultMap.put("total",highlightPage.getTotalElements());//返回总记录数
return resultMap;
}
}

②测试相关方法

 @RunWith(SpringRunner.class)
@ContextConfiguration("classpath:spring/applicationContext-*.xml")
public class ItemSearchServiceImplTest { @Autowired
private ItemSearchService itemSearchService; @Test
public void search() {
//构造json字符串
String searchStr = new String("{'keywords':'华为','category':'手机','brand':'华为'," +
"'spec':{'机身内存':'16G','网络':'联通3G'},'price':'1000-3000'," +
"'pageNo':1,'pageSize':10,'sortField':'price','sort':'ASC'}");
Map searchMap = JSON.parseObject(searchStr, Map.class);
//根据条件进行搜索过滤
Map<String, Object> search = itemSearchService.search(searchMap);
for (String s : search.keySet()) {
System.out.println(s+":"+search.get(s));
}
Assert.assertNotEquals(0,search.size());
//结果9
/*total:9
totalPages:1
rows:[
TbItem{id=1041685, title='<em style="color:red">华为</em> 麦芒B199 白 电信3G手机 双卡双待双通', price=1249.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=1060844, title='<em style="color:red">华为</em> Ascend P6S 碳素黑 联通3G手机 双卡双待', price=1259.0,image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=1075409, title='<em style="color:red">华为</em> 麦芒B199 深灰 电信3G手机 双卡双待双通', price=1269.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=1082721, title='<em style="color:red">华为</em> 麦芒B199 深灰色 电信3G手机 双模双待双通',price=1269.0, image='http://**.jpg',goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=917460, title='<em style="color:red">华为</em> P6 (P6-C00) 黑 电信3G手机 双卡双待双通',price=1288.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=917461, title='<em style="color:red">华为</em> P6 (P6-C00) 白 电信3G手机 双卡双待双通',price=1299.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=1060847, title='<em style="color:red">华为</em> Ascend P6S 阿尔卑斯白 联通3G手机 双卡双待',price=1328.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=1075413, title='<em style="color:red">华为</em> 麦芒B199 金 电信3G手机 双卡双待双通', price=1329.0, image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}},
TbItem{id=917770, title='<em style="color:red">华为</em> P6-C00 电信3G手机(粉色) CDMA2000/GSM 双模双待双通', image='http://**.jpg', goodsId=1, category='手机', brand='华为', seller='华为', specMap={网络=联通3G, 机身内存=16G}}]
*/
} @Test
public void add(){
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));
itemSearchService.add(item);
} @Test
public void searchById(){
TbItem tbItem = itemSearchService.searchById(536563);
System.out.println(tbItem.getTitle());
} @Test
public void deleteById(){
itemSearchService.deleteById("536563");
} @Test
public void pageQuery(){
ScoredPage<TbItem> page = itemSearchService.pageQuery(2, 10);
System.out.println("总记录数:"+page.getTotalElements());
List<TbItem> list = page.getContent();
for(TbItem item:list){
System.out.println(item.getTitle() +item.getPrice());
}
} @Test
public void deleteAll(){
itemSearchService.deleteAll();
}
}

到此为止,Spring Data Solr的基本使用基本就是这样。

上一篇:Solr学习笔记(5)—— Spring Data Solr入门


下一篇:MongoDB十二种最有效的模式设计【转】