javaweb使用solr实现搜索功能

需求明确,原应用使用mysql数据库,基本功能CRUD,增加一个搜索功能。数据库名wenda,下有一个question表,搜索目标question的title和content(返回题目或内容包含搜索关键字的question)。1.下载需要的工具;2.分词工具打包;3.solr搭建

一.准备工作

ide: intellij
架构:springmvc+maven+mybatis
需要安装的工具
solr(搜索用)+IK Analyzer(中文分词用)+maven(打包)+mysql的jar包
这个分词工具不是“很好”,但是比没有好很多!没有的话,中文搜索结果简直没法看。
打包可以不用maven,我用的maven
如果是mac电脑,安装下homebrew,brew install <name>就可以安装 solr 和 maven
分词工具源码下载地址:https://code.google.com/archive/p/ik-analyzer/downloads
估计需要翻墙,下载源码,要源码!后面自己打包用
solr 和 maven 轻轻百度一下很多

二.打包分词工具

  • 1.IK Analyzer目录
A870F4FE-0790-4F9D-BB87-385FE23B2C1F.png
  • 2.intellij 新建maven项目
    org文件夹放在 src/main/java 下
    另外3个文件放在src/mian/resources下


    image.png
  • 3.pom.xml 导入依赖(很重要)
    dependies和build要一样,然后maven就会下载这些依赖了
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.wltea</groupId>
    <artifactId>ik-analyzer</artifactId>
    <version>6.6.0</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>6.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>6.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>6.6.0</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.dic</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.dic</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
  • 4.修改代码
    源码比较旧,有些方法已经修改(参数个数改变等),还有些函数可以不需要,删掉(不删掉的话要一个个修改到编译通过,整个项目有代码编译不通过就跑步起来,为了方便,不需要的就直接删掉了)
    query包和sample包已经删掉,加了util包,实现自己的方法,用于solr分词


    image.png

    lucene包下的2个类都需要修改

//IKTokenizer,java
public IKTokenizer(boolean useSmart){
        //super(in); //注释掉这句话和方法参数Reader in
        offsetAtt = addAttribute(OffsetAttribute.class);
        termAtt = addAttribute(CharTermAttribute.class);
        typeAtt = addAttribute(TypeAttribute.class);
        _IKImplement = new IKSegmenter(input , useSmart);
    }
//IKAnalyzer.java
@Override
    protected TokenStreamComponents createComponents(String fieldName) {
        //方法参数Reader in 去掉,下面 new IKTokenizer()中的参数in也去掉
        Tokenizer _IKTokenizer = new IKTokenizer( this.useSmart());
        return new TokenStreamComponents(_IKTokenizer);
    }

实现util中的类
public class IKToKenizerFactory extends TokenizerFactory {

private boolean useSmart;

public IKToKenizerFactory(Map<String,String> args) {
    super(args);
    useSmart = getBoolean(args, "useSmart", false);
}
@Override
public Tokenizer create(AttributeFactory attributeFactory) {
    Tokenizer tokenizer = new IKTokenizer(useSmart);
    return tokenizer;
}

}

  • 5.打包
    命令行 进到项目目录,mvn package打包
    intellij 的话再左下角有命令行终端,直接就是在项目目录下


    image.png

三.搭建solr

下载后的目录结构

image.png
  • 1.启动
    安装完成后,进入solr目录,目前版本6.6.0,我的是
    /usr/local/Cellar/solr/6.6.0/
    cd /usr/local/Cellar/solr/6.6.0/bin (可执行文件在bin目录下)
    solr start (启动solr)
    看到下图,solr 启动完成了。


    image.png
  • 2.配置
    配置的目的:
    (1)数据库交互
    (2)搜索结果能看(是我们想要的结果),使用分词工具
    新建一个容器(collection)
    solr create_core wenda
    刷新后就可以看到wenda,/usr/local/Cellar/solr/6.6.0/server/solr 目录下多了一个wenda文件夹

    image.png

    i.数据库配置
    详细可参考https://wiki.apache.org/solr/DIHQuickStart,我也是看的
    /usr/local/Cellar/solr/6.6.0/server/solr /wenda/conf/ 目录下新建data-config.xmlwen文件

  <dataConfig>
  <dataSource driver="com.mysql.jdbc.Driver" password="你的数据库密码" type="JdbcDataSource" url="jdbc:mysql://localhost/wenda" user="你的数据库账户"/>
  <document>
    <entity name="question" query="select id,title,content from question">
      <field column="title" name="question_title"/>
      <field column="content" name="question_content"/>
    </entity>
  </document>
