最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解SpringBoot+Lucene案例介紹

 更新時(shí)間:2019年06月26日 14:10:45   作者:滄海一粟  
這篇文章主要介紹了詳解SpringBoot+Lucene案例介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一、案例介紹

  1. 模擬一個(gè)商品的站內(nèi)搜索系統(tǒng)(類似淘寶的站內(nèi)搜索);
  2. 商品詳情保存在mysql數(shù)據(jù)庫(kù)的product表中,使用mybatis框架;
  3. 站內(nèi)查詢使用Lucene創(chuàng)建索引,進(jìn)行全文檢索;
  4. 增、刪、改,商品需要對(duì)Lucene索引修改,搜索也要達(dá)到近實(shí)時(shí)的效果。

對(duì)于數(shù)據(jù)庫(kù)的操作和配置就不在本文中體現(xiàn),主要講解與Lucene的整合。

二、引入lucene的依賴

向pom文件中引入依賴

    <!--核心包-->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>7.6.0</version>
    </dependency>
    <!--對(duì)分詞索引查詢解析-->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-queryparser</artifactId>
      <version>7.6.0</version>
    </dependency>
    <!--一般分詞器,適用于英文分詞-->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-common</artifactId>
      <version>7.6.0</version>
    </dependency>
    <!--檢索關(guān)鍵字高亮顯示 -->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-highlighter</artifactId>
      <version>7.6.0</version>
    </dependency>
    <!-- smartcn中文分詞器 -->
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-smartcn</artifactId>
      <version>7.6.0</version>
    </dependency>

三、配置初始化Bean類

初始化bean類需要知道的幾點(diǎn):

1.實(shí)例化 IndexWriter,IndexSearcher 都需要去加載索引文件夾,實(shí)例化是是非常消耗資源的,所以我們希望只實(shí)例化一次交給spring管理。

2.IndexSearcher 我們一般通過(guò)SearcherManager管理,因?yàn)镮ndexSearcher 如果初始化的時(shí)候加載了索引文件夾,那么

后面添加、刪除、修改的索引都不能通過(guò)IndexSearcher 查出來(lái),因?yàn)樗鼪](méi)有與索引庫(kù)實(shí)時(shí)同步,只是第一次有加載。

3.ControlledRealTimeReopenThread創(chuàng)建一個(gè)守護(hù)線程,如果沒(méi)有主線程這個(gè)也會(huì)消失,這個(gè)線程作用就是定期更新讓SearchManager管理的search能獲得最新的索引庫(kù),下面是每25S執(zhí)行一次。

4.要注意引入的lucene版本,不同的版本用法也不同,許多api都有改變。

@Configuration
public class LuceneConfig {
  /**
   * lucene索引,存放位置
   */
  private static final String LUCENEINDEXPATH="lucene/indexDir/";
  /**
   * 創(chuàng)建一個(gè) Analyzer 實(shí)例
   * 
   * @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()) {
      //如果文件夾不存在,則創(chuàng)建
      file.mkdirs();
    }
    return FSDirectory.open(path);
  }
  
  /**
   * 創(chuàng)建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線程");
    // 開(kāi)啟線程
    cRTReopenThead.start();
    return searcherManager;
  }
}

四、創(chuàng)建需要的Bean類

創(chuàng)建商品Bean

/**
 * 商品bean類
 * @author yizl
 *
 */
public class Product {
  /**
   * 商品id
   */
  private int id;
  /**
   * 商品名稱
   */
  private String name;
  /**
   * 商品類型
   */
  private String category;
  /**
   * 商品價(jià)格
   */
  private float price;
  /**
   * 商品產(chǎn)地
   */
  private String place;
  /**
   * 商品條形碼
   */
  private String code;
  ......

創(chuàng)建一個(gè)帶參數(shù)查詢分頁(yè)通用類PageQuery類

/**
 * 帶參數(shù)查詢分頁(yè)類
 * @author yizl
 *
 * @param <T>
 */
public class PageQuery<T> {

  private PageInfo pageInfo;
  /**
   * 排序字段
   */
  private Sort sort;
  /**
   * 查詢參數(shù)類
   */
  private T params;
  /**
   * 返回結(jié)果集
   */
  private List<T> results;
  /**
   * 不在T類中的參數(shù)
   */
  private Map<String, String> queryParam;
  
