上周简单的学习了solr的数据导入以及IK分词器的安装,今天学习一下solr的一些基本操作,以及和spring boot的整合。在开始项目之前简单的学习一下solr的一些查询的操作
一、admin页面简单查询
先在本地启动solr,然后进入solr admin管理页面。在这之前我修改了数据库的数据,所以需要重新导入新的数据。选择自定义的core,点击"Query",入下图所示:
简单介绍一下几个常用查询参数:
"q":the quert string,即查询条件,":"表示查询所有内容,支持多个查询条件。
"fq":fiter query,即过滤查询,使用Filter Query可以充分利用FilterQuery Cache,提高检索性能。个人觉得可能更多时候用来做区间查询。
"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(默认):
图-2.png
因为数据比较少,只有3条记录,所以分页没起到效果,注意一点的是:solr查询的AND、OR、TO这些必须要大写,自己用小写发现没起作用,查询条件的格式一定邀注意,不然就会出错,另外查询条件也是支持通配符的("*"号)。这些常用的查询还是比较简单的,和sql也比较接近,这里就不过多介绍了,建议自己动手练习一下。
二、spring boot整合solr
spring boot有关于solr的支持官方文档, Spring Data JPA同样也支持solr,自己有兴趣可以尝试一下,其实本质上是一样的。我这次使用的是Spring Data Solr。
打开idea,创建一个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和结果两部分,如下图:
图-3.png
响应头包括的内容有查询状态、查询时间、参数;而结果里面是查询到的数量,查询起始数量,以及最终的查询结果(这个结果是一个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串这个步骤,当然用其他方法也是可行的。
二、查询所有
这个其实和单个查询没有上面区别,只是查询条件设置成":"就可以了,代码如下:
@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秒钟内自动提交事务,然后根据状态值判断是否成功,如果不成功则回滚,但是这一点自己有点疑问,就是如果在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,但是功能上可以使用,点击年龄会进行顺序或者逆序排序。
图-4.png
搜索功能,也就是根据多个字段进行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;
}
自己简单测试一下,结果如下,可见年龄顺序既不是顺序也不是逆序:
而根据年龄排序,只需要在上面代码添加排序的规则就可以了,另外就是ajax传递参数时将是顺序还是逆序这个标识位传递给后台即可,这里就不再贴代码了。
好了,以上就是这次spring boot整合solr的一个很简单的demo,其实单机的solr操作并不复杂,只需要掌握常用的一些操作即可。今天主要学习就是通过admin管理页面熟悉常用的查询操作和spring boot整合后solrClient的使用,此外就是实体类和SolrDocument之间转换的问题。当然自己的demo很简单,如果是复杂的场景就又不太一样了。
这次的代码已经更新到我的github,如果文章或代码中有什么问题,欢迎指正!!
网友评论