</dataConfig>

注:url中的wenda对应我的数据库,entity name对应数据表,query是sql语句,field column是表中的属性名,name就是它映射到solr上的名字(也就是表中title在solr中是question_title),也可以都用title

ii.数据库和solr交互
solr/libexec/ 目录下加ext文件夹,在加mysql文件下,在家mysql的jar包;
ikanalyzer文件夹,加入分词器的jar包


image.png

/usr/local/Cellar/solr/6.6.0/server/solr /wenda/conf/ 下的solrconfig.xml,找个“合适的地方”加入下面几句话(不要加在别的标签内)。

<--! jar包语句加在lib处,请求处理语句加在requestHandler处比较好看 文件中搜索lib 就找到jar添加的位置,一般同一类行的语句都是相邻的-->
<!-- 导入jar包-->
<lib dir="${solr.install.dir}/libexec/dist/" regex="solr-dataimporthandler-\d.*\.jar" />
  <lib dir="${solr.install.dir}/libexec/ext/ikanalyzer" regex=".*\.jar" />
  <lib dir="${solr.install.dir}/libexec/ext/mysql" regex=".*\.jar" />

  <!-- A request handler that returns indented JSON by default -->
  <requestHandler name="/query" class="solr.SearchHandler">
    <lst name="defaults">
      <str name="echoParams">explicit</str>
      <str name="wt">json</str>
      <str name="indent">true</str>
    </lst>
  </requestHandler>

iii.加入分词器
加入分词器,并声明哪些字段是用这个分词器
/usr/local/Cellar/solr/6.6.0/server/solr /wenda/conf/ 下的managed-schema文件,找个“合适的地方”加入下面几句话(不要加在别的标签内)。

<!-- 文末有解释 -->

<!-- 中文分词-->
  <fieldType class="solr.TextField" name="text_ik">
    <!-- 索引时候的分词 -->
    <analyzer type="index">
      <tokenizer class="org.wltea.analyzer.util.IKToKenizerFactory" useSmart="false"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/><filter class="solr.FlattenGraphFilterFactory"/>-->
    </analyzer>
    <!-- 查询时候的分词器 -->
    <analyzer type="query">
      <tokenizer class="org.wltea.analyzer.util.IKToKenizerFactory" useSmart="true"/>
    </analyzer>
  </fieldType>

 <field indexed="true" multiValued="true" name="_text_" stored="false" type="text_ik"/>
  <field indexed="true" multiValued="true" name="question_title" stored="true" type="text_ik"/>
  <field indexed="true" multiValued="true" name="question_content" stored="true" type="text_ik"/>

注:1.每个标签的属性顺序可能和你的不一样,我用自动格式化,按字母顺序了。2.<fieldType>标签,添加中文分词器,命名为text_ik,注意到索引和搜索的差别是否是智能模式,差别看下图。望文生义,索引非只能,只要是个词语就建个索引(喝多关键字都能搜到它,但是可能不是我们要的),查询是智能的,进行了语义识别。3.<field>标签 主要是name和type属性,查询的域类型名用text_ik分词器。4.默认情况下域类型都是text,用text_general分词器,这种中文不友好,所以直接把text的分词器也改成test_ik。

image.png
  • 3.导入数据
    wenda-dataimport-execute


    image.png

四.项目中增加搜索功能代码

