设为首页 收藏本站
查看: 1548|回复: 0

[经验分享] 使用hibernate search和solr简化企业级搜索

[复制链接]

尚未签到

发表于 2018-11-2 06:57:42 | 显示全部楼层 |阅读模式
  最近刚忙完一个电影网站,其中的全文搜索的功能我就是用Solr完成的,在此将我在开发中遇到的问题以及怎样解决问题的经验拿出来与大家分享。
  我们这个网站有一个站内搜索的功能,例如站内新闻,输入关键字去搜索。数据库里有上万条数据,如果去挨个like,效率会很低,经领导指点,说可以试一试 HibernateSearch和Apache solr结合mmseg4j分词进行全文检索,于是我就开始我的Solr之旅。
  
     一开始在网上搜了很多例子拿来入门,首先是分词,mmseg4j是用来分词的,常用的分词分析器有三种:MaxWordAnalyzer(最大分 词),SimpleAnalyzer(简单的),ComplexAnalyzer(复杂的),最开始我用的是ComplexAnalyzer,看上去很不 错,后来遇到了个小问题,例如“吴宇森吃羊肉串”,经过ComplexAnalyzer分分词后,用Solr去搜“吴宇森”会返回想要的结果,但是“吴 宇”去搜什么也没返回。这是一个让人很头疼的问题,于是我试验了MaxWordAnalyzer,发现“吴宇”,“吴宇森”都能返回正确的结果,这才是我 们想要的。
  
    一段测试例子,大家可以拿去试一下MaxWordAnalyzer,SimpleAnalyzer,ComplexAnalyzer之间的区别。

Java代码

  • import java.io.IOException;

  • import junit.framework.TestCase;

  • import org.apache.lucene.analysis.Analyzer;
  • import org.apache.lucene.document.Document;
  • import org.apache.lucene.document.Field;
  • import org.apache.lucene.index.CorruptIndexException;
  • import org.apache.lucene.index.IndexWriter;
  • import org.apache.lucene.queryParser.ParseException;
  • import org.apache.lucene.queryParser.QueryParser;
  • import org.apache.lucene.search.IndexSearcher;
  • import org.apache.lucene.search.Query;
  • import org.apache.lucene.search.ScoreDoc;
  • import org.apache.lucene.search.TopDocs;
  • import org.apache.lucene.store.Directory;
  • import org.apache.lucene.store.RAMDirectory;

  • import com.chenlb.mmseg4j.analysis.MaxWordAnalyzer;

  • public class LuceneUseSimpleAnalyzerTest extends TestCase {

  •     Directory dir;
  •     Analyzer analyzer;

  •     @Override
  •     protected void setUp() throws Exception {
  •         String txt = "吴宇森吃羊肉串";
  •         //analyzer = new SimpleAnalyzer();
  •         //analyzer = new ComplexAnalyzer();
  •         //分词分析器
  •         analyzer = new MaxWordAnalyzer();
  •         //内存索引对象
  •         dir = new RAMDirectory();
  •         IndexWriter iw = new IndexWriter(dir, analyzer);
  •         Document doc = new Document();
  •         //Field.Store.YES表示在索引里将整条数据存储
  •         doc.add(new Field("txt", txt, Field.Store.YES, Field.Index.ANALYZED));
  •         iw.addDocument(doc);
  •         iw.commit();
  •         iw.optimize();
  •         iw.close();
  •     }

  •     public void testSearch() {
  •         try {
  •             //实例化搜索器
  •             IndexSearcher searcher = new IndexSearcher(dir);
  •             //构造Query对象
  •             QueryParser qp = new QueryParser("txt", analyzer);
  •             Query q = qp.parse("吴宇森");
  •             System.out.println(q);
  •             //搜索相似度最高的10条
  •             TopDocs tds = searcher.search(q, 10);
  •             //命中的个数
  •             System.out.println("======size:" + tds.totalHits + "========");
  •             //输出返回结果
  •             for (ScoreDoc sd : tds.scoreDocs) {
  •                 System.out.println(sd.score);
  •                 System.out.println(searcher.doc(sd.doc).get("txt"));
  •             }
  •         } catch (CorruptIndexException e) {
  •             e.printStackTrace();
  •         } catch (IOException e) {
  •             e.printStackTrace();
  •         } catch (ParseException e) {
  •             e.printStackTrace();
  •         }
  •     }

  • }
  

  
