最近学习SSM的例子其中涉及到了Solr的使用,作为Lucene在企业级应用中的扩展很值得研究和学习
因此记录实践开发中的后端实现步骤和遇到的问题
安装Solr:
下载网址:https://lucene.apache.org/solr/downloads.html
选择zip的windos安装包
下载后解压进入bin目录(F:\solr-8.4.0\bin),打开cmd窗口
启动命令
solr start
访问端口为8983,我们可以通过 localhost:8983
或者 127.0.0.1:8983
访问 Solr 网页
启动后控制界面如下图:
在图中红色区域,需要我们创建 Solr 的索引库,在刚才的命令窗口中输入:
solr create -c mycore
其中 mycore 为 core 的名字,然后在重启solr
重启命令:
solr restart -p 8983
在查询之前,我们需要做一些相关的操作:
1、新建applicationContext-solr.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="httpSolrClient" class="org.apache.solr.client.solrj.impl.HttpSolrClient">
<constructor-arg name="builder" value="http://localhost:8983/solr/mycore"/>
</bean>
</beans>
2、引入依赖包
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>7.3.0</version>
</dependency>
3、编写测试类插入数据
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.common.SolrInputDocument;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import javax.ws.rs.core.Context;
@ContextConfiguration(locations = {"classpath:applicationContext-solr.xml"})
public class TestSolrJ extends AbstractJUnit4SpringContextTests {
@Autowired
private SolrClient solrServer;
@Test
public void testSave() throws Exception {
SolrInputDocument inputDocument = new SolrInputDocument();
inputDocument.addField( "id","35");
inputDocument.addField("item_title","ssm项目开发实战");
inputDocument.addField( "item_content", "ssm指的是:Srping MVC + Spring + Mybatis" );
inputDocument.addField("item_image","www.ssm.png");
inputDocument.addField( "author", "wly" );
solrServer.add( inputDocument );
solrServer.commit();
}
}
5、修改配置文件
进入F:\solr-8.4.0\server\solr\mycore\conf,修改managed-schema
将我们将我们导入的字段类型改成string
然后再重启,重新进入控制界面,选择刚才我们创建的索引库,就可以查询到对应的数据
solr联合多个字段进行检索
在我们的应用中经常会有这种情形:当用户输入某个字符串查找时,需要如果在标题及内容中存在这个字会串时均要把记录加载出来,通过引入copyField及multiValue这两个标签便可解决这种问题。
配置文件:
<field name="id" type="string" multiValued="false" indexed="true" required="true" stored="true"/>
<field name="item_image" type="string" stored="false" docValues="false"/>
<field name="item_content" type="string"/>
<field name="author" type="string"/>
<field name="item_title" type="string"/>
<field name="item_title_str" type="string" indexed="true" stored="false" multiValued="true"/>
<copyField source="item_title" dest="item_title_str" maxChars="256"/>
<copyField source="item_content" dest="item_title_str" maxChars="256"/>
展示图如下:
IKAnalyzer 分词器
Solr 可通过自带的分词器 smartcn 或者第三方分词器 IKAnalyzer 来实现,IKAnalyzer 分词效果较好,所以这里使用 IKAnalyzer 分词器。
下载 IKAnalyzer 分词器 Jar 包
ik-analyzer-solr5-5.x.jar
solr-analyzer-ik-5.1.0.jar
将其中的两个 Jar 包放入 F:\solr-8.4.0\server\solr-webapp\webapp\WEB-INF\lib
下
修改 F:\solr-8.4.0\server\solr\mycore\conf
下的 managed-schema文件
<!-- 添加ik分词器 -->
<fieldType name="text_ik" class="solr.TextField">
<analyzer type="index" isMaxWordLength="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
<analyzer type="query" isMaxWordLength="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
type="index"
代表创建索引时的分词
type="query"
代表查询时的分词
<!-- 需要分词的字段 -->
<field name="title" type="text_ik" indexed="true" stored="true" required="true" multiValued="false" />
我们对文章标题进行分词查询
配置其它字段名和类型,否则会查询出集合类型的数据
<field name="comment_num" type="string"/>
<field name="downvote" type="string"/>
<field name="upvote" type="string"/>
<field name="nick_name" type="string"/>
<field name="img_url" type="string"/>
<field name="rpt_time" type="pdate"/>
<field name="content" type="text_general"/>
<field name="category" type="string"/>
<field name="u_id" type="string"/>
<field name="personal" type="string"/>
注意:content 字段不能设置为 String 类型,否则内容过多会超出范围,报异常。除了日期和 content 字段外其它都设置为 String类型。
然后重启solr
开始在项目中进行引用
在 web.xml 中引入 applicationContext-solr.xml
配置文件
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-mybatis.xml,
classpath:applicationContext-redis.xml,
classpath:applicationContext-activemq.xml,
classpath:applicationContext-solr.xml
</param-value>
</context-param>
新建SolrService 接口,包含了基本的查询,删除等操作
public interface SolrService {
/**
* 根据关键字搜索文章并分页
* @param keyword
* @return
*/
PageHelper.Page<UserContent> findByKeyWords(String keyword, Integer pageNum, Integer pageSize);
/**
* 添加文章到solr索引库中
* @param userContent
*/
void addUserContent(UserContent userContent);
/**
* 根据solr索引库
* @param userContent
*/
void updateUserContent(UserContent userContent);
/**
* 根据文章id删除索引库
* @param id
*/
void deleteById(Long id);
}
创建其实现类SolrServiceImpl
查询步骤,先是设置查询条件,设置分页信息,然后获取结果集,遍历结果集创建文档对象,然后封装到对应的实体类中
再进行分页组合数据格式,如果发生异常则返回 null。
public class SolrServiceImpl implements SolrService {
@Autowired
HttpSolrClient solrClient;
@Override
public PageHelper.Page<UserContent> findByKeyWords(String keyword, Integer pageNum, Integer pageSize) {
SolrQuery solrQuery = new SolrQuery( );
//设置查询条件
solrQuery.setQuery( "title:"+keyword );
//设置高亮
solrQuery.setHighlight( true );
solrQuery.addHighlightField( "title" );
solrQuery.setHighlightSimplePre( "<span style='color:red'>" );
solrQuery.setHighlightSimplePost( "</span>" );
//分页
if (pageNum == null || pageNum < 1) {
pageNum = 1;
}
if (pageSize == null || pageSize < 1) {
pageSize = 7;
}
solrQuery.setStart( (pageNum-1)*pageSize );
solrQuery.setRows( pageSize );
solrQuery.addSort("rpt_time", SolrQuery.ORDER.desc);
//开始查询
try {
QueryResponse response = solrClient.query( solrQuery );
//获得高亮数据集合
Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();
//获得结果集
SolrDocumentList resultList = response.getResults();
//获得总数量
long totalNum = resultList.getNumFound();
List<UserContent> list = new ArrayList<UserContent>( );
for(SolrDocument solrDocument:resultList){
//创建文章对象
UserContent content = new UserContent();
//文章id
String id = (String) solrDocument.get( "id" );
Object content1 = solrDocument.get( "content" );
Object commentNum = solrDocument.get( "comment_num" );
Object downvote = solrDocument.get( "downvote" );
Object upvote = solrDocument.get( "upvote" );
Object nickName = solrDocument.get( "nick_name" );
Object imgUrl = solrDocument.get( "img_url" );
Object uid = solrDocument.get( "u_id" );
Object rpt_time = solrDocument.get( "rpt_time" );
Object category = solrDocument.get( "category" );
Object personal = solrDocument.get( "personal" );
//取得高亮数据集合中的文章标题
Map<String, List<String>> map = highlighting.get( id );
String title = map.get( "title" ).get( 0 );
content.setId( Long.parseLong( id ) );
content.setCommentNum( Integer.parseInt( commentNum.toString() ) );
content.setDownvote( Integer.parseInt( downvote.toString() ) );
content.setUpvote( Integer.parseInt( upvote.toString() ) );
content.setNickName( nickName.toString() );
content.setImgUrl( imgUrl.toString() );
content.setuId( Long.parseLong( uid.toString() ) );
content.setTitle( title );
content.setPersonal( personal.toString() );
Date date = (Date)rpt_time;
content.setRptTime(date);
List<String> clist = (ArrayList)content1;
content.setContent( clist.get(0).toString() );
content.setCategory( category.toString() );
list.add( content );
}
PageHelper.startPage(pageNum, pageSize);//开始分页
PageHelper.Page page = PageHelper.endPage();//分页结束
page.setResult(list);
page.setTotal(totalNum);
return page;
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void addUserContent(UserContent cont) {
if(cont!=null){
addDocument(cont);
}
}
@Override
public void updateUserContent(UserContent cont) {
if(cont!=null){
addDocument(cont);
}
}
@Override
public void deleteById(Long id) {
try {
solrClient.deleteById(id.toString());
solrClient.commit();
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void addDocument(UserContent cont){
try {
SolrInputDocument inputDocument = new SolrInputDocument();
inputDocument.addField( "comment_num", cont.getCommentNum() );
inputDocument.addField( "downvote", cont.getDownvote() );
inputDocument.addField( "upvote", cont.getUpvote() );
inputDocument.addField( "nick_name", cont.getNickName());
inputDocument.addField( "img_url", cont.getImgUrl() );
inputDocument.addField( "rpt_time", cont.getRptTime() );
inputDocument.addField( "content", cont.getContent() );
inputDocument.addField( "category", cont.getCategory());
inputDocument.addField( "title", cont.getTitle() );
inputDocument.addField( "u_id", cont.getuId() );
inputDocument.addField( "id", cont.getId());
inputDocument.addField( "personal", cont.getPersonal());
solrClient.add( inputDocument );
solrClient.commit();
} catch (SolrServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
controller层的编写
@RequestMapping("/index_list")
public String findAllList(Model model, @RequestParam(value = "keyword",required = false) String keyword,
@RequestParam(value = "pageNum",required = false) Integer pageNum ,
@RequestParam(value = "pageSize",required = false) Integer pageSize) {
User user = (User)getSession().getAttribute("user");
if(user!=null){
model.addAttribute( "user",user );
}
if(StringUtils.isNotBlank(keyword)){
Page<UserContent> page = solrService.findByKeyWords( keyword ,pageNum,pageSize);
model.addAttribute("keyword", keyword);
model.addAttribute("page", page);
}else {
Page<UserContent> page = findAll(pageNum,pageSize);
model.addAttribute( "page",page );
}
return "../index";
}
单元测试类 TestSolrJ 中新建方法 testSaveAll,并将所需要的配置文件都进行加载,此步骤是将数据库的数据加载到solr
@ContextConfiguration(locations = {"classpath:applicationContext-redis.xml","classpath:spring-mybatis.xml","classpath:applicationContext-activemq.xml","classpath:applicationContext-solr.xml"})
public class TestSolrJ extends AbstractJUnit4SpringContextTests {
@Autowired
private SolrClient solrServer;
@Autowired
private UserContentService userContentService;
@Test
public void testSaveAll() throws IOException, SolrServerException {
List<UserContent> list = userContentService.findAll();
if(list!=null && list.size()>0){
for (UserContent cont : list){
SolrInputDocument inputDocument = new SolrInputDocument();
inputDocument.addField( "comment_num", cont.getCommentNum() );
inputDocument.addField( "downvote", cont.getDownvote() );
inputDocument.addField( "upvote", cont.getUpvote() );
inputDocument.addField( "nick_name", cont.getNickName());
inputDocument.addField( "img_url", cont.getImgUrl() );
inputDocument.addField( "rpt_time", cont.getRptTime() );
inputDocument.addField( "content", cont.getContent() );
inputDocument.addField( "category", cont.getCategory());
inputDocument.addField( "title", cont.getTitle() );
inputDocument.addField( "u_id", cont.getuId() );
inputDocument.addField( "id", cont.getId());
inputDocument.addField( "personal", cont.getPersonal());
solrServer.add( inputDocument );
}
}
solrServer.commit();
}
}
加载后可以启动测试: