引言
在阅读此文前,最好了解下【lucene原理】,这样有助于你能更好的理解此文
lucene原理:https://blog.csdn.net/yb546822612/article/details/103063493
如果觉得那文章太长,看得两眼发花,也可以下载在demo先跑起来,过把隐,然后对照此文边看边理解
demo 码云地址在最底部
一、 新建springboot项目
1、项目结构如下:
引用jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.7</version>
</dependency>
<!--对分词索引查询解析-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>8.3.0</version>
</dependency>
<!--高亮 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>8.3.0</version>
</dependency>
<!--smartcn 中文分词器 SmartChineseAnalyzer smartcn分词器 需要lucene依赖 且和lucene版本同步-->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>8.3.0</version>
</dependency>
<!--ik-analyzer 中文分词器-->
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>ik-analyzers</artifactId>
<version>5.1.0</version>
</dependency>
<!--MMSeg4j 分词器-->
<dependency>
<groupId>com.chenlb.mmseg4j</groupId>
<artifactId>mmseg4j-solr</artifactId>
<version>2.4.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
此处用的最新的lucene版本8.3.0
2、编写lucene配制文件
@Configuration
public class LuceneConfig {
/**
* lucene索引,存放位置
*/
public static final String LUCENEINDEXPATH="lucene/indexDir/";
/**
* 创建一个 Analyzer 实例
*
* @return
*/
@Bean
public Analyzer analyzer() {
return new SmartChineseAnalyzer();
}
/**
* 索引位置
*
* @return
* @throws IOException
*/
@Bean
public Directory directory() throws IOException {
Path path = Paths.get(LUCENEINDEXPATH);
File file = path.toFile();
if(!file.exists()) {
//如果文件夹不存在,则创建
file.mkdirs();
}
return FSDirectory.open(path);
}
/**
* 创建indexWriter
*
* @param directory
* @param analyzer
* @return
* @throws IOException
*/
@Bean
public IndexWriter indexWriter(Directory directory, Analyzer analyzer) throws IOException {
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
/**
* 清空索引,下面两个方法,打开时,启动应用时,会自动清空所有索引,
* 如果项目的数据比较多,建索引时间长或索引不能清空,这个一定要装着
* 所以大家要根据实际情况慎用此开关
*/
indexWriter.deleteAll();
indexWriter.commit();
return indexWriter;
}
/**
* SearcherManager管理
*
* @param directory
* @return
* @throws IOException
*/
@Bean
public SearcherManager searcherManager(Directory directory, IndexWriter indexWriter) throws IOException {
SearcherManager searcherManager = new SearcherManager(indexWriter, false, false, new SearcherFactory());
ControlledRealTimeReopenThread cRTReopenThead = new ControlledRealTimeReopenThread(indexWriter, searcherManager,
5.0, 0.025);
cRTReopenThead.setDaemon(true);
//线程名称
cRTReopenThead.setName("更新IndexReader线程");
// 开启线程
cRTReopenThead.start();
return searcherManager;
}
}
注意很上述:
@Bean
public IndexWriter indexWriter(Directory directory, Analyzer analyzer) throws IOException {
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
/**
* 清空索引,下面两个方法,打开时,启动应用时,会自动清空所有索引,
* 如果项目的数据比较多,建索引时间长或索引不能清空,这个一定要装着
* 所以大家要根据实际情况慎用此开关
*/
indexWriter.deleteAll();
indexWriter.commit();
return indexWriter;
}
3、编写service
public interface LuceneService {
/**
* 增加索引
* @param list
* @throws IOException
*/
public void createProductIndex(List<Product> list) throws IOException;
/**
* 查询
* @param pageQuery
* @return
* @throws Exception
* @throws ParseException
*/
public PageQuery searchProduct(PageQuery pageQuery) throws Exception, ParseException;
/**
*删除
* @param id
* @throws IOException
*/
public void deleteProductIndexById(String id) throws IOException;
}
service实现
@Service
public class LuceneServiceImpl implements LuceneService {
@Autowired
private IndexWriter indexWriter;
@Override
public void createProductIndex(List<Product> productList) throws IOException {
List<Document> docs = new ArrayList<Document>();
for (Product p : productList) {
Document doc = new Document();
doc.add(new StringField("id", p.getId() + "", Field.Store.YES));
doc.add(new TextField("skuName", p.getSkuName(), Field.Store.YES));
doc.add(new TextField("cid1Name", p.getCid1Name(), Field.Store.YES));
doc.add(new TextField("clickurl", p.getClickurl(), Field.Store.YES));
Double price = p.getPrice();
doc.add(new DoublePoint("price", price));
doc.add(new StoredField("price", p.getPrice()));
doc.add(new DoublePoint("discount", p.getDiscount()));
doc.add(new StoredField("discount", p.getDiscount()));
doc.add(new DoublePoint("discountRate", p.getDiscountRate()));
doc.add(new StoredField("discountRate", p.getDiscountRate()));
doc.add(new DoublePoint("cid1", p.getCid1()));
// 正排索引用于排序、聚合
doc.add(new DoubleDocValuesField("price", price));
// 存储到索引库
doc.add(new StoredField("cid1", p.getCid1()));
docs.add(doc);
}
indexWriter.addDocuments(docs);
// indexWriter.updateDocument(docs);
indexWriter.commit();
// indexWriter.close();
}
@Autowired
private Analyzer analyzer;
@Autowired
private SearcherManager searcherManager;
@Override
public PageQuery searchProduct(PageQuery pageQuery) throws Exception, ParseException {
searcherManager.maybeRefresh();
IndexSearcher indexSearcher = searcherManager.acquire();
// Analyzer analyzer = new IKAnalyzer(true);
// DirectoryReader directoryReader = DirectoryReader.open(FSDirectory.open(Paths.get(LuceneConfig.LUCENEINDEXPATH)));
// //索引查询器
// IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
Product params = pageQuery.getParams();
Map<String, String> queryParam = pageQuery.getQueryParam();
BooleanQuery.Builder builder = new BooleanQuery.Builder();
Sort sort = new Sort();
// 排序规则
com.test.lucene.model.Sort sort1 = pageQuery.getSort();
if (sort1 != null && sort1.getOrder() != null) {
if ("ASC".equals(sort1.getOrder().toUpperCase())) {
sort.setSort(new SortField(sort1.getField(), SortField.Type.FLOAT, false));
} else if ("DESC".equals((sort1.getOrder()).toUpperCase())) {
sort.setSort(new SortField(sort1.getField(), SortField.Type.FLOAT, true));
}
}
// 模糊匹配,匹配词
String keyStr = params.getSkuName();
if (params != null && params.getSkuName() != null) {
// 输入空格,不进行模糊查询
if (!"".equals(keyStr.replaceAll(" ", ""))) {
builder.add(new QueryParser("skuName", analyzer).parse(keyStr), BooleanClause.Occur.MUST);
}
}
// 精确查询
if (params != null && params.getCid1() != null) {
builder.add(new TermQuery(new Term("cid1", String.valueOf(params.getCid1()))), BooleanClause.Occur.MUST);
}
// if (params != null && params.getGetEndTimeForL() != null) {
// builder.add(new TermQuery(new Term("cid1", String.valueOf(params.getCid1()))), BooleanClause.Occur.MUST);
// }
// if (queryParam.get("lowerPrice") != null && queryParam.get("upperPrice") != null) {
// // 价格范围查询
// builder.add(FloatPoint.newRangeQuery("price", Float.parseFloat(queryParam.get("lowerPrice")),
// Float.parseFloat(queryParam.get("upperPrice"))), BooleanClause.Occur.MUST);
// }
PageInfo pageInfo = pageQuery.getPageInfo();
TopDocs topDocs = indexSearcher.search(builder.build(), pageInfo.getPageNum() * pageInfo.getPageSize(), sort);
pageInfo.setTotal(topDocs.totalHits.value);
ScoreDoc[] hits = topDocs.scoreDocs;
List<Product> pList = new ArrayList<Product>();
for (int i = 0; i < hits.length; i++) {
Document doc = indexSearcher.doc(hits[i].doc);
Product Product = new Product();
Product.setId(Integer.parseInt(doc.get("id")));
Product.setSkuName(doc.get("skuName"));
Product.setCid1(Long.valueOf(doc.get("cid1")));
Product.setCid1Name(doc.get("cid1Name"));
Product.setCid1Name(doc.get("cid1Name"));
Product.setUrl(doc.get("url"));
Product.setMaterialUrl(doc.get("materialUrl"));
Product.setClickurl(doc.get("clickurl"));
Product.setPrice(Double.valueOf(doc.get("price")));
Product.setCommissionShare(doc.get("commissionShare") != null ?Double.valueOf(doc.get("commissionShare")):null);
Product.setDiscount(doc.get("discount") != null ? Double.valueOf(doc.get("discount")):null);
Product.setDiscountRate(doc.get("discountRate") != null ? Double.valueOf(doc.get("discountRate")):null);
Product.setPingouPrice(doc.get("pingouPrice") != null ? Double.valueOf(doc.get("pingouPrice")):null);
Product.setLink(doc.get("link"));
pList.add(Product);
}
pageQuery.setPageInfo(pageInfo);
pageQuery.setResults(pList);
return pageQuery;
}
@Override
public void deleteProductIndexById(String id) throws IOException {
indexWriter.deleteDocuments(new Term("id",id));
indexWriter.commit();
}
}
三、运行测试
1、生成索引
@RequestMapping("creat")
@ResponseBody
public String create() throws Exception{
List<Product> list = new ArrayList<Product>();
Product product = new Product();
product.setId(1);
product.setBindType(10);
product.setBrandCode("001");
product.setBrandName("联想");
product.setCid1(10000L);
product.setCid1Name("电脑");
product.setClickurl("www.baidu.com");
product.setComments(100L);
product.setCommission(10.0);
product.setCommissionShare(20.0);
product.setCreateTime(new Date());
product.setDesc("desc");
product.setDiscount(10.0);
product.setDiscountRate(30.0);
product.setSkuId(635373L);
product.setPrice(999.01);
product.setSkuName("硕扬 i7升十二线程/RX560 4G独显/16G内存办公游戏台式组装电脑主机DIY组装机");
list.add(product);
luceneService.createProductIndex(list);
return "hello world" ;
}
访问 localhost:8080/create后
发现 F:\git-resp\test\test-demo\lucene\indexDir 目录生成以下文件,其实这就是索引文件,
这个目录地址就是LuceneConfig文件中配制的LUCENEINDEXPATH地址
public static final String LUCENEINDEXPATH="lucene/indexDir/";
与你项目的根目录地址相同
2、查询索引
controler代码
@RequestMapping("find")
@ResponseBody
public PageInfo<Product> find(@RequestBody Product pro) throws Exception{
PageInfo pageInfo = new PageInfo();
pageInfo.setPageNum(pro.getPage());
pageInfo.setPageSize(pro.getLimit());
PageQuery pageQuery = new PageQuery();
pageQuery.setPageInfo(pageInfo);
pageQuery.setParams(pro);
PageQuery result = luceneService.searchProduct(pageQuery);
PageInfo<Product> pageResult = new PageInfo<Product>();
pageResult.setPageSize(pro.getLimit());
pageResult.setPageNum(pro.getPage());
pageResult.setTotal(result.getPageInfo().getTotal());
pageResult.setList(result.getResults());
return pageResult;
}
post请求
返回结果
{
"total": 1,
"list": [
{
"id": 1,
"cid1": 10000,
"cid1Name": "电脑",
"commission": null,
"commissionShare": null,
"bindType": null,
"brandCode": null,
"brandName": null,
"comments": null,
"discount": 10.0,
"eliteId": null,
"eliteName": null,
"getEndTime": null,
"getStartTime": null,
"link": null,
"materialUrl": null,
"owner": null,
"pingouPrice": null,
"pingouTmCount": null,
"pingouUrl": null,
"price": 999.01,
"quota": null,
"shopId": null,
"shopName": null,
"skuId": null,
"skuName": "硕扬 i7升十二线程/RX560 4G独显/16G内存办公游戏台式组装电脑主机DIY组装机",
"spuid": null,
"url": null,
"useEndTime": null,
"useStartTime": null,
"clickurl": "www.baidu.com",
"discountRate": 30.0,
"updateTime": null,
"createTime": null,
"page": null,
"limit": null,
"desc": null
}
],
"pageNum": 1,
"pageSize": 10,
"size": 0,
"startRow": 0,
"endRow": 0,
"pages": 0,
"prePage": 0,
"nextPage": 0,
"isFirstPage": false,
"isLastPage": false,
"hasPreviousPage": false,
"hasNextPage": false,
"navigatePages": 0,
"navigatepageNums": null,
"navigateFirstPage": 0,
"navigateLastPage": 0
}
看到这里,说明我们springboot集成lucene成功了
demo 码云地址:https://gitee.com/xing_xin/springboot-lucene.git