到这如果不出问题,恭喜你,胜利就在眼前。

  • 1.mvc中 实际的业务基本都是Service在干,但是要干什么呢。
    来一个词,要能找到;加了一个问题,添加到solr去,我回头能找到新的这个问题。代码中query设置的一些属性可以对找网页上wenda-query 中的一些设置来看。

/**
 * 14:06 on 2017/7/30
 * If u like , it is created by TXM,
 * Else u should have like it.
 */
@Service
public class SearchService {
    private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

    private static final String SOLR_URL = "http://localhost:8983/solr/wenda";
    private HttpSolrClient client = new HttpSolrClient.Builder(SOLR_URL).build();
    private static final String QUESTION_TITLE_FIELD = "question_title";
    private static final String QUESTION_CONTENT_FIELD = "question_content";

    public List<Question> searchQuestion(String keyword, int offset, int count,
                                         String hlPre, String hlPos) throws Exception{


        List<Question> questionList = new ArrayList<>();
        SolrQuery query = new SolrQuery(keyword);
        query.setRows(count);
        query.setStart(offset);
        query.setHighlight(true);
        query.setHighlightSimplePre(hlPre);
        query.setHighlightSimplePost(hlPos);
        query.set("hl.fl", QUESTION_CONTENT_FIELD + "," + QUESTION_TITLE_FIELD);
        QueryResponse response = client.query(query);
        for (Map.Entry<String, Map<String, List<String>>> entry : response.getHighlighting().entrySet()) {
            Question q = new Question();
            q.setId(Integer.parseInt(entry.getKey()));
            if (entry.getValue().containsKey(QUESTION_CONTENT_FIELD)) {
                List<String> contentList = entry.getValue().get(QUESTION_CONTENT_FIELD);
                if (contentList.size() > 0) {
                    q.setContent(contentList.get(0));
                }
            }
            if (entry.getValue().containsKey(QUESTION_TITLE_FIELD)) {
                List<String> titleList = entry.getValue().get(QUESTION_TITLE_FIELD);
                if (titleList.size() > 0) {
                    q.setTitle(titleList.get(0));
                }
            }
            questionList.add(q);
        }

        return questionList;
    }


    public boolean indexQuestion(int qid, String title, String content) throws Exception{
        SolrInputDocument doc = new SolrInputDocument();
        doc.setField("id", qid);
        doc.setField(QUESTION_TITLE_FIELD, title);
        doc.setField(QUESTION_CONTENT_FIELD, content);
        UpdateResponse response = client.add(doc, 1000);
        return response != null && response.getStatus() == 0;
    }
}
  • 2.路由响应由控制器处理
@Controller
public class SearchController {
    private static final Logger logger = LoggerFactory.getLogger(SearchController.class);

    @Autowired
    SearchService searchService;


    @Autowired
    QuestionService questionService;

    @RequestMapping(path = {"/search"}, method = {RequestMethod.GET})
    public String search (Model model, @RequestParam("q") String keyword,
                          @RequestParam(value = "offset", defaultValue = "0") int offset,
                          @RequestParam(value = "count", defaultValue = "10") int count) {
        try {
            List<Question> questionList = searchService.searchQuestion(keyword, offset, count, "<em>", "</em>");
            List<ViewObject> vos = new ArrayList<>();
            for (Question question : questionList) {
                Question q = questionService.selectById(question.getId());
                ViewObject vo = new ViewObject();
                if (question.getContent() != null) {
                    q.setContent(question.getContent());
                }
                if (question.getTitle() != null) {
                    q.setTitle(question.getTitle());
                }
                vo.set("question", question);
                vos.add(vo);
            }
            model.addAttribute("vos", vos);
            model.addAttribute("keyword", keyword);
        } catch (Exception e) {
            logger.error("搜索评论失败 " + e.getMessage());
        }
        return "result";
    }
}

可以用postman工具测试功能
写好路径localhost:8080/search和参数
自动变成localhost:8080/search?q="分布式"


x
  • 3.新question加入到solr,只需在原来addquestion时,再调用SearchService类的indexQuestion方法。

总算搞完了,也是今天刚学的。很多具体的还没搞明白,勉强先跑起来再研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容