全文搜索-lucene

184 阅读3分钟

流程

1.建立索引
2.查询
3.走索引
4.得到查询结果

如何建立索引?

步骤
1.准备数据
2.给数据添加索引


增删查改索引

就跟增删查改数据,一模一样。

何时建立索引?

更新数据的时候,都要更新索引。即,增改删数据,都要同时更新索引。

具体如何实现?在实现业务功能的时候,即增删改的时候,调用索引工具类的方法即可。

查询流程

1.用户输入关键字,点击搜索按钮
2.进入controller控制器
3.根据关键字,基于索引查询数据
4.得到查询结果

索引工具类IndexUtil

主要实现以下功能
1.建立索引
包括增改删索引

2.走索引,查询数据


代码

添加索引

/**
     * 添加数据
     *
     * @param user
     */
    public void addIndex(User user //输入数据:是新增数据) throws Exception {
        IndexWriter writer = getWriter();
        Document doc = new Document();
        
        //给以下三个字段,添加索引
        doc.add(new StringField("id", String.valueOf(user.getUserId()), Field.Store.YES));
        /**
         * yes是会将数据存进索引,如果查询结果中需要将记录显示出来就要存进去,如果查询结果
         * 只是显示标题之类的就可以不用存,而且内容过长不建议存进去
         * 使用TextField类是可以用于查询的。
         */
        doc.add(new TextField("username", user.getUsername(), Field.Store.YES));
        doc.add(new TextField("description", user.getDescription(), Field.Store.YES));
        
        writer.addDocument(doc);
        writer.close();
    }

查询数据

/**
     * 查询用户
     *
     * @param q 查询关键字
     * @return
     * @throws Exception
     */
    public List<User> searchBlog(String q //输入数据:是关键字) throws Exception {
        /**
         * 注意的是查询索引的位置得是存放索引的位置,不然会找不到。
         */
        dir = FSDirectory.open(Paths.get("/data/lucene"));
        IndexReader reader = DirectoryReader.open(dir);
        IndexSearcher is = new IndexSearcher(reader);
        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
        SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
        /**
         * username和description就是我们需要进行查找的两个字段
         * 同时在存放索引的时候要使用TextField类进行存放。
         */
        QueryParser parser = new QueryParser("username", analyzer); //要查询的两个字段
        Query query = parser.parse(q);
        QueryParser parser2 = new QueryParser("description", analyzer);
        Query query2 = parser2.parse(q);
        booleanQuery.add(query, BooleanClause.Occur.SHOULD);
        booleanQuery.add(query2, BooleanClause.Occur.SHOULD);
        TopDocs hits = is.search(booleanQuery.build(), 100); //查询结果
        
        QueryScorer scorer = new QueryScorer(query);
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
        /**
         * 这里可以根据自己的需要来自定义查找关键字高亮时的样式。
         */
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        List<User> userList = new LinkedList<User>();
        for (ScoreDoc scoreDoc : hits.scoreDocs) { //遍历查询结果,组装数据
            Document doc = is.doc(scoreDoc.doc);
            User user = new User();
            user.setUserId(Integer.parseInt(doc.get(("id"))));
            user.setDescription(doc.get(("description")));
            String username = doc.get("username");
            String description = doc.get("description");
            if (username != null) {
                TokenStream tokenStream = analyzer.tokenStream("username", new StringReader(username));
                String husername = highlighter.getBestFragment(tokenStream, username);
                if (StringUtil.isEmpty(husername)) {
                    user.setUsername(username);
                } else {
                    user.setUsername(husername);
                }
            }
            if (description != null) {
                TokenStream tokenStream = analyzer.tokenStream("description", new StringReader(description));
                String hContent = highlighter.getBestFragment(tokenStream, description);
                if (StringUtil.isEmpty(hContent)) {
                    if (description.length() <= 200) {
                        user.setDescription(description);
                    } else {
                        user.setDescription(description.substring(0, 200));
                    }
                } else {
                    user.setDescription(hContent);
                }
            }
            userList.add(user);
        }
        
        return userList; //最终的组装数据
    }

控制器

    @RequestMapping("/q")
    public String search(@RequestParam(value = "q", required = false, defaultValue = "") String q //输入数据是关键字,
                         @RequestParam(value = "page", required = false, defaultValue = "1") String page,
                         Model model,
                         HttpServletRequest request) throws Exception {
        LuceneIndex luceneIndex = new LuceneIndex();
        List<User> userList = luceneIndex.searchBlog(q); //走索引,查询数据
        
        /**
         * 关于查询之后的分页我采用的是每次分页发起的请求都是将所有的数据查询出来,
         * 具体是第几页再截取对应页数的数据,典型的拿空间换时间的做法,如果各位有什么
         * 高招欢迎受教。
         */
        Integer toIndex = userList.size() >= Integer.parseInt(page) * 5 ? Integer.parseInt(page) * 5 : userList.size();
        List<User> newList = userList.subList((Integer.parseInt(page) - 1) * 5, toIndex);
        model.addAttribute("userList", newList);
        String s = this.genUpAndDownPageCode(Integer.parseInt(page), userList.size(), q, 5, "");
        model.addAttribute("pageHtml", s);
        model.addAttribute("q", q);
        model.addAttribute("resultTotal", userList.size());
        model.addAttribute("pageTitle", "搜索关键字'" + q + "'结果页面");

        return "queryResult";
    }

添加多个表的多个字段

应用场景-即时通讯软件

参考

crossoverjie.top/2016/07/06/…

github.com/crossoverJi…