  ......

五、創(chuàng)建索引庫(kù)

1.項(xiàng)目啟動(dòng)后執(zhí)行同步數(shù)據(jù)庫(kù)方法

項(xiàng)目啟動(dòng)后,更新索引庫(kù)中所有的索引。

/**
 * 項(xiàng)目啟動(dòng)后,立即執(zhí)行
 * @author yizl
 *
 */
@Component
@Order(value = 1)
public class ProductRunner implements ApplicationRunner {
  
  @Autowired
  private ILuceneService service; 
  
  @Override
  public void run(ApplicationArguments arg0) throws Exception {
    /**
     * 啟動(dòng)后將同步Product表,并創(chuàng)建index
     */
    service.synProductCreatIndex();
  }
}

2.從數(shù)據(jù)庫(kù)中查詢出所有的商品

從數(shù)據(jù)庫(kù)中查找出所有的商品

  @Override
  public void synProductCreatIndex() throws IOException {
    // 獲取所有的productList
    List<Product> allProduct = mapper.getAllProduct();
    // 再插入productList
    luceneDao.createProductIndex(allProduct);
  }

3.創(chuàng)建這些商品的索引

把List中的商品創(chuàng)建索引

我們知道,mysql對(duì)每個(gè)字段都定義了字段類型,然后根據(jù)類型保存相應(yīng)的值。

那么lucene的存儲(chǔ)對(duì)象是以document為存儲(chǔ)單元,對(duì)象中相關(guān)的屬性值則存放到Field(域)中;

Field類的常用類型

Field類 數(shù)據(jù)類型 是否分詞 index是否索引 Stored是否存儲(chǔ) 說(shuō)明
StringField 字符串 N Y Y/N 構(gòu)建一個(gè)字符串的Field,但不會(huì)進(jìn)行分詞,將整串字符串存入索引中,適合存儲(chǔ)固定(id,身份證號(hào),訂單號(hào)等)
FloatPoint
LongPoint
DoublePoint
數(shù)值型 Y Y N 這個(gè)Field用來(lái)構(gòu)建一個(gè)float數(shù)字型Field,進(jìn)行分詞和索引,比如(價(jià)格)
StoredField 重載方法,,支持多種類型 N N Y 這個(gè)Field用來(lái)構(gòu)建不同類型Field,不分析,不索引,但要Field存儲(chǔ)在文檔中
TextField 字符串或者流 Y Y Y/N 一般此對(duì)字段需要進(jìn)行檢索查詢

上面是一些常用的數(shù)據(jù)類型, 6.0后的版本,數(shù)值型建立索引的字段都更改為Point結(jié)尾,F(xiàn)loatPoint,LongPoint,DoublePoint等,對(duì)于浮點(diǎn)型的docvalue是對(duì)應(yīng)的DocValuesField,整型為NumericDocValuesField,F(xiàn)loatDocValuesField等都為NumericDocValuesField的實(shí)現(xiàn)類。

commit()的用法

commit()方法,indexWriter.addDocuments(docs);只是將文檔放在內(nèi)存中,并沒(méi)有放入索引庫(kù),沒(méi)有commit()的文檔,我從索引庫(kù)中是查詢不出來(lái)的;

許多博客代碼中,都沒(méi)有進(jìn)行commit(),但仍然能查出來(lái),因?yàn)槊看尾迦?他都把IndexWriter關(guān)閉.close(),Lucene關(guān)閉前,都會(huì)把在內(nèi)存的文檔,提交到索引庫(kù)中,索引能查出來(lái),在spring中IndexWriter是單例的,不關(guān)閉,所以每次對(duì)索引都更改時(shí),都需要進(jìn)行commit()操作;

這樣設(shè)計(jì)的目的,和數(shù)據(jù)庫(kù)的事務(wù)類似,可以進(jìn)行回滾,調(diào)用rollback()方法進(jìn)行回滾。

  @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("name", p.getName(), Field.Store.YES));
      doc.add(new StringField("category", p.getCategory(), Field.Store.YES));
      // 保存price,
      float price = p.getPrice();
      // 建立倒排索引
      doc.add(new FloatPoint("price", price));
      // 正排索引用于排序、聚合
      doc.add(new FloatDocValuesField("price", price));
      // 存儲(chǔ)到索引庫(kù)
      doc.add(new StoredField("price", price));
      doc.add(new TextField("place", p.getPlace(), Field.Store.YES));
      doc.add(new StringField("code", p.getCode(), Field.Store.YES));
      docs.add(doc);
    }
    indexWriter.addDocuments(docs);
    indexWriter.commit();
  }

六、多條件查詢

按條件查詢,分頁(yè)查詢都在下面代碼中體現(xiàn)出來(lái)了,有什么不明白的可以單獨(dú)查詢資料,下面的匹配查詢已經(jīng)比較復(fù)雜了.