import java.io.IOException;
  

  
import junit.framework.TestCase;
  

  
import org.apache.lucene.analysis.Analyzer;
  
import org.apache.lucene.document.Document;
  
import org.apache.lucene.document.Field;
  
import org.apache.lucene.index.CorruptIndexException;
  
import org.apache.lucene.index.IndexWriter;
  
import org.apache.lucene.queryParser.ParseException;
  
import org.apache.lucene.queryParser.QueryParser;
  
import org.apache.lucene.search.IndexSearcher;
  
import org.apache.lucene.search.Query;
  
import org.apache.lucene.search.ScoreDoc;
  
import org.apache.lucene.search.TopDocs;
  
import org.apache.lucene.store.Directory;
  
import org.apache.lucene.store.RAMDirectory;
  

  
import com.chenlb.mmseg4j.analysis.MaxWordAnalyzer;
  


  
public>  

  
Directory dir;
  
Analyzer analyzer;
  

  
@Override
  
protected void setUp() throws Exception {
  
String txt = "吴宇森吃羊肉串";
  
//analyzer = new SimpleAnalyzer();
  
//analyzer = new ComplexAnalyzer();
  
//分词分析器
  
analyzer = new MaxWordAnalyzer();
  
//内存索引对象
  
dir = new RAMDirectory();
  
IndexWriter iw = new IndexWriter(dir, analyzer);
  
Document doc = new Document();
  
//Field.Store.YES表示在索引里将整条数据存储
  
doc.add(new Field("txt", txt, Field.Store.YES, Field.Index.ANALYZED));
  
iw.addDocument(doc);
  
iw.commit();
  
iw.optimize();
  
iw.close();
  
}
  

  
public void testSearch() {
  
try {
  
//实例化搜索器
  
IndexSearcher searcher = new IndexSearcher(dir);
  
//构造Query对象
  
QueryParser qp = new QueryParser("txt", analyzer);
  
Query q = qp.parse("吴宇森");
  
System.out.println(q);
  
//搜索相似度最高的10条
  
TopDocs tds = searcher.search(q, 10);
  
//命中的个数
  
System.out.println("======size:" + tds.totalHits + "========");
  
//输出返回结果
  
for (ScoreDoc sd : tds.scoreDocs) {
  
System.out.println(sd.score);
  
System.out.println(searcher.doc(sd.doc).get("txt"));
  
}
  
} catch (CorruptIndexException e) {
  
e.printStackTrace();
  
} catch (IOException e) {
  
e.printStackTrace();
  
} catch (ParseException e) {
  
e.printStackTrace();
  
}
  
}
  

  
}
  

  可见mmseg4j是以非常灵活的分词工具而且分词速度也很快,2.5M的长篇小说用Complex模式是5.3秒左右,用Simple模式是2.9秒 左右,好的,分词搞定了。再提一点,如何将mmseg4j应用到我们的Entity上的,我用的是元数据声明的方式:

Java代码

  • @Field(name = "newsTitle", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  • private String title;

  • @Field(name = "newsSummary", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  • private String summary;

  • @Field(name = "newsPublishedTime", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  • private Date publishedTime;
  

  
@Field(name = "newsTitle", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))

  
private String>  

  
@Field(name = "newsSummary", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  
private String summary;
  

  
@Field(name = "newsPublishedTime", store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  
private Date publishedTime;
  

  
    这里面的name =  "newTitle",name = "newsSummry",是生成索引的名称,同时还要在Solr的schema.xml里定义与之相对应的field:

Xml代码




  

  

  

  

  

  
    说到这,再提一下如何将集合建立索引以及配置,例如OneToMany,首先在一的一端进行声明:

Java代码

  • @IndexedEmbedded
  • private Set plotKeywords = new HashSet(); //关键词联想
  

  
@IndexedEmbedded
  
private Set plotKeywords = new HashSet(); //关键词联想
  

  然后在多的一端指定到底是哪一个字段建立索引:

Java代码

  • @Field(store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  • private String summary;
  

  
@Field(store = Store.YES, index = Index.TOKENIZED, analyzer = @Analyzer(impl = MaxWordAnalyzer.class))
  
private String summary;
  

  在这里你可以不给你的索引字段加name,默认会在索引库里有name="plotKeywords.summary"这样一个索引名称,同时也不要忘记在Solr的schema.xml里定义与之相对应的field:

Xml代码


  

  

  

  type="textMax" ,对应schema.xml里的:

Xml代码


  •    
  •             
  •             
  •    

  


  
  


  

  
  

  

  

  dicPath="D:/data/dict"是词库的路径,如果我们用的是MaxWordAnalyzer进行的分词,那么为了保证Solr能搜到我们 想要的结果,必须在schema.xml里配置上面一段fieldType,指定mode="max-word",这样Solr就会按照最大分词去给我们 返回与之相对应的结果,“吴宇”,“吴宇森”,都会返回结果。
  如何将Solr集成到我们的项目中呢?很简单,就拿Tomcat举例,解压Solr,将..\apache-solr-1.3.0\example \solr 文件夹拷贝到Tmocat的bin文件夹下,配置schema.xml上面提到了,还有索引库路径,在solrconfig.xml里配置:

Xml代码

  • ${solr.data.dir:D:/data/index}
  

  
${solr.data.dir:D:/data/index}
  

  然后在web.xml里面配置Solr的SolrDispatchFilter:

Xml代码


  •      SolrRequestFilter
  •      org.apache.solr.servlet.SolrDispatchFilter



  •      SolrRequestFilter
  •      /*

  

  

  
SolrRequestFilter
  
org.apache.solr.servlet.SolrDispatchFilter
  

  

  

  
SolrRequestFilter
  
/*
  

  

  在这里就不提引入的第三方jar文件,可以参考Solr文档。要注意的是HibernateSearch结合mmseg4j分词时候,我们用到了自己的词 库,需要指定一个虚拟机参数:-Dmmseg.dic.path=d:/data/dict,在这里我们将分词用到的词库放到了d:/data /dict,有可能词库过大造成虚拟机内存溢出,还要加参数: -Xmx256m  -Xmx512m。就这样Solr就集成到我们的项目中了,而且HibernateSearch帮我们管理了索引库,增加,删除,还有修改没有个被索引的 字段,HibernateSearch都会帮我们同步索引库。
  Solr的查询语法很灵活,大家可以参考官方文档,在这里我要提一下查询or和and,Solr默认的配置是or,在schema.xml里:

Xml代码



  

  

  

  

  如果想用到and,还有一种方式在Url里指定参数fq,过滤条件,例如:q=kaka&fq=age:[15 to 20],搜索条件是kaka,并且age是15到20岁之间。
  Solr支持很多种查询输出格式,用参数wt指定,有xml, json, php, phps,我这里用到了json。一段JavaScript供大家参考:

Js代码

  • $.ajax({
  •      type: "post",//使用post方法访问后台
  •     dataType: "json",//返回json格式的数据
  •     url: "/select/",//要访问的后台地址
  •     data: "q=" + keywords + "&start=" + 0 + "&rows=" + 5 + "&indent=" + "on" + "&wt=" + "json" + "&sort=" + "newsPublishedTime asc",//要发送的数据
  •     success: function(j) {//msg为返回的数据,在这里做数据绑定
  •          var docs = j.response.docs;
  •               for (var i=0; i

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-629538-1-1.html 上篇帖子: solr 教程 01-DenoFiend 下篇帖子: 有关Solr一元分词查询效果的研究
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表