看前须知
简单使用,直接上手写,无原理,只有自己的理解。(欢迎补充,指正)
1.加入依赖
我使用的是springboot web项目,直接在maven中加入的lucene相关依赖。其它的web依赖我这里就不放上来了。
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.7.2</version>
</dependency>
<!--
<dependency>
<groupId>org.wltea.ik-analyzer</groupId>
<artifactId>ik-analyzer</artifactId>
<version>8.1.0</version>
</dependency>
-->
上面的依赖包被我注释掉了,原因是我下载不下来,不知道是我写错了还是什么其它原因,在下面添加了另一个依赖,但是查询的时候出问题了(不影响,没有这个依赖也行,我用的标准分词器)。
<dependency>
<groupId>com.github.magese</groupId>
<artifactId>ik-analyzer</artifactId>
<version>7.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.7.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--Lucene用到的依赖包-->
2.增
我们先围绕着增删改查来写,首先是要创建索引库,也就是创建一个空文件夹存放索引文件,无中文路径即可。比如:
在创建完索引后,我们直接上手写代码,首先是要将你需要使用的数据放入lucene。代码如下:
@Autowired
private ITbSkuService iTbSkuService;
//往索引库添加
@RequestMapping("/insertData")
public Object insertData() throws IOException {
//1.采集数据
List<TbSku> tbSkuList = iTbSkuService.list();
//2.创建Document文档对象
List<Document> documentList = new ArrayList<Document>();
for (TbSku tbSku : tbSkuList){
Document document = new Document();
/**
* ID
* 是否分词:不分词,因为ID拆分成一个一个词便没有意义
* 是否索引:索引,如果根据主键查询就需要索引。
* 是否存储:是,ID为主键,也是业务ID,需要用到。
*/
document.add(new StringField("id",tbSku.getId(), Field.Store.YES));
document.add(new TextField("name",tbSku.getName(), Field.Store.YES));
//价格必须分词,lucene规定,配个storedField进行存储
document.add(new IntPoint("price",tbSku.getPrice()));
document.add(new StoredField("price",tbSku.getPrice()));
document.add(new StringField("brandName",tbSku.getBrandName(), Field.Store.YES));
document.add(new StringField("categoryName",tbSku.getCategoryName(), Field.Store.YES));
document.add(new StoredField("image",tbSku.getImage()));
documentList.add(document);
}
//3.创建分词器,对文档进行分词
Analyzer analyzer = new StandardAnalyzer();
//4.声明索引库位置
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
//5.写入索引需要的配置
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
//6.创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
//7.写入到索引库
for (Document document : documentList){
indexWriter.addDocument(document);
}
//8.释放资源
indexWriter.close();
return "ok";
}
稍微讲一下,我用的mybatis-plus的方法直接将数据库的数据查出来了大概一百万条,如果你用的mybatis或jdbc就使用你对应的方法。
创建文档,文档你就当做是柜子,一层一层的,一条数据就是一个文档,lucene会为每一个文档发放一个唯一ID,文档的长度多长呢,和你查出来的数据一样长,一个循环搞定。
你一个对象可能有十几个成员变量,你只存你需要的,用得上的也就是document.add(),后面new的对象不太一样可以参考:
是否分词 是否索引 是否存储(对象下面的字母)
StringField(FieldName,FieldValue,Store.YES))N,Y,Y或N
FloatPoint(FieldName, FieldValue)Y,Y ,N
DoublePoint(FieldName,FieldValue)Y,Y,N
LongPoint(FieldName, FieldValue) Y,Y,N
IntPoint(FieldName, FieldValue)Y,Y,N
StoredField(FieldName, FieldValue)N,N,Y
TextField(FieldName, FieldValue,Store.NO) 或 TextField(FieldName,reader)Y,Y,Y或N
举例 name字段 你肯定需要用name来进行查询 所以需要索引,也需要分词(将华为手机分为 华 为 手机)一个字就是一个词,是否存储呢,如果你不展示到页面就不储存,如果要展示则存储,否则为null。
ID字段,肯定不用ID来查询吧,所以不索引,ID如果分为1 2 5 8 这样的则没有意义所以不分词,需要存储,因为ID是数据库唯一的业务ID吗,你后面肯定用的上所以Y。
创建分词器,StandardAnalyzer对英雄分词效果比较好,会去掉标点符号空格,大写字母转小写,is a an都会无视,IK分词器对中文分词效果比较好,(我这里依赖没弄好所以是标准分词器)两者区别 比如华为手机 StandardAnalyzer:华 为 手 机,IK:华为 手机,有中文语义分析,接着声明索引库位置,也就是图一那个位置。如果出现图一相关文件就说明放入成功,可以下载一个LUKE工具查看索引库内容。
删除
代码如下:
public String deleteIndex() throws Exception{
Analyzer analyzer = new StandardAnalyzer();
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
indexWriter.deleteDocuments(new Term("id","998188"));
indexWriter.close();
return "ok";
}
创建分词器,声明索引库为期,deleteDoucments()方法,new一个term对象,key,value传入进入,字段名,值。
删除所有(慎用)
public String deleteAllIndex() throws Exception{
Analyzer analyzer = new StandardAnalyzer();
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
indexWriter.deleteAll();
indexWriter.close();
return "ok";
}
修改
public String updateIndex() throws Exception{
//分词器 跟创建时要使用一致
Analyzer analyzer = new StandardAnalyzer();
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
Document document = new Document();
document.add(new StringField("id","998188", Field.Store.YES));
document.add(new TextField("name","修改后的商品", Field.Store.YES));
//价格必须分词,lucene规定,配个storedField进行存储
document.add(new IntPoint("price",100));
document.add(new StoredField("price",100));
document.add(new StringField("brandName","chris", Field.Store.YES));
document.add(new StringField("categoryName","martin", Field.Store.YES));
document.add(new StoredField("image","xxx"));
indexWriter.updateDocument(new Term("id","998188"),document);
indexWriter.close();
return "ok";
}
注:你添加进去的时候用的什么分词器,修改要用同样的,这里修改是直接将原有数据从索引库删除,然后再添加。
查询
public Object queryIndex() throws Exception {
//1.创建分词器,创建query搜索对象
Analyzer analyzer = new StandardAnalyzer();
//创建搜索解析器 参数1、默认字段域 参数2、分词器
QueryParser queryParser = new QueryParser("name",analyzer);
//创建搜索对象
Query query = queryParser.parse("华为手机");
//2.创建流对象,声明索引库位置
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
//3.创建索引读取对象
IndexReader indexReader = DirectoryReader.open(directory);
//4.创建索引搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//5.使用索引搜索对象,执行搜索,返回结果集TopDocs
//1.搜索对象,2.返回条数;
TopDocs topDocs = indexSearcher.search(query,10);
System.out.println("总条数:"+topDocs.totalHits);
//获取查询结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
if (scoreDocs != null){
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document document = indexReader.document(docId);
System.out.println("====================================");
System.out.println("id="+document.get("id"));
System.out.println("name="+document.get("name"));
System.out.println("brandName="+document.get("brandName"));
System.out.println("categoryName="+document.get("categoryName"));
}
}
indexReader.close();
return "ok";
}
注:queryParse只能对文本进行查询,indexSearcher.serch第二个参数是你要查询多少条,topDocs.totalHits是查询结果的总条数。下面的循环你可以直接放入对象集合中返回,我这里只做了打印。
对数值进行查询
public String numberQuery() throws Exception{
//1.创建分词器,创建query搜索对象
Analyzer analyzer = new StandardAnalyzer();
Query query = IntPoint.newRangeQuery("price",100,1000);
//2.创建流对象,声明索引库位置
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
//3.创建索引读取对象
IndexReader indexReader = DirectoryReader.open(directory);
//4.创建索引搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//5.使用索引搜索对象,执行搜索,返回结果集TopDocs
//1.搜索对象,2.返回条数;
TopDocs topDocs = indexSearcher.search(query,10);
System.out.println("总条数:"+topDocs.totalHits);
//获取查询结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
if (scoreDocs != null){
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document document = indexReader.document(docId);
System.out.println("====================================");
System.out.println("id="+document.get("id"));
System.out.println("name="+document.get("name"));
System.out.println("brandName="+document.get("brandName"));
System.out.println("price="+document.get("price"));
System.out.println("categoryName="+document.get("categoryName"));
}
}
indexReader.close();
return "ok";
}
注:查询100-1000价格的物品
组合查询
public String mixQuery() throws Exception{
//1.创建分词器,创建query搜索对象
Analyzer analyzer = new StandardAnalyzer();
Query query1 = IntPoint.newRangeQuery("price",100,1000);
QueryParser queryParser = new QueryParser("name",analyzer);
Query query2 = queryParser.parse("华为 AND 手机");
//创建组合查询对象
BooleanQuery.Builder query = new BooleanQuery.Builder();
//Must就是and代表两者都要满足 should就是or满足其一 mustnot就是not相当于非(不用这个,如果查询条件都是Mustnot,或者只有一个查询条件它是mustnot则查询不到任何数据)
query.add(query1, BooleanClause.Occur.MUST);
query.add(query2, BooleanClause.Occur.MUST);
//2.创建流对象,声明索引库位置
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
//3.创建索引读取对象
IndexReader indexReader = DirectoryReader.open(directory);
//4.创建索引搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//5.使用索引搜索对象,执行搜索,返回结果集TopDocs
//1.搜索对象,2.返回条数;
TopDocs topDocs = indexSearcher.search(query.build(),10);
System.out.println("总条数:"+topDocs.totalHits);
//获取查询结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
if (scoreDocs != null){
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document document = indexReader.document(docId);
System.out.println("====================================");
System.out.println("id="+document.get("id"));
System.out.println("name="+document.get("name"));
System.out.println("brandName="+document.get("brandName"));
System.out.println("price="+document.get("price"));
System.out.println("categoryName="+document.get("categoryName"));
}
}
indexReader.close();
return "ok";
}
注:组合查询 价格在100-1000之间,并且名字是华为手机,使用BooleanQuery的内部类对象构建查询条件,query1使用intponit还是其他的取决于你放进去的时候是什么格式。
例子
如现有一个页面,页面有一个搜索框(queryString),有一个价格范围的选择(price),且可以翻页,去索引库中查询。
Controller如下:
@Autowired
private SearchService searchService;
/**
* 搜索
* @param queryString 关键字
* @param price 价格范围
* @param page 当前页
* @return
* @throws Exception
*/
@RequestMapping
public String query(String queryString, String price, Integer page, Model model)throws Exception{
if(StringUtils.isEmpty(page)){
page = 1;
}
if (page <= 0){
page = 1;
}
ResultModel resultModel = searchService.query(queryString,price,page);
model.addAttribute("result",resultModel);
model.addAttribute("queryString",queryString);
model.addAttribute("price",price);
model.addAttribute("page",page);
return "search";
}
}
Service如下:
public interface SearchService {
ResultModel query(String queryString, String price, Integer page)throws Exception;
}
实现类如下:
@Service
public class SearchServiceImpl implements SearchService {
//每页展示查询20条
public static final Integer PAGE_SIZE = 20;
@Override
public ResultModel query(String queryString, String price, Integer page) throws Exception {
ResultModel resultModel = new ResultModel();
//从第几条开始查询,查询到多少条为止
int start = (page - 1) * PAGE_SIZE;
int end = page * PAGE_SIZE;
Analyzer analyzer = new StandardAnalyzer();
//组合查询对象
BooleanQuery.Builder builder = new BooleanQuery.Builder();
QueryParser queryParser = new QueryParser("name",analyzer);
Query query1 = null;
if (StringUtils.isEmpty(queryString)){
query1 = queryParser.parse("*:*");
}else{
query1 = queryParser.parse(queryString);
}
builder.add(query1, BooleanClause.Occur.MUST);
//价格 0-500
if (!StringUtils.isEmpty(price)){
String[] split = price.split("-");
Query query2 = IntPoint.newRangeQuery("price",Integer.parseInt(split[0]),Integer.parseInt(split[1].toString()));
builder.add(query2, BooleanClause.Occur.MUST);
}
Directory directory = FSDirectory.open(Paths.get("F:\\study_data\\lecene\\lucene_index"));
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(builder.build(), end);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List<TbSku> tbSkuList = new ArrayList<TbSku>();
if (scoreDocs!=null){
//看40行,从哪开始,到哪结束 例第一页:从0开始到19
for (int i=start;i<end;i++) {
Document document = indexReader.document(scoreDocs[i].doc);
TbSku tbSku = new TbSku();
tbSku.setId(document.get("id"));
tbSku.setName(document.get("name"));
tbSku.setBrandName(document.get("brandName"));
tbSku.setCategoryName(document.get("categoryName"));
tbSku.setPrice(Integer.parseInt(document.get("price")));
tbSku.setImage(document.get("image"));
tbSkuList.add(tbSku);
}
}
//总条数
resultModel.setRecordCount(topDocs.totalHits);
//结果集
resultModel.setSkuList(tbSkuList);
//当前页
resultModel.setCurPage(page.longValue());
//总页数
Long pageCount = topDocs.totalHits % PAGE_SIZE>0?(topDocs.totalHits/PAGE_SIZE)+1:topDocs.totalHits/PAGE_SIZE;
resultModel.setPageCount(pageCount);
return resultModel;
}
}
住:没写太多注释,大概意思就是每页展示20条,如果是第一页就是(1-1)20从0开始查,查到120条,如果是第二页就是(2-1)20从20开始查,查到220条以此类推,后面直接去索引库查询,方法和组合查询一个意思,最终封装成你想要的结果返回页面即可。
结尾
完成上面的例子,可以说已经完成增删改查的基础操作了,原理之类的半懂不懂,建议看一下官方解释,或B站搜一搜大佬讲的(这个文章也是我看完别人讲解后整理的,当做一个学习记录),第一次写,可能有点问题,欢迎大家指正和讨论,(现在比较流行ES和SOLR,貌似是基于lucene封装的)。