searcherManager.maybeRefresh()方法,刷新searcherManager中的searcher,獲取到最新的IndexSearcher。

  @Autowired
  private Analyzer analyzer;

  @Autowired
  private SearcherManager searcherManager;
  
  @Override
  public PageQuery<Product> searchProduct(PageQuery<Product> pageQuery) throws IOException, ParseException {
    searcherManager.maybeRefresh();
    IndexSearcher indexSearcher = searcherManager.acquire();
    Product params = pageQuery.getParams();
    Map<String, String> queryParam = pageQuery.getQueryParam();
    Builder builder = new BooleanQuery.Builder();
    Sort sort = new Sort();
    // 排序規(guī)則
    com.infinova.yimall.entity.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 = queryParam.get("searchKeyStr");
    if (keyStr != null) {
      // 輸入空格,不進(jìn)行模糊查詢
      if (!"".equals(keyStr.replaceAll(" ", ""))) {
        builder.add(new QueryParser("name", analyzer).parse(keyStr), Occur.MUST);
      }
    }

    // 精確查詢
    if (params.getCategory() != null) {
      builder.add(new TermQuery(new Term("category", params.getCategory())), Occur.MUST);
    }
    if (queryParam.get("lowerPrice") != null && queryParam.get("upperPrice") != null) {
      // 價(jià)格范圍查詢
      builder.add(FloatPoint.newRangeQuery("price", Float.parseFloat(queryParam.get("lowerPrice")),
          Float.parseFloat(queryParam.get("upperPrice"))), Occur.MUST);
    }
    PageInfo pageInfo = pageQuery.getPageInfo();
    TopDocs topDocs = indexSearcher.search(builder.build(), pageInfo.getPageNum() * pageInfo.getPageSize(), sort);

    pageInfo.setTotal(topDocs.totalHits);
    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);
      System.out.println(doc.toString());
      Product product = new Product();
      product.setId(Integer.parseInt(doc.get("id")));
      product.setName(doc.get("name"));
      product.setCategory(doc.get("category"));
      product.setPlace(doc.get("place"));
      product.setPrice(Float.parseFloat(doc.get("price")));
      product.setCode(doc.get("code"));
      pList.add(product);
    }
    pageQuery.setResults(pList);
    return pageQuery;
  }

七、刪除更新索引

  @Override
  public void deleteProductIndexById(String id) throws IOException {
    indexWriter.deleteDocuments(new Term("id",id));
    indexWriter.commit();
  }

八、補(bǔ)全Spring中剩余代碼

Controller層

@RestController
@RequestMapping("/product/search")
public class ProductSearchController {
  
  @Autowired
  private ILuceneService service;
  /**
   * 
   * @param pageQuery
   * @return
   * @throws ParseException 
   * @throws IOException 
   */
  @PostMapping("/searchProduct")
  private ResultBean<PageQuery<Product>> searchProduct(@RequestBody PageQuery<Product> pageQuery) throws IOException, ParseException {
    PageQuery<Product> pageResult= service.searchProduct(pageQuery);
    return ResultUtil.success(pageResult);
  }
  
}

public class ResultUtil<T> {

  public static <T> ResultBean<T> success(T t){
    ResultEnum successEnum = ResultEnum.SUCCESS;
    return new ResultBean<T>(successEnum.getCode(),successEnum.getMsg(),t);
  }

  public static <T> ResultBean<T> success(){
    return success(null);
  }

  public static <T> ResultBean<T> error(ResultEnum Enum){
    ResultBean<T> result = new ResultBean<T>();
    result.setCode(Enum.getCode());
    result.setMsg(Enum.getMsg());
    result.setData(null);
    return result;
  }
}

public class ResultBean<T> implements Serializable {

  private static final long serialVersionUID = 1L;
  
  /**
   * 返回code
   */
  private int code;
  /**
   * 返回message
   */
  private String msg;
  /**
   * 返回值
   */
  private T data;
  ...

public enum ResultEnum {
  UNKNOW_ERROR(-1, "未知錯(cuò)誤"),
  SUCCESS(0, "成功"),
  PASSWORD_ERROR(10001, "用戶名或密碼錯(cuò)誤"),
  PARAMETER_ERROR(10002, "參數(shù)錯(cuò)誤");

  /**
   * 返回code
   */
  private Integer code;
  /**
   * 返回message
   */
  private String msg;

  ResultEnum(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
  }

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java后端接口中提取請(qǐng)求頭中的Cookie和Token的方法

