一.前期准备
solrj的作用是方便我们在Java服务端快捷调用solr API,对solr索引进行增删改查。1.设置Field
编辑solr_home\solr_core\conf下的managed-schema文件
这些是原有的field,不用更改,其中"_text_"的text_general类型,在上一篇博客:Solr搜索引擎学习笔记之Solr服务器搭建 已设置了中文分词器smartcn
<!--这个id字段是必须的-->
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<!-- docValues are enabled by default for long type so we don't need to index the version field -->
<field name="_version_" type="plong" indexed="false" stored="false"/>
<field name="_root_" type="string" indexed="true" stored="false" docValues="false" />
<field name="_text_" type="text_general" indexed="true" stored="false" multiValued="true"/>
<!-- 唯一键 -->
<uniqueKey>id</uniqueKey>
这些是新添加的field,可根据自己的实体类进行添加
<field name="author_id" type="string" stored="true"/>
<field name="author_name" type="text_general" indexed="true" stored="true"/>
<field name="author_nickname" type="text_general" indexed="true" stored="true"/>
<field name="bookName" type="text_general" indexed="true" stored="true"/>
<field name="status" type="pint" indexed="true" stored="true"/>
<field name="content" type="text_general" indexed="true" stored="true"/>
<field name="publishDate" type="plong" indexed="true" stored="true"/>
这些是复制域,用于全文检索的字段
<!-- 复制域,供检索 -->
<copyField source="author_name" dest="_text_"/>
<copyField source="author_nickname" dest="_text_"/>
<copyField source="bookName" dest="_text_"/>
<copyField source="content" dest="_text_"/>
2.引入solrj包
<!-- solr相关包 -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>6.4.2</version>
</dependency>
二.solrj应用实例
1.创建SolrClient
- 通过HttpSolrClient的构造方法创建SolrClient,这种方式官方从solr6.5开始已不推荐使用
private HttpSolrClient solrClient=new HttpSolrClient("http://localhost:8080/solr/solr_core");
- 使用HttpSolrClient内部类Builder构建SolrClient
solrClient=new HttpSolrClient.Builder("http://localhost:8080/solr/solr_core").build();
solrClient.setConnectionTimeout(500);
solrClient.setSoTimeout(1000);
- 通过Spring配置文件声明SolrClient
<!-- 定义solr的server -->
<bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrClient">
<constructor-arg index="0" value="http://localhost:8080/solr/solr_core"/>
<!-- 设置响应解析器 -->
<property name="parser">
<bean class="org.apache.solr.client.solrj.impl.XMLResponseParser"/>
</property>
<!-- 建立连接的最长时间 -->
<property name="connectionTimeout" value="500"/>
</bean>
2.各类实现及jsp页面
- java基础类:Book
package com.lmz.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import org.apache.solr.common.SolrInputDocument;
public class Book implements Serializable{
/** serialVersionUID */
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 书名 */
private String bookName;
/** 作者 */
private Author author;
/** 状态[0:下架;1:上架] */
private Integer status;
/** 内容 */
private String content;
/** 出版时间 */
private Timestamp publishDate;
通过实体获取全文检索文档,实体属性对应上面配置field
/**
* 获取solr全文检索对象
*
* @param book
*
* @return 全文检索对象
*/
public SolrInputDocument getSolrInputDocument(Book book) {
SolrInputDocument document=new SolrInputDocument();
document.addField("id", book.getId());
document.addField("bookName", book.getBookName());
document.addField("status", book.getStatus());
document.addField("content", book.getContent());
document.addField("publishDate", book.getPublishDate().getTime());
if (book.getAuthor()!=null) {
document.addField("author_id", book.getAuthor().getId());
document.addField("author_name", book.getAuthor().getName());
document.addField("author_nickname", book.getAuthor().getNickname());
}
return document;
}
- java基础类:Author
package com.lmz.entity;
import java.io.Serializable;
/**
* 作者实体
*
*/
public class Author implements Serializable{
/** serialVersionUID */
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 名字 */
private String name;
/** 昵称 */
private String nickname;
- solr工具类:SolrUtils
包括了索引的增删改,修改方法就是添加方法,id相同的数据会覆盖掉,查询方法过于复杂,不在此
package com.lmz.utils;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.lmz.exception.SolrCheckException;
/**
* solr工具类
*
*/
@Component
public class SolrUtils {
@Autowired
private HttpSolrClient solrClient;
public SolrUtils(){
}
添加单个文档对象到检索库
/**
* 添加对象到全文检索
*
* @param document
* @throws IOException
* @throws SolrServerException
* @throws SolrCheckException
*/
public void add(SolrInputDocument document) throws SolrServerException, IOException, SolrCheckException {
UpdateResponse response = solrClient.add(document);
if (response.getStatus() == 0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr add error");
}
}
添加多个文档对象到检索库
/**
* 添加对象到全文检索
*
* @param document
* @throws IOException
* @throws SolrServerException
* @throws SolrCheckException
*/
public void add(Collection<SolrInputDocument> docs) throws IOException, SolrServerException, SolrCheckException {
if (docs != null && docs.size() > 0) {
UpdateResponse response = solrClient.add(docs);
if (response.getStatus() == 0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr add error");
}
}
}
上面是配置field的文档添加,若没有配置field,可在实体属性上使用@Field注解就行,也不必获取检索文档对象,便可直接添加
/**
* 添加bean对象到全文检索
* @param object
* @throws IOException
* @throws SolrServerException
* @throws SolrCheckException
*/
public void addByBean(Object object) throws IOException, SolrServerException, SolrCheckException {
UpdateResponse response=solrClient.addBean(object);
if (response.getStatus() == 0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr add error");
}
}
/**
* 添加bean对象到全文检索
*
* @param document
* @throws IOException
* @throws SolrServerException
* @throws SolrCheckException
*/
public void addByBean(Collection<Object> objects) throws IOException, SolrServerException, SolrCheckException {
if (objects != null && objects.size() > 0) {
UpdateResponse response = solrClient.addBeans(objects);
if (response.getStatus() == 0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr add error");
}
}
}
删除检索
/**
* 在全文检索中移除对象
*
* @param id
* @throws SolrServerException
* @throws IOException
* @throws SolrCheckException
*/
public void delete(String id) throws SolrServerException, IOException, SolrCheckException {
UpdateResponse response=solrClient.deleteById(id);
if (response.getStatus()==0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr delete error");
}
}
/**
* 在全文检索中移除对象
*
* @param id
* @throws SolrServerException
* @throws IOException
* @throws SolrCheckException
*/
public void delete(List<String> ids) throws SolrServerException, IOException, SolrCheckException {
UpdateResponse response=solrClient.deleteById(ids);
if (response.getStatus()==0) {
solrClient.commit();
} else{
throw new SolrCheckException("solr delete error");
}
}
/**
* 删除全文检索的所有对象
*
* @throws SolrServerException
* @throws IOException
* @throws SolrCheckException
*/
public void deleteAll() throws SolrServerException, IOException, SolrCheckException {
UpdateResponse response=solrClient.deleteByQuery("*");
if (response.getStatus()==0) {
solrClient.commit();
} else {
throw new SolrCheckException("solr delete all error");
}
}
获取solr的server接口
/**
* 获取solrClient
* @return
*/
public SolrClient getSolrClient() {
return solrClient;
}
- 时间工具类:DateUtils
package com.lmz.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 时间工具类
*
*/
public class DateUtils {
/**
* 根据当前时间获取id
* @return id
*/
public static Long getIDByDate() {
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddmmss");
String idStr=sdf.format(new Date());
return new Long(idStr);
}
}
- solr检索实体类:SolrSearchBean
package com.lmz.bean;
import java.io.Serializable;
import java.util.List;
public class SolrSearchBean implements Serializable {
/** SerialVersionUID */
private static final long serialVersionUID = 1L;
/** 关键字列表 */
private List<String> keywordList;
/** 排序类型[0:倒序;1:正序] */
private Integer orderType;
/** 过滤条件:状态[0:下架;1:上架] */
private Integer status;
/** 是否高亮 */
private Boolean ifHL;
/** 当前页数 */
private Integer pageNo;
/** 页面大小 */
private Integer pageSize;
- 返回结果集:PageResult
package com.lmz.bean;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 分页结果类
* @param <T>
*
*/
public class PageResult<T> implements Serializable{
/** serialVersionUID */
private static final long serialVersionUID = 1L;
/** 当前页 */
private Integer pageNo;
/** 每页大小 */
private Integer pageSize;
/** 总数 */
private Long count;
/**总页数*/
private Integer totalPage;
/** list集合数据 */
private List<T> list;
/** map集合数据 */
private Map<Object, Object> map;
- 图书solr服务类:BookSolrService
package com.lmz.service;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lmz.bean.PageResult;
import com.lmz.bean.SolrSearchBean;
import com.lmz.entity.Author;
import com.lmz.entity.Book;
import com.lmz.exception.SolrCheckException;
import com.lmz.utils.SolrUtils;
/**
* solr服务
*
*/
@Service
public class BookSolrService {
/** solr工具类 */
@Autowired
private SolrUtils solrUtils;
/**
* 删除全部索引
*
* @throws SolrServerException
* @throws IOException
* @throws SolrCheckException
*/
public void delAll() throws SolrServerException, IOException, SolrCheckException {
solrUtils.deleteAll();
}
/**
* 添加索引
*
* @param book
* @throws SolrCheckException
* @throws IOException
* @throws SolrServerException
*/
public void addSolrServer(Book book) throws SolrServerException, IOException, SolrCheckException {
solrUtils.add(book.getSolrInputDocument(book));
}
获取SolrQuery,设置检索条件
/**
* 通过搜索实体获取SolrQuery
*
* @param bean
* 搜索实体
* @return SolrQuery
*/
private SolrQuery getSolrQuery(SolrSearchBean bean) {
SolrQuery query = new SolrQuery();
StringBuffer sb_q = new StringBuffer();
if (bean.getKeywordList() == null || bean.getKeywordList().size() == 0) {
// 没有关键字则全查
sb_q.append("*");
} else {
for (int i = 0; i < bean.getKeywordList().size(); i++) {
if (i == 0) {
sb_q.append(bean.getKeywordList().get(i));
} else {
sb_q.append(" AND ").append(bean.getKeywordList().get(i));
}
}
}
// 若是全查,取消高亮
if ("*".equals(sb_q.toString())) {
bean.setIfHL(false);
}
// 设置检索关键字,从"_text_"域中检索,也就是复制域中设置了该域的域
query.set("q", "_text_:" + sb_q.toString());
StringBuffer sb_fq = new StringBuffer();
// 过滤状态
if (bean.getStatus() != null && bean.getStatus() >= 0) {
addStr(sb_fq);
sb_fq.append("status:" + bean.getStatus());
}
// 设置过滤条件
if (sb_fq.length() > 0) {
query.set("fq", sb_fq.toString());
}
//指定查询输出结构格式
query.set("wt", "json");
// 设置分页
// 开始页,solr第一页从0开始
query.setStart(bean.getPageNo() - 1);
// 每页大小
query.setRows(bean.getPageSize());
// 排序
if (bean.getOrderType() != null && bean.getOrderType() >= 0) {
if (bean.getOrderType() == 0) {
query.addSort("publishDate", SolrQuery.ORDER.desc);
} else {
query.addSort("publishDate", SolrQuery.ORDER.asc);
}
}
// 设置高亮
if (bean.getIfHL()) {
// 开启高亮
query.setHighlight(true);
// 添加高亮字段
query.addHighlightField("bookName");
query.addHighlightField("content");
query.addHighlightField("author_name");
query.addHighlightField("author_nickname");
// 高亮的头部分
query.setHighlightSimplePre("<font color='red'>");
// 高亮的尾部分
query.setHighlightSimplePost("</font>");
}
return query;
}
搜索
/**
* 搜索
*
* @param bean
* 搜索实体
* @return 搜索结果集
* @throws SolrCheckException
*/
public PageResult<Book> search(SolrSearchBean bean) throws SolrCheckException {
// 获取SolrQuery
SolrQuery query = getSolrQuery(bean);
// 搜索响应
QueryResponse response = null;
// 结果分页
PageResult<Book> page = new PageResult<Book>();
// 设置结果分页
page.setPageNo(bean.getPageNo());
page.setPageSize(bean.getPageSize());
page.setCount(0L);
page.setTotalPage(0);
try {
// 执行搜索
response = solrUtils.getSolrClient().query(query);
// 获取搜索结果文档
SolrDocumentList documentList = response.getResults();
// 获取所有高亮的字段
Map<String, Map<String, List<String>>> highlightMap = response.getHighlighting();
// 设置搜索总数
page.setCount(documentList.getNumFound());
// 设置总页数
page.setTotalPage((int) (page.getCount() % page.getPageSize() == 0 ? page.getCount() / page.getPageSize()
: page.getCount() / page.getPageSize() + 1));
// 图书集合
List<Book> books = new ArrayList<Book>();
Book book = null;
Author author = null;
// 从结果文档中获取数据,并封装实体
for (SolrDocument solrDocument : documentList) {
book = new Book();
book.setId(new Long(solrDocument.getFieldValue("id").toString()));
book.setBookName(solrDocument.getFieldValue("bookName").toString());
book.setContent(solrDocument.getFieldValue("content").toString());
book.setStatus(new Integer(solrDocument.getFieldValue("status").toString()));
book.setPublishDate(
new Timestamp(Long.parseLong(solrDocument.getFieldValue("publishDate").toString())));
author = new Author();
author.setId(new Long(solrDocument.getFirstValue("author_id").toString()));
author.setName(solrDocument.getFieldValue("author_name").toString());
author.setNickname(solrDocument.getFirstValue("author_nickname").toString());
// 获取并设置高亮字段
if (bean.getIfHL()) {
String id = solrDocument.getFieldValue("id").toString();
/* 高亮集合里不是去不内容,仅仅只是含有高亮的部分,
* 如果字段数据比较大,那么搜索出来的数据就会截掉不属于高亮的部分,
* 因此这也是使用集合来封装数据的原因
*/
List<String> bookNameList = highlightMap.get(id).get("bookName");
List<String> contentList = highlightMap.get(id).get("content");
List<String> authorNameList = highlightMap.get(id).get("author_name");
List<String> authorNicknameList = highlightMap.get(id).get("author_nickname");
if (bookNameList != null && bookNameList.size() > 0) {
book.setBookName(bookNameList.get(0));
}
if (contentList != null && contentList.size() > 0) {
book.setContent(contentList.get(0));
}
if (authorNameList != null && authorNameList.size() > 0) {
author.setName(authorNameList.get(0));
}
if (authorNicknameList != null && authorNicknameList.size() > 0) {
author.setNickname(authorNicknameList.get(0));
}
}
book.setAuthor(author);
books.add(book);
}
page.setList(books);
} catch (SolrServerException | IOException e) {
throw new SolrCheckException("solr search error");
}
return page;
}
/**
* 在字符串末尾追加" and "
*
* @param sb_fq
*/
private void addStr(StringBuffer sb_fq) {
if (sb_fq.length() > 0) {
sb_fq.append(" and ");
}
}
- Controller:SolrTestController
package com.lmz.controller;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.SolrServerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lmz.bean.PageResult;
import com.lmz.bean.SolrSearchBean;
import com.lmz.entity.Book;
import com.lmz.exception.SolrCheckException;
import com.lmz.service.BookSolrService;
import com.lmz.utils.DateUtils;
@Controller
public class SolrTestController {
@Autowired
private BookSolrService bookSolrService;
/**
* 添加索引
* @param model
* @param book book对象,包含author对象
* @return
*/
@RequestMapping("/add")
public String add(Model model,Book book) {
String msg="添加成功";
Long id=DateUtils.getIDByDate();
book.setId(id);
book.getAuthor().setId(id);
book.setPublishDate(new Timestamp(new Date().getTime()));
try {
bookSolrService.addSolrServer(book);
} catch (SolrServerException | IOException | SolrCheckException e) {
msg="添加失败";
}
model.addAttribute("msg", msg);
return "index";
}
/**
* 全文检索
* @param model
* @param bean solr搜索对象
* @return
*/
@RequestMapping("/search")
public String search(Model model,SolrSearchBean bean) {
//去除集合中空数据
if (bean.getKeywordList()!=null) {
for (int i = 0; i < bean.getKeywordList().size();) {
if (StringUtils.isBlank(bean.getKeywordList().get(i))) {
bean.getKeywordList().remove(i);
if (i >= bean.getKeywordList().size()) {
break;
}
} else {
i++;
}
}
}
//设置分页
bean.setPageNo(1);
bean.setPageSize(10);
//分页结果
PageResult<Book> page=null;
//搜索
try {
page = bookSolrService.search(bean);
} catch (SolrCheckException e) {
//solr异常,组装空结果
page=new PageResult<Book>();
page.setCount(0l);
page.setTotalPage(0);
page.setPageNo(0);
page.setPageSize(bean.getPageSize());
page.setList(new ArrayList<Book>());
}
model.addAttribute("page", page);
return "search";
}
/**
* 删除全部索引
* @return
*/
@RequestMapping("/del")
@ResponseBody
public String del() {
try {
bookSolrService.delAll();
} catch (SolrServerException | IOException | SolrCheckException e) {
return "删除失败";
}
return "删除成功";
}
}
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="${root}/resources/js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#del").click(function(){
if(confirm('确定删除全部索引?')){
var url='${root}/del';
$.post(url,{},function(res){
alert(res);
});
}
});
});
</script>
</head>
<body>
<a href="${root}/add.jsp">添加索引</a><br/>
<a href="${root}/search.jsp">搜索</a><br/>
<a href="javascript:void(0)" id='del'>删除全部索引</a><br/>
<p>${msg}</p>
</body>
</html>
- add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div align="center">
<form action="${root}/add" method="post">
<table rules="all" frame="border">
<tbody>
<tr>
<td><label>书名</label></td>
<td><input type="text" name="bookName"/></td>
</tr>
<tr>
<td><label>作者</label></td>
<td><input type="text" name="author.name"/></td>
</tr>
<tr>
<td><label>作者昵称</label></td>
<td><input type="text" name="author.nickname"/></td>
</tr>
<tr>
<td><label>内容</label></td>
<td><textarea name="content" cols="45" rows="10"></textarea></td>
</tr>
<tr>
<td><label>上下架</label></td>
<td>
<select name="status">
<option value="1">上架</option>
<option value="0">下架</option>
</select>
</td>
</tr>
</tbody>
</table>
<input type="submit" value="添加索引"/>
</form>
</div>
</body>
</html>
- search.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
.show{
margin-top: 30px;
}
</style>
</head>
<body>
<div>
<form action="${root}/search" method="post">
<table rules="all" frame="border">
<tbody>
<tr>
<td>关键字</td>
<td><input type="text" name="keywordList" /></td>
<td><input type="text" name="keywordList" /></td>
<td><input type="text" name="keywordList" /></td>
</tr>
<tr>
<td>筛选条件</td>
<td>
<select name="status">
<option value="-1">请选择状态</option>
<option value="1">上架</option>
<option value="0">下架</option>
</select>
</td>
<td>
<select name="orderType">
<option value="-1">请选择排序</option>
<option value="0">按发版时间升序</option>
<option value="1">按发版时间降序</option>
</select>
</td>
<td>
<select name="ifHL">
<option value="true">高亮</option>
<option value="false">不高亮</option>
</select>
</td>
</tr>
</tbody>
</table>
<div>
<input type="submit" value="search"/>
</div>
</form>
</div>
<c:if test="${not empty page}">
<div class="show">
<table rules="all" frame="border">
<tr>
<th>序号</th>
<th>id</th>
<th>bookName</th>
<th>status</th>
<th>content</th>
<th>publishDate</th>
<th>author_id</th>
<th>author_name</th>
<th>author_nickname</th>
</tr>
<c:forEach items="${page.list}" var="l" varStatus="i">
<tr>
<td>${i.index}</td>
<td>${l.id}</td>
<td>${l.bookName}</td>
<td>${l.status}</td>
<td>${l.content}</td>
<td>${l.publishDate}</td>
<td>${l.author.id}</td>
<td>${l.author.name}</td>
<td>${l.author.nickname}</td>
</tr>
</c:forEach>
</table>
</div>
<div>
<span>共${page.count}条记录 </span> <span>第${page.pageNo}页 </span> <span>共${page.totalPage}页
</span>
</div>
</c:if>
</body>
</html>
ok,整个实例已写完,下面来测试结果如何吧
三.测试
测试这里就进行添加和检索,删除因为就写了一个全删测试,就不进行了,修改的话就如同添加。
- 添加索引
点击添加后,到solr管理页面,查看是否添加成功
这是检索全部数据,如果出现刚才新添加的数据就说明添加成功,如下:
- 全文检索
这是检索页面,如果没填关键字就会检索全部数据,现在就填写关键字:我的游戏,结果如下:
ok,其它的搜索,就由你们完成了,现在整个实例就完成了......