1、pom.xml
<dependencies>
<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.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.7.2</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- IK中文分词器 -->
<!-- https://mvnrepository.com/artifact/com.github.magese/ik-analyzer -->
<dependency>
<groupId>com.github.magese</groupId>
<artifactId>ik-analyzer</artifactId>
<version>8.5.0</version>
</dependency>
</dependencies>
2、索引操作
2.1、创建索引库
@org.junit.Test
public void createIndexTest() throws Exception {
//1. 采集数据
SkuDao skuDao = new SkuDaoImpl();
List<Sku> skuList = skuDao.querySkuList();
//文档集合
List<Document> docList = new ArrayList<>();
for (Sku sku : skuList) {
//2. 创建文档对象
Document document = new Document();
//创建域对象并且放入文档对象中
/**
* 是否分词: 否, 因为主键分词后无意义
* 是否索引: 是, 如果根据id主键查询, 就必须索引
* 是否存储: 是, 因为主键id比较特殊, 可以确定唯一的一条数据, 在业务上一般有重要所用, 所以存储
* 存储后, 才可以获取到id具体的内容
*/
document.add(new StringField("id", sku.getId(), Field.Store.YES));
/**
* 是否分词: 是, 因为名称字段需要查询, 并且分词后有意义所以需要分词
* 是否索引: 是, 因为需要根据名称字段查询
* 是否存储: 是, 因为页面需要展示商品名称, 所以需要存储
*/
document.add(new TextField("name", sku.getName(), Field.Store.YES));
/**
* 是否分词: 是(因为lucene底层算法规定, 如果根据价格范围查询, 必须分词)
* 是否索引: 是, 需要根据价格进行范围查询, 所以必须索引
* 是否存储: 是, 因为页面需要展示价格
*/
document.add(new IntPoint("price", sku.getPrice()));
document.add(new StoredField("price", sku.getPrice()));
/**
* 是否分词: 否, 因为不查询, 所以不索引, 因为不索引所以不分词
* 是否索引: 否, 因为不需要根据图片地址路径查询
* 是否存储: 是, 因为页面需要展示商品图片
*/
document.add(new StoredField("image", sku.getImage()));
/**
* 是否分词: 否, 因为分类是专有名词, 是一个整体, 所以不分词
* 是否索引: 是, 因为需要根据分类查询
* 是否存储: 是, 因为页面需要展示分类
*/
document.add(new StringField("categoryName", sku.getCategoryName(), Field.Store.YES));
/**
* 是否分词: 否, 因为品牌是专有名词, 是一个整体, 所以不分词
* 是否索引: 是, 因为需要根据品牌进行查询
* 是否存储: 是, 因为页面需要展示品牌
*/
document.add(new StringField("brandName", sku.getBrandName(), Field.Store.YES));
//将文档对象放入到文档集合中
docList.add(document);
}
//3. 创建分词器, StandardAnalyzer标准分词器, 对英文分词效果好, 对中文是单字分词, 也就是一个字就认为是一个词.
Analyzer analyzer = new IKAnalyzer();
//4. 创建Directory目录对象, 目录对象表示索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建IndexWriterConfig对象, 这个对象中指定切分词使用的分词器
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//6. 创建IndexWriter输出流对象, 指定输出的位置和使用的config初始化对象
IndexWriter indexWriter = new IndexWriter(dir, config);
//7. 写入文档到索引库
for (Document doc : docList) {
indexWriter.addDocument(doc);
}
//8. 释放资源
indexWriter.close();
}
2.2、索引库修改操作
@org.junit.Test
public void updateIndexTest() throws Exception {
//需要变更成的内容
Document document = new Document();
document.add(new StringField("id", "100000003145", Field.Store.YES));
document.add(new TextField("name", "xxxx", Field.Store.YES));
document.add(new IntPoint("price", 123));
document.add(new StoredField("price", 123));
document.add(new StoredField("image", "xxxx.jpg"));
document.add(new StringField("categoryName", "手机", Field.Store.YES));
document.add(new StringField("brandName", "华为", Field.Store.YES));
//3. 创建分词器, StandardAnalyzer标准分词器, 对英文分词效果好, 对中文是单字分词, 也就是一个字就认为是一个词.
Analyzer analyzer = new StandardAnalyzer();
//4. 创建Directory目录对象, 目录对象表示索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建IndexWriterConfig对象, 这个对象中指定切分词使用的分词器
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//6. 创建IndexWriter输出流对象, 指定输出的位置和使用的config初始化对象
IndexWriter indexWriter = new IndexWriter(dir, config);
//修改, 第一个参数: 修改条件, 第二个参数: 修改成的内容
indexWriter.updateDocument(new Term("id", "100000003145"), document);
//8. 释放资源
indexWriter.close();
}
2.3、测试根据条件删除
@org.junit.Test
public void deleteIndexTest() throws Exception {
//3. 创建分词器, StandardAnalyzer标准分词器, 对英文分词效果好, 对中文是单字分词, 也就是一个字就认为是一个词.
Analyzer analyzer = new StandardAnalyzer();
//4. 创建Directory目录对象, 目录对象表示索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建IndexWriterConfig对象, 这个对象中指定切分词使用的分词器
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//6. 创建IndexWriter输出流对象, 指定输出的位置和使用的config初始化对象
IndexWriter indexWriter = new IndexWriter(dir, config);
//测试根据条件删除
//indexWriter.deleteDocuments(new Term("id", "100000003145"));
//测试删除所有内容
indexWriter.deleteAll();
//8. 释放资源
indexWriter.close();
}
2.4、测试创建索引速度优化
@org.junit.Test
public void createIndexTest2() throws Exception {
//1. 采集数据
SkuDao skuDao = new SkuDaoImpl();
List<Sku> skuList = skuDao.querySkuList();
//文档集合
List<Document> docList = new ArrayList<>();
for (Sku sku : skuList) {
//2. 创建文档对象
Document document = new Document();
document.add(new StringField("id", sku.getId(), Field.Store.YES));
document.add(new TextField("name", sku.getName(), Field.Store.YES));
document.add(new IntPoint("price", sku.getPrice()));
document.add(new StoredField("price", sku.getPrice()));
document.add(new StoredField("image", sku.getImage()));
document.add(new StringField("categoryName", sku.getCategoryName(), Field.Store.YES));
document.add(new StringField("brandName", sku.getBrandName(), Field.Store.YES));
//将文档对象放入到文档集合中
docList.add(document);
}
long start = System.currentTimeMillis();
//3. 创建分词器, StandardAnalyzer标准分词器, 对英文分词效果好, 对中文是单字分词, 也就是一个字就认为是一个词.
Analyzer analyzer = new StandardAnalyzer();
//4. 创建Directory目录对象, 目录对象表示索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建IndexWriterConfig对象, 这个对象中指定切分词使用的分词器
/**
* 没有优化 小100万条数据, 创建索引需要7725ms
*
*/
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//设置在内存中多少个文档向磁盘中批量写入一次数据
//如果设置的数字过大, 会过多消耗内存, 但是会提升写入磁盘的速度
//config.setMaxBufferedDocs(500000);
//6. 创建IndexWriter输出流对象, 指定输出的位置和使用的config初始化对象
IndexWriter indexWriter = new IndexWriter(dir, config);
//设置多少给文档合并成一个段文件,数值越大索引速度越快, 搜索速度越慢; 值越小索引速度越慢, 搜索速度越快
//indexWriter.forceMerge(1000000);
//7. 写入文档到索引库
for (Document doc : docList) {
indexWriter.addDocument(doc);
}
//8. 释放资源
indexWriter.close();
long end = System.currentTimeMillis();
System.out.println("=====消耗的时间为:==========" + (end - start) + "ms");
}
3、高级搜索
3.1、关键字查询
@Test
public void testIndexSearch() throws Exception {
//1. 创建分词器(对搜索的关键词进行分词使用)
//注意: 分词器要和创建索引的时候使用的分词器一模一样
Analyzer analyzer = new StandardAnalyzer();
//2. 创建查询对象,
//第一个参数: 默认查询域, 如果查询的关键字中带搜索的域名, 则从指定域中查询, 如果不带域名则从, 默认搜索域中查询
//第二个参数: 使用的分词器
QueryParser queryParser = new QueryParser("name", analyzer);
//3. 设置搜索关键词
//华 OR 为 手 机
Query query = queryParser.parse("华为手机");
//4. 创建Directory目录对象, 指定索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建输入流对象
IndexReader indexReader = DirectoryReader.open(dir);
//6. 创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//7. 搜索, 并返回结果
//第二个参数: 是返回多少条数据用于展示, 分页使用
TopDocs topDocs = indexSearcher.search(query, 10);
//获取查询到的结果集的总数, 打印
System.out.println("=======count=======" + topDocs.totalHits);
//8. 获取结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//9. 遍历结果集
if (scoreDocs != null) {
for (ScoreDoc scoreDoc : scoreDocs) {
//获取查询到的文档唯一标识, 文档id, 这个id是lucene在创建文档的时候自动分配的
int docID = scoreDoc.doc;
//通过文档id, 读取文档
Document doc = indexSearcher.doc(docID);
System.out.println("==================================================");
//通过域名, 从文档中获取域值
System.out.println("===id==" + doc.get("id"));
System.out.println("===name==" + doc.get("name"));
System.out.println("===price==" + doc.get("price"));
System.out.println("===image==" + doc.get("image"));
System.out.println("===brandName==" + doc.get("brandName"));
System.out.println("===categoryName==" + doc.get("categoryName"));
}
}
//10. 关闭流
}
3.2、数值范围查询
@Test
public void testRangeQuery() throws Exception {
//1. 创建分词器(对搜索的关键词进行分词使用)
//注意: 分词器要和创建索引的时候使用的分词器一模一样
Analyzer analyzer = new StandardAnalyzer();
//2. 创建查询对象,
Query query = IntPoint.newRangeQuery("price", 100, 1000);
//4. 创建Directory目录对象, 指定索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建输入流对象
IndexReader indexReader = DirectoryReader.open(dir);
//6. 创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//7. 搜索, 并返回结果
//第二个参数: 是返回多少条数据用于展示, 分页使用
TopDocs topDocs = indexSearcher.search(query, 10);
//获取查询到的结果集的总数, 打印
System.out.println("=======count=======" + topDocs.totalHits);
//8. 获取结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//9. 遍历结果集
if (scoreDocs != null) {
for (ScoreDoc scoreDoc : scoreDocs) {
//获取查询到的文档唯一标识, 文档id, 这个id是lucene在创建文档的时候自动分配的
int docID = scoreDoc.doc;
//通过文档id, 读取文档
Document doc = indexSearcher.doc(docID);
System.out.println("==================================================");
//通过域名, 从文档中获取域值
System.out.println("===id==" + doc.get("id"));
System.out.println("===name==" + doc.get("name"));
System.out.println("===price==" + doc.get("price"));
System.out.println("===image==" + doc.get("image"));
System.out.println("===brandName==" + doc.get("brandName"));
System.out.println("===categoryName==" + doc.get("categoryName"));
}
}
//10. 关闭流
}
3.3、组合查询
@Test
public void testBooleanQuery() throws Exception {
//1. 创建分词器(对搜索的关键词进行分词使用)
//注意: 分词器要和创建索引的时候使用的分词器一模一样
Analyzer analyzer = new StandardAnalyzer();
//2. 创建查询对象,
Query query1 = IntPoint.newRangeQuery("price", 100, 1000);
QueryParser queryParser = new QueryParser("name", analyzer);
//3. 设置搜索关键词
//华 OR 为 手 机
Query query2 = queryParser.parse("华为手机");
//创建布尔查询对象(组合查询对象)
/**
* BooleanClause.Occur.MUST 必须相当于and, 也就是并且的关系
* BooleanClause.Occur.SHOULD 应该相当于or, 也就是或者的关系
* BooleanClause.Occur.MUST_NOT 不必须, 相当于not, 非
* 注意: 如果查询条件都是MUST_NOT, 或者只有一个查询条件, 然后这一个查询条件是MUST_NOT则
* 查询不出任何数据.
*/
BooleanQuery.Builder query = new BooleanQuery.Builder();
query.add(query1, BooleanClause.Occur.MUST);
query.add(query2, BooleanClause.Occur.MUST);
//4. 创建Directory目录对象, 指定索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建输入流对象
IndexReader indexReader = DirectoryReader.open(dir);
//6. 创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//7. 搜索, 并返回结果
//第二个参数: 是返回多少条数据用于展示, 分页使用
TopDocs topDocs = indexSearcher.search(query.build(), 10);
//获取查询到的结果集的总数, 打印
System.out.println("=======count=======" + topDocs.totalHits);
//8. 获取结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//9. 遍历结果集
if (scoreDocs != null) {
for (ScoreDoc scoreDoc : scoreDocs) {
//获取查询到的文档唯一标识, 文档id, 这个id是lucene在创建文档的时候自动分配的
int docID = scoreDoc.doc;
//通过文档id, 读取文档
Document doc = indexSearcher.doc(docID);
System.out.println("==================================================");
//通过域名, 从文档中获取域值
System.out.println("===id==" + doc.get("id"));
System.out.println("===name==" + doc.get("name"));
System.out.println("===price==" + doc.get("price"));
System.out.println("===image==" + doc.get("image"));
System.out.println("===brandName==" + doc.get("brandName"));
System.out.println("===categoryName==" + doc.get("categoryName"));
}
}
//10. 关闭流
}
3.4、测试相关度排序
@Test
public void testIndexSearch2() throws Exception {
//1. 创建分词器(对搜索的关键词进行分词使用)
//注意: 分词器要和创建索引的时候使用的分词器一模一样
Analyzer analyzer = new IKAnalyzer();
//需求: 不管是名称域还是品牌域或者是分类域有关于手机关键字的查询出来
//查询的多个域名
String[] fields = {"name", "categoryName", "brandName"};
//设置影响排序的权重, 这里设置域的权重
Map<String, Float> boots = new HashMap<>();
boots.put("categoryName", 10000000000f);
//从多个域查询对象
MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer, boots);
//设置查询的关键词
Query query = multiFieldQueryParser.parse("手机");
//4. 创建Directory目录对象, 指定索引库的位置
Directory dir = FSDirectory.open(Paths.get("D:\\dir"));
//5. 创建输入流对象
IndexReader indexReader = DirectoryReader.open(dir);
//6. 创建搜索对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//7. 搜索, 并返回结果
//第二个参数: 是返回多少条数据用于展示, 分页使用
TopDocs topDocs = indexSearcher.search(query, 10);
//获取查询到的结果集的总数, 打印
System.out.println("=======count=======" + topDocs.totalHits);
//8. 获取结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//9. 遍历结果集
if (scoreDocs != null) {
for (ScoreDoc scoreDoc : scoreDocs) {
//获取查询到的文档唯一标识, 文档id, 这个id是lucene在创建文档的时候自动分配的
int docID = scoreDoc.doc;
//通过文档id, 读取文档
Document doc = indexSearcher.doc(docID);
System.out.println("==================================================");
//通过域名, 从文档中获取域值
System.out.println("===id==" + doc.get("id"));
System.out.println("===name==" + doc.get("name"));
System.out.println("===price==" + doc.get("price"));
System.out.println("===image==" + doc.get("image"));
System.out.println("===brandName==" + doc.get("brandName"));
System.out.println("===categoryName==" + doc.get("categoryName"));
}
}
//10. 关闭流
}