    Java后端接口中提取請(qǐng)求頭中的Cookie和Token的方法

    在現(xiàn)代 Web 開(kāi)發(fā)中,HTTP 請(qǐng)求頭(Header)是客戶端與服務(wù)器之間傳遞信息的重要方式之一,本文將詳細(xì)介紹如何在 Java 后端(以 Spring Boot 為例)中提取請(qǐng)求頭中的 Cookie 和 Token,并提供完整的代碼示例和優(yōu)化建議,需要的朋友可以參考下
    2025-01-01
  • springBoot項(xiàng)目配置文件加載優(yōu)先級(jí)及同配置覆蓋問(wèn)題詳解

    springBoot項(xiàng)目配置文件加載優(yōu)先級(jí)及同配置覆蓋問(wèn)題詳解

    SpringBoot配置?件可以放置在多種路徑下,不同路徑下的配置優(yōu)先級(jí)有所不同,下面這篇文章主要給大家介紹了關(guān)于springBoot項(xiàng)目配置文件加載優(yōu)先級(jí)及同配置覆蓋問(wèn)題的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理)

    Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理)

    這篇文章主要介紹了Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理),分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • Java中的流式編程問(wèn)題

    Java中的流式編程問(wèn)題

    這篇文章主要介紹了Java中的流式編程問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java詳解swagger2如何配置使用

    Java詳解swagger2如何配置使用

    編寫(xiě)和維護(hù)接口文檔是每個(gè)程序員的職責(zé),根據(jù)Swagger2可以快速幫助我們編寫(xiě)最新的API接口文檔,再也不用擔(dān)心開(kāi)會(huì)前仍忙于整理各種資料了,間接提升了團(tuán)隊(duì)開(kāi)發(fā)的溝通效率
    2022-06-06
  • java環(huán)境變量如何配置

    java環(huán)境變量如何配置

    這篇文章主要為大家詳細(xì)介紹了java環(huán)境變量配置教程,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Java基于裝飾者模式實(shí)現(xiàn)的圖片工具類實(shí)例【附demo源碼下載】

    Java基于裝飾者模式實(shí)現(xiàn)的圖片工具類實(shí)例【附demo源碼下載】

    這篇文章主要介紹了Java基于裝飾者模式實(shí)現(xiàn)的圖片工具類,結(jié)合完整實(shí)例形式分析了裝飾者模式實(shí)現(xiàn)圖片的判斷、水印、縮放、復(fù)制等功能,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下
    2017-09-09
  • Java 數(shù)據(jù)結(jié)構(gòu)之刪除鏈表中重復(fù)的結(jié)點(diǎn)

    Java 數(shù)據(jù)結(jié)構(gòu)之刪除鏈表中重復(fù)的結(jié)點(diǎn)

    在一個(gè)排序的鏈表中,會(huì)存在重復(fù)的結(jié)點(diǎn),如何實(shí)現(xiàn)刪除該鏈表中重復(fù)的結(jié)點(diǎn),重復(fù)的結(jié)點(diǎn)不保留,并返回鏈表頭指針呢?接下來(lái)小編將帶你詳細(xì)介紹
    2021-12-12
  • idea文件上有鎖文件只讀不可編輯的解決

    idea文件上有鎖文件只讀不可編輯的解決

    這篇文章主要介紹了idea文件上有鎖文件只讀不可編輯的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java forEach對(duì)原數(shù)組的操作過(guò)程

    Java forEach對(duì)原數(shù)組的操作過(guò)程

    forEach對(duì)于基本數(shù)據(jù)類型,是直接賦值,對(duì)于引用數(shù)據(jù)類型,是引用地址值,forEach遍歷時(shí),是創(chuàng)建的臨時(shí)變量,引用的數(shù)據(jù)地址,本文給大家介紹Java forEach對(duì)原數(shù)組的操作過(guò)程,感興趣的朋友一起看看吧
    2024-02-02

最新評(píng)論

江门市| 合阳县| 台前县| 咸阳市| 应城市| 镇雄县| 阜康市| 庆安县| 林芝县| 泊头市| 凌云县| 临泽县| 深水埗区| 南陵县| 南阳市| 湘潭市| 且末县| 广元市| 扶风县| 思茅市| 独山县| 南阳市| 昌黎县| 怀来县| 咸阳市| 利辛县| 托克托县| 白朗县| 南丹县| 乡城县| 崇明县| 红桥区| 醴陵市| 绥滨县| 拜城县| 新泰市| 阿巴嘎旗| 特克斯县| 礼泉县| 漳州市| 柯坪县|