最近學習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();
}
}
加載後可以啓動測試: