上周簡單的學習了solr的數據導入以及IK分詞器的安裝吞歼,今天學習一下solr的一些基本操作银受,以及和spring boot的整合践盼。在開始項目之前簡單的學習一下solr的一些查詢的操作
一、admin頁面簡單查詢
先在本地啟動solr宾巍,然后進入solr admin管理頁面咕幻。在這之前我修改了數據庫的數據,所以需要重新導入新的數據顶霞。選擇自定義的core肄程,點擊"Query",入下圖所示:
簡單介紹一下幾個常用查詢參數:
"q":the quert string选浑,即查詢條件蓝厌,":"表示查詢所有內容,支持多個查詢條件鲜侥。
"fq":fiter query褂始,即過濾查詢,使用Filter Query可以充分利用FilterQuery Cache描函,提高檢索性能崎苗。個人覺得可能更多時候用來做區(qū)間查詢。
"sort":sort field or function asc|desc舀寓,即根據指定field進行排序胆数。
"start,rows"主要用來做分頁,這個應該都比較熟悉互墓。
"fl":field list必尼,就是查詢結果顯示field,多個field用逗號分割篡撵。
"df" default search field判莉,默認查詢field。
Raw Query Parameters:就是所有的查詢條件育谬,只是查詢參數是在URL上而已券盅。
"wt": the wite type,即查詢結果的顯示形式膛檀,支持多種個數锰镀,json、xml咖刃、python泳炉、ruby、csv等嚎杨,最常用的可能還是json或者xml吧花鹅。
上面這個幾個都是比較差用的,查詢條件枫浙,其他的還有一些翠胰,就不再介紹了。
用一個簡單的需求來測試一下自脯,查詢條件:性別為male且地址為FR之景,年齡在30歲以下,結果按照年齡逆序排列膏潮,每頁只展示5條記錄,锻狗,輸出格式為json(默認):
因為數據比較少,只有3條記錄焕参,所以分頁沒起到效果轻纪,注意一點的是:solr查詢的AND、OR叠纷、TO這些必須要大寫刻帚,自己用小寫發(fā)現沒起作用,查詢條件的格式一定邀注意涩嚣,不然就會出錯崇众,另外查詢條件也是支持通配符的("*"號)掂僵。這些常用的查詢還是比較簡單的,和sql也比較接近顷歌,這里就不過多介紹了锰蓬,建議自己動手練習一下。
二眯漩、spring boot整合solr
spring boot有關于solr的支持官方文檔芹扭, Spring Data JPA同樣也支持solr,自己有興趣可以嘗試一下赦抖,其實本質上是一樣的舱卡。我這次使用的是Spring Data Solr。
打開idea,創(chuàng)建一個spring boot項目,添加相關的依賴序仙,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypc</groupId>
<artifactId>solrdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>solrdemo</name>
<description>solr demo test</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
接下來是項目的配置文件邦鲫,打開application.properties
server.port=9090
# 數據庫配置
spring.datasource.url=jdbc:postgresql://localhost:5432/springboot?useSSL=false&characterEncoding=utf8
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=123456
# mybatis 配置
mybatis.type-aliases-package=com.ypc.solrdemo.entity
mybatis.mapper-locations=classpath:mapper/*.xml
# solr配置
spring.data.solr.host=http://localhost:8983/solr/custom_core
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
配置solr host的時候注意一定加上自己的core的名稱,不然會報500,錯誤,因為一個solr可以有多個core,服務器是沒辦法判斷你要使用那個core,所以必須顯示指定core的名稱蝴簇。
接下來是相關的controller service 以及mybatis的mapper和mapper.xml代碼,因為比較簡單這里就不再貼相關代碼了匆帚。
我這里主要是通過solr client熬词,簡單的做了一些CRUD操作:
1、單個查詢:
根據 id查詢吸重,先查詢solr互拾,如果solr沒用查詢到再查詢數據庫,并將結果添加到solr嚎幸;
2颜矿、查詢所有
也是先通過solr去查詢,如果查詢不到就去數據庫查詢嫉晶,然后將查詢結果添加到solr骑疆;
3、單個添加
先更新到數據庫替废,如果異常箍铭,則回滾,這里使用@Transactional注解椎镣,然后將添加到solr诈火,并提交,如果異常則回滾solr状答。
4冷守、根據id刪除
和單個添加一樣刀崖,如果數據庫事務失敗則回滾;成功教沾,則刪除solr里面的信息蒲跨,solr失敗同樣回滾译断。
5授翻、頁面搜索
通過一個搜索框,根據用戶搜索條件孙咪,查詢相關信息堪唐,并根據年齡排序,一個非常簡單的html頁面翎蹈。
一淮菠、根據Id查詢
根據id查詢方法有幾種,先說一種最簡單或者說最笨的方法荤堪,service代碼如下:
public User queryById(int id) {
// 先通過solr查詢合陵,查詢不到查數據庫
SolrQuery query = new SolrQuery();
query.setQuery("id:" + id);
User user = null;
try {
QueryResponse response = solrClient.query(query);
SolrDocumentList documentList = response.getResults();
if (!documentList.isEmpty()) {
for (SolrDocument document:documentList) {
user = new User();
user.setId(id);
user.setAddress((String) document.get("address"));
user.setMobile((String) document.get("mobile"));
user.setUserName((String) document.get("userName"));
user.setAge((Integer) document.get("age"));
user.setDescription((String) document.get("description"));
LOGGER.info(">>>> query user from solr success <<<<");
}
} else {
// 從數據庫查詢
user = userMapper.queryById(id);
if (user != null) {
solrClient.addBean(user,1000);
}
LOGGER.info(">>>> query user from database <<<<");
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
}
return user;
}
這里query.setQuery("id:" + id);其實等同于query.set("q","id:" + id);也就是說使用solrClient查詢的時候設置的參數名稱和在admin管理頁面的查詢條件名稱是一樣的。當然也可以直接使用solrClient.getById(String.valueOf(id))這個方法澄阳,我為了更直觀的了解查詢時設置查詢參數拥知,因此使用了query.setQuery("id:" + id)方法。
查詢返回的結果是一個QueryResponse就是整個的查詢結果碎赢,包括header和結果兩部分低剔,如下圖:
響應頭包括的內容有查詢狀態(tài)、查詢時間肮塞、參數襟齿;而結果里面是查詢到的數量,查詢起始數量枕赵,以及最終的查詢結果(這個結果是一個document list)猜欺。
先根據id查詢solr,獲取到SolrDocumentList拷窜,然后遍歷(雖然只有一個document)开皿,然后轉換成我們需要的實體對象。但是如果字段多的話在實體類User和SolrDocument之間轉換非常的不方便装黑,所以我們需要修改下項目的User類
@SolrDocument(solrCoreName = "custom_core")
public class User {
@Id
@Field
private int id;
@Field
private String userName;
@Field
private String sex;
@Field
private String address;
@Field
private String description;
@Field
private int age;
@Field
private String mobile;
@Field
private String period;
// 方法省略
}
可以在實體類上添加上@SolrDocument(solrCoreName = "custom_core")這個注解副瀑,表明這個實體類可以轉換成SolrDocument對象,此外一定不要忘了指定core的名稱恋谭。如果實體類屬性和solr的field對應不上糠睡,可以使用@Field(value="field名稱")注解,實現實體類和solr field之間的對應關系疚颊。
這樣在像solr添加User對象的時候就不需要手動將其轉換為SolrDocument了狈孔,這點在添加用戶時候我們再看⌒湃希現在的情況是我們查詢出來的是SolrDocument,而我們返回的User對象均抽,當然從功能上講直接返回SolrDocument好像也是可行的嫁赏。但是從邏輯來講,返回的應該就是User對象油挥。我自己看了一下好像不能直接轉換潦蝇,當然通過反射自己寫一個工具類方法也是可行的。我使用的是通過json深寥,其實solr查詢返回結果的形式攘乒,默認就是json,我只需要將SolrDocument顯示的轉成json惋鹅,然后在轉成User對象就可以了则酝,代碼如下:
@Override
public User queryById(int id) {
User user = null;
try {
SolrDocument solrDocument = solrClient.getById(String.valueOf(id));
Gson gson = new Gson();
// 方法1
String solrString = gson.toJson(solrDocument);
user = gson.fromJson(solrString,User.class);
// 方法2
Map<String,Object> map = solrDocument.getFieldValueMap();
user = gson.fromJson(map.toString(),User.class);
if (null == user) {
user = userMapper.queryById(id);
solrClient.addBean(user,1000);
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
}
return user;
}
上面的方法中代碼明顯就簡單了不少,而且兩種方法都是可行的闰集,只是方法1先將SolrDocument轉成json串沽讹,然后再將json傳轉成對應的User對象;方法2值獲取SolrDocument的key vlaue值武鲁,然后轉成string后再轉成User對象爽雄。兩種方法都是可行的,但是我感覺方法2邀好一點洞坑,中間省去了對象轉json串這個步驟盲链,當然用其他方法也是可行的。
二迟杂、查詢所有
這個其實和單個查詢沒有上面區(qū)別刽沾,只是查詢條件設置成":"就可以了,代碼如下:
@Override
public List<User> queryAll() {
List<User> list = null;
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
query.setStart(0);
query.setRows(20);
try {
QueryResponse response = solrClient.query(query);
SolrDocumentList documentList = response.getResults();
if (!documentList.isEmpty()) {
Gson gson = new Gson();
String listString = gson.toJson(documentList);
list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
// list = convertToModel(documentList);
LOGGER.info(">>>> query user from solr success <<<<");
} else {
list = userMapper.queryAll();
solrClient.addBeans(list);
LOGGER.info(">>>> query user from database <<<<");
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
}
return list;
}
查詢所有信息排拷,查出來結果是一個SolrDocumentList侧漓,可以遍歷出每個SolrDocument,并轉成User對象监氢,也可以直接轉成json串布蔗,然后直接轉成List<User>。另外是我設置的是值查詢前20個(實際沒有20個)浪腐,本來是想做一個分頁查詢的纵揍,但是前端沒處理好,所以就沒用分頁做查詢了议街。
三泽谨、單個添加
因為我沒用寫專門的form表單,所以就直接REST Client工具提交了用戶信息了,提交數據格式是json:controller和service如下:
@ResponseBody
@PostMapping("/insert")
public Map<String, Object> insertUser(@RequestBody User user) {
Map<String,Object> result = userService.insertAndUpdate(user);
return result;
}
// service
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> insertAndUpdate(User user) {
Map<String,Object> result = new HashMap<>();
result.put("success",false);
// 返回結果表示受影響的數據條數吧雹,而不是id值
int insert = userMapper.insertUser(user);
if (insert != 1) {
throw new RuntimeException(" >>>> insert user to database failed,the return value should be 1,but result is:" + insert + " <<<<");
}
// 插入或者更新solr數據
try {
UpdateResponse response = solrClient.addBean(user,1000);
int staus = response.getStatus();
if (staus != 0) {
LOGGER.error(">>>> update solr document failed <<<<");
solrClient.rollback();
result.put("message","insert user to solr failed");
return result;
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
result.put("message",e.getMessage());
return result;
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
result.put("message",e.getMessage());
return result;
}
result.put("message","insert user to solr success");
result.put("success",true);
return result;
}
solr的添加和更新是一個操作骨杂,添加則添加新數據,更新覆蓋原有數據雄卷,其他都是一樣的搓蚪,我這里使用了@Transactional注解,當我插入數據庫的數據記錄不等于1時丁鹉,自己拋出一個運行時異常(當然自己定義一個專門的統一異常更好些)妒潭,數據回滾。在更新solr的數據時鳄炉,solrClient.addBean(user,1000)杜耙;即在1秒鐘內自動提交事務搜骡,然后根據狀態(tài)值判斷是否成功拂盯,如果不成功則回滾,但是這一點自己有點疑問记靡,就是如果在1秒內提交事務失敗谈竿,是否需要顯式回滾?摸吠?這個還需要自己再查下相關的資料空凸。個人感覺應該是不需要的,但是為了保險起見寸痢,顯示的回滾了呀洲,如果有了解的朋友希望能指點一下。其實這里自己還遇到一個問題啼止,就是數據庫插入數據后道逗,插入的對象如何返回id的問題,因為數據庫設置的是id自增主鍵献烦,而返回結果是影響的行數滓窍,即插入的數據記錄數,那么如何獲取新插入的數據id呢巩那?開始自己想的是查詢最后一次插入的id值吏夯,即用:SELECT currval('user_id_seq');類似于mysql的SELECT LAST_INSERT_ID()。但是自己還想獲取受影響的記錄數以判斷插入是否成功即横,以便回滾噪生。后來網上找了些資料,修改mapper.xml文件如下:
<insert id="insertUser" parameterType="com.ypc.solrdemo.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user (username,age,mobile,address,sex,description)
values (#{userName},#{age},#{mobile},#{address},#{sex},#{description})
</insert>
這樣插入數據成功后东囚,對應的id值會直接映射到對象中跺嗽,mybatis官方文檔。useGeneratedKeys屬性只對插入和修改有效,這告訴MyBatis使用JDBC getGeneratedKeys方法來檢索數據庫內部生成的主鍵值(像MySQL或pgsql自動增量字段)抛蚁,默認值為false陈醒;keyProperty用來標識MyBatis將設置getGeneratedKeys返回的鍵值映射到哪個屬性上。
四瞧甩、根據id刪除
和根據id插入基本一樣钉跷,代碼如下:
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> deleteUserById(int id) {
Map<String,Object> result = new HashMap<>();
result.put("success",false);
// 先刪除數據庫,再更新solr
int delete = userMapper.deleteById(id);
if (delete != 1) {
throw new RuntimeException(">>>> delete user failed ,user id=" + id + " <<<<");
}
try {
UpdateResponse response = solrClient.deleteById(String.valueOf(id),1000);
int status = response.getStatus();
if (status != 0) {
LOGGER.error(">>>> delete user from solr failed ,user id=" + id + " <<<<");
solrClient.rollback();
result.put("message","delete user to solr failed");
return result;
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
result.put("message",e.getMessage());
return result;
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
result.put("message",e.getMessage());
return result;
}
result.put("success",true);
result.put("message","delete user success");
return result;
}
五肚逸、搜索框搜索
自己寫了一個簡單的html頁面爷辙,作為搜索框使用,用戶輸入條件朦促,后臺根據條件查詢膝晾,并且自己設置了一個簡單的根據年齡排序的功能,比較low务冕,但是功能上可以使用血当,點擊年齡會進行順序或者逆序排序。
搜索功能禀忆,也就是根據多個字段進行OR查詢臊旭,這里其實可以設置查詢根據年齡順序或是逆序,但是為了后面排序的功能箩退,這里就省略了离熏,代碼如下:
@Override
public List<User> queryByCondition(String de) {
List<User> list = null;
// 關鍵字模糊查詢
SolrQuery query = new SolrQuery();
String nameLike = "userName:*" + de + "*";
String desLike = " OR description:*" + de+ "*";
String sexLike = " OR sex:*" + de + "*";
String addLike = " OR address:*" + de + "*";
query.set("q",nameLike + desLike + sexLike + addLike);
query.setStart(0);
query.setRows(20);
try {
QueryResponse response = solrClient.query(query);
SolrDocumentList documentList = response.getResults();
if (!documentList.isEmpty()) {
Gson gson = new Gson();
String listString = gson.toJson(documentList);
list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
} else {
LOGGER.info(">>>> no result returned by the filter query word: " + de + " <<<<");
}
} catch (SolrServerException e) {
LOGGER.error(e.getMessage(),e);
} catch (IOException e) {
LOGGER.error(e.getMessage(),e);
}
return list;
}
自己簡單測試一下,結果如下戴涝,可見年齡順序既不是順序也不是逆序:
而根據年齡排序滋戳,只需要在上面代碼添加排序的規(guī)則就可以了,另外就是ajax傳遞參數時將是順序還是逆序這個標識位傳遞給后臺即可啥刻,這里就不再貼代碼了奸鸯。
好了,以上就是這次spring boot整合solr的一個很簡單的demo郑什,其實單機的solr操作并不復雜府喳,只需要掌握常用的一些操作即可。今天主要學習就是通過admin管理頁面熟悉常用的查詢操作和spring boot整合后solrClient的使用蘑拯,此外就是實體類和SolrDocument之間轉換的問題钝满。當然自己的demo很簡單,如果是復雜的場景就又不太一樣了申窘。
這次的代碼已經更新到我的github弯蚜,如果文章或代碼中有什么問題,歡迎指正L攴ā碎捺!