hibernate search5.3.0全文检索集成到spring data jpa(hibernate)上面同时实现高亮效果 weir 2015-07-27 16:13:05.0 java,hibernateSearch 5621 hibernate search 我就不多说了,它是基于lucene的全文检索工具,记得上大学那时候接触了compass全文检索工具,后来也没怎么用,再后来这家伙不更新了,所以hibernate就推出了自己的基于lucene的全文检索工具就是这家伙hibernate Search。 不用说天然的优势就是可以无缝的和hibernate集成甚至不需要什么配置,一直在更新中,最近想在自己的博客里面加入搜索功能,本想用比较热乎的solr,找了半天资料还是放弃了,用在我的博客里面有点大题小做了,所以自然就锁定了hibernate search,简单嘛,而且不需要什么配置对hibernate jpa都很有好,虽然我用的是spring data jpa 但是我想也不会影响它的使用。 废话就不说了,我们进入正题,从配置开始: 1.persistence.xml 文件里面加入: e:/index "/> 如果是linux系统就把路径换一下,配置搞定,是不是很简单,其他地方不需要任何改动。 2.实体: 这不用解释了吧,你想要在那个实体上面做检索就配置那个实体,说白了就是加一些注解。 我就简略的写了,至于注解的含义大家在网上找找。 @Indexed @Analyzer(impl=SmartChineseAnalyzer.class) public class Posts implements java.io.Serializable { @Id //这里不需要加什么,如果你用的是hibernate做持久层需要加@DocumentId 这个注解 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(name = "post_title", nullable = false,length=1000) @Field @Boost(2) public String getPtitle() { return ptitle; } public void setPtitle(String ptitle) { this.ptitle = ptitle; } @Lob @Field public String getContent() { return content; } public void setContent(String content) { this.content = content; } 还需要我解释么,我觉得不需要了。 就这么简单,加几个注解就可以了,系统在运行时会自动生成索引文件 3.检索服务: 这个主要是放在service层,因为我用的是spring data jpa @Service("postService") public class PostServiceImpl implements PostService{ @Autowired private EntityManagerFactory emf; 这里我在这个实现类里面注入了在applicationContext.xml 里面配置的EntityManagerFactory, 我想你应该懂这些 这个地方的方法是在service层实现的,同时注入: @Autowired private EntityManagerFactory emf; public QueryResultsearch(int nowpage,int size,String keyWord) { QueryResultqueryResult = new QueryResult(); EntityManager em = emf.createEntityManager(); FullTextEntityManager fManager = Search.getFullTextEntityManager(em); QueryBuilder qb = fManager.getSearchFactory().buildQueryBuilder().forEntity(Posts.class).get(); Query q = qb.keyword().onFields("ptitle","description","content").matching(keyWord).createQuery(); FullTextQuery fq = fManager.createFullTextQuery(q, Posts.class); queryResult.setTotalRecord(fq.getResultSize()); Listre = fq.setFirstResult(nowpage).setMaxResults(size).getResultList(); re = hightLight(q, re, Posts.class, null, "ptitle","description","content"); queryResult.setResultList(re); return queryResult; } /** * @param org.apache.lucene.search.Query luceneQuery * @param searchResults 搜索结果集 * @param searchResultClass 搜索结果类型 * @param excludeFields 要排除高亮的字段 * @param fieldNames 需要高亮的字段 * @return 高亮后的searchResults */ privateListhightLight(Query luceneQuery, ListsearchResults, ClasssearchResultClass, ListexcludeFields, String... fieldNames) { SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("", ""); QueryScorer queryScorer = new QueryScorer(luceneQuery); Highlighter highlighter = new Highlighter(formatter, queryScorer); Analyzer analyzer = new SmartChineseAnalyzer(); for (E e : searchResults) { for (String fieldName : fieldNames) { if(null != excludeFields && excludeFields.contains(fieldName)){ continue; } Object fieldValue = ReflectionUtils.invokeMethod(BeanUtils.getPropertyDescriptor(searchResultClass, fieldName).getReadMethod(), e); String hightLightFieldValue = null; if(fieldValue instanceof String){ try { hightLightFieldValue = highlighter.getBestFragment( analyzer, fieldName , String.valueOf(fieldValue)); } catch (Exception e1) { e1.printStackTrace(); } ReflectionUtils.invokeMethod(BeanUtils.getPropertyDescriptor(searchResultClass, fieldName).getWriteMethod(), e, hightLightFieldValue); } } } return searchResults; } 解释:onFields("ptitle","description","content") 这几个就是你要检索的字段,有几个写几个。 这里面加入了分页效果,高亮效果,这段代码我也是在网上找的修改了一下,如果谁还有更好的可以留言分享一下。 这个高亮显示 不知道为什么有了问题 我又改了一下: /** * 高亮显示文章 * * @param query {@link org.apache.lucene.search.Query} * @param data 未高亮的数据 * @param fields 需要高亮的字段 * @return 高亮数据 */ public static List<Posts> hightLight(Query query, List<Posts> data, String... fields) { List<Posts> result = new ArrayList<Posts>(); SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<b><font color=\"red\">", "</font></b>"); QueryScorer queryScorer = new QueryScorer(query); Highlighter highlighter = new Highlighter(formatter, queryScorer); // 使用IK中文分词 Analyzer analyzer = new SmartChineseAnalyzer(); for (Posts a : data) { // 构建新的对象进行返回,避免页面错乱(我的页面有错乱) Posts article = new Posts(); for (String fieldName : fields) { // 获得字段值,并给新的文章对象赋值 Object fieldValue = ReflectionUtils .invokeMethod(BeanUtils.getPropertyDescriptor(Posts.class, fieldName).getReadMethod(),a); ReflectionUtils.invokeMethod(BeanUtils.getPropertyDescriptor(Posts.class, fieldName).getWriteMethod(), article, fieldValue); String hightLightFieldValue = null; try { hightLightFieldValue = highlighter.getBestFragment(analyzer, fieldName, String.valueOf(fieldValue)); } catch (Exception e) { throw new RuntimeException("高亮显示关键字失败", e); } // 如果高亮成功则重新赋值 if (hightLightFieldValue != null) { ReflectionUtils.invokeMethod(BeanUtils.getPropertyDescriptor(Posts.class, fieldName).getWriteMethod(), article,hightLightFieldValue); } } // 赋值ID ReflectionUtils.invokeMethod(BeanUtils.getPropertyDescriptor(Posts.class, "id").getWriteMethod(), article, a.getId()); result.add(article); } return result; } 这个好使了。 package com.weirblog.fenye; import java.util.List; /** * 查询结果集,包括数据和总数 * @author db2admin * * @param*/ public class QueryResult{ /** 查询得出的数据List **/ private ListresultList; /** 查询得出的总数 **/ private int totalRecord; public ListgetResultList() { return resultList; } public void setResultList(ListresultList) { this.resultList = resultList; } public int getTotalRecord() { return totalRecord; } public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; } } package com.weirblog.fenye; import java.util.List; /** * 分页数据包装,包括分页信息和List数据 */ public class PageView{ /** 分页数据 **/ private Listrecords; /** 页码开始索引和结束索引 **/ private PageIndex pageIndex; /** 总页数 **/ private int totalPage = 1; /** 每页显示记录数 **/ private int maxResult = 10; /** 当前页 **/ private int currentPage = 1; /** 总记录数 **/ private int totalRecord; /** 每次显示多少页,必须保证大于3页,保证左右链接都可以使用 **/ private int viewPageCount = 10; /** 要获取记录的开始索引 **/ public int getFirstResult() { return (this.currentPage - 1); // return (this.currentPage - 1) * this.maxResult; } public int getViewPageCount() { return viewPageCount; } public void setViewPageCount(int viewPageCount) { this.viewPageCount = viewPageCount; } public PageView(int maxResult, int currentPage) { this.maxResult = maxResult; this.currentPage = (currentPage <= 0 ? 1 : currentPage); } public PageView(int currentPage) { this.currentPage = (currentPage <= 0 ? 1 : currentPage); } public void setQueryResult(QueryResultqr) { setTotalRecord(qr.getTotalRecord()); setRecords(qr.getResultList()); } public int getTotalRecord() { return totalRecord; } public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; setTotalPage(this.totalRecord % this.maxResult == 0 ? this.totalRecord / this.maxResult : this.totalRecord / this.maxResult + 1); } public ListgetRecords() { return records; } public void setRecords(Listrecords) { this.records = records; } public PageIndex getPageIndex() { return pageIndex; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; this.pageIndex = PageIndex.getPageIndex(viewPageCount, currentPage, totalPage); } public int getMaxResult() { return maxResult; } public int getCurrentPage() { return currentPage; } } 这些关于分页的封装我就不解释了。 4.controller层: @RequestMapping("/search") public String search(Integer page,String keyWord,Model model) { PageViewpageView = new PageView(2, page!=null ? page:1); pageView.setQueryResult(postService.search(pageView.getFirstResult(), pageView.getMaxResult(), keyWord)); model.addAttribute("pageView", pageView); return "/search"; } 这个也应该不需要解释什么。 还有一些就是jar包了我用的是最新稳定版本5.3.0 hibernate-search-engine-5.3.0.Final hibernate-search-orm-5.3.0.Final lucene-analyzers-common-4.10.4 lucene-core-4.10.4 xml-apis-1.3.03 E:\gj\hibernate-search-5.3.0.Final\dist\lib\required 重复的去掉 hibernate 需要也是最新的了4.3.10 lucene需要: lucene 这些有的hibernate search包里面没有 自己去lucene官网上下载,还有一个办法就是自己搭建一个maven工程加入: org.hibernatehibernate-search-orm5.3.0.Final 就是这样才搞定了jar包缺失的问题,很扯淡是不是。 最后还是看看效果吧: 还行吧。