Lucene-Java

看前须知

简单使用,直接上手写,无原理,只有自己的理解。(欢迎补充,指正)

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封装的)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值