1、solrj介绍
SolrJ是操作Solr的JAVA客户端,它提供了增加、修改、删除、查询Solr索引的JAVA接口。SolrJ针对 Solr提供了Rest 的HTTP接口进行了封装, SolrJ底层是通过使用httpClient中的方法来完成Solr的操作。HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。学过python爬虫的应该都知道requests库,这个库可以很简单的通过几行代码就构造出一个HTTP请求,并拿到HTTP响应,当然,解析需要另外的库。
2、SSM整合solr
2.1 添加依赖
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>8.5.1</version>
</dependency>
要想使用solrj就必须要添加solr-solrj的依赖,由于我的solr版本是8.5.1,所以我添加的依赖版本是8.5.1,具体以你的solr版本为准。
2.2 配置文件
spring-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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--
Spring整合Solr
-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="httpSolrClient" class="org.apache.solr.client.solrj.impl.HttpSolrClient">
<constructor-arg name="builder" value="${solrUrl}"/>
</bean>
</beans>
这个配置文件通过IoC的方式注入了HttpSolrClient这个bean,创建关闭对象都交给spring了,我们不用操心了。不过要想这个生效,把这个文件在spring-mvc.xml中导入。
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/solr
user=root
pass=huwenlong
solrUrl=http://localhost:8081/solr/core_demo
这个properties文件定义了mysql数据库以及solrUrl的信息。
spring-mvc.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="suffix" value=".jsp"/>
</bean>
<context:component-scan base-package="com.qianfeng.controller"/>
<mvc:annotation-driven>
<!-- 使用fastjson来替代默认的HttpMessageConverter以提高执行效率-->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<!-- 设置UTF-8编码防止乱码-->
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler/>
<import resource="classpath:spring-mybatis.xml"/>
<import resource="classpath:spring-solr.xml"/>
</beans>
这个配置文件设置了FastJson来代替默认的json转换器,因为我们直接返回json字符串,前后端分离。
2.3 给entity添加注解
Products.java
package com.qianfeng.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.solr.client.solrj.beans.Field;
import java.util.Date;
import java.io.Serializable;
/**
* (Products)实体类
*
* @author makejava
* @since 2020-04-27 17:51:03
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Products implements Serializable {
private static final long serialVersionUID = 355132763315683774L;
/**
* 商品编号
*/
private Integer pid;
@Field("id")
private String id;
/**
* 商品名称
*/
@Field("prod_pname")
private String pname;
/**
* 商品分类ID
*/
private Integer catalog;
/**
* 商品分类名称
*/
@Field("prod_catalog_name")
private String catalogName;
/**
* 价格
*/
@Field("prod_price")
private Double price;
/**
* 数量
*/
private Integer number;
/**
* 商品描述
*/
@Field("prod_description")
private String description;
/**
* 图片名称
*/
@Field("prod_picture")
private String picture;
/**
* 上架时间
*/
private Date releaseTime;
}
managed-schema
...
<field name="prod_pname" type="text_ik" indexed="true" stored="true" required="true" />
<field name="prod_catalog_name" type="string" indexed="true" stored="true" required="true" />
<field name="prod_price" type="pdouble" indexed="true" stored="true" required="true" />
<field name="prod_description" type="text_ik" indexed="true" stored="true" required="true" />
<field name="prod_picture" type="string" indexed="false" stored="true" required="true" />
...
由于我在solr_home下的core_demo下的managed-schema文件中自定义五个字段,和数据库中的字段名不一致,所以使用@Field注解将字段对应起来,注意,对应起来的两个字段必须类型一致。
2.4 编写测试类
SolrTest.java
package com.qianfeng.service;
import com.qianfeng.entity.Products;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.params.SolrParams;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-mybatis.xml")
public class SolrTest {
@Resource
private HttpSolrClient solrClient;
@Value("${solrUrl}")
private String solrUrl;
/**
* 每条索引都有一个唯一的id,如果一个Document的id存在,则执行修改操作,如果不存在,执行添加操作
* @throws IOException
* @throws SolrServerException
*/
@Test
public void testSaveOrUpdate() throws IOException, SolrServerException {
//HttpSolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build();
Products p = new Products();
p.setPid(999999);
p.setPname("后台测试solr");
p.setCatalogName("测试类别");
p.setPrice(11.1);
p.setDescription("测试测试");
p.setPicture("bbb.jpg");
UpdateResponse response = solrClient.addBean(p);
// solrClient.commit();
// solrClient.close();
}
/**
* 删除索引
* @throws IOException
* @throws SolrServerException
*/
@Test
public void testDelete() throws IOException, SolrServerException {
//HttpSolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build();
solrClient.deleteById("999999");
//solrClient.deleteByQuery("*:*");先查询后删除,这个意思是删除全部
//solrClient.deleteByQuery("prod_price:[20 TO *");
// solrClient.commit();
// solrClient.close();
}
@Test
public void testSimpleQuery() throws IOException, SolrServerException {
HttpSolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build();
//传递查询参数
SolrParams solrParams = new SolrQuery("*:*");
//进行查询,返回查询响应
QueryResponse response = solrClient.query(solrParams);
//建立映射
List<Products> products = response.getBeans(Products.class);
System.out.println(products.size());
for (Products product : products) {
System.out.println(product);
}
// solrClient.commit();
// solrClient.close();
}
@Test
public void testComplexQuery() throws IOException, SolrServerException {
//HttpSolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build();
String keyWord = "手机";
SolrQuery solrQuery = new SolrQuery();
//关键字查询,查询商品名称
if(StringUtils.isEmpty(keyWord)){
solrQuery.set("q","*");
}else {
solrQuery.set("q",keyWord);
}
String categoryName = "";
//对类别进行筛选
if(!StringUtils.isEmpty(categoryName)){
solrQuery.addFilterQuery("prod_catalog_name:"+categoryName);
}
//对价格进行筛选,从前台传过来的参数形如price=10-100或price=-100或price=10-
String priceStr = "";
if(!StringUtils.isEmpty(priceStr)){
String fq = "";
String[] split = priceStr.split("-");
if(split.length==1){
fq = "prod_price:["+split[0]+" TO *]";
}else if (split.length==2){
String prefix = split[0];
if(StringUtils.isEmpty(split[0])){
prefix = "*";
}
fq = "prod_price:["+prefix+" TO "+split[1]+"]";
}
solrQuery.addFilterQuery(fq);
}
//对价格进行排序,升序与降序,默认是0,1代表升序,2代表降序
int priceSort = 1;
if(priceSort==1){
solrQuery.addSort("prod_price", SolrQuery.ORDER.asc);
}else if(priceSort==2){
solrQuery.addSort("prod_price", SolrQuery.ORDER.desc);
}
//从前台获取当前页
int currentPage = 1;
//从前台获取每页条目数
int pageSize = 5;
//就算开始的索引
int start = pageSize*(currentPage-1);
solrQuery.setStart(start);
solrQuery.setRows(pageSize);
//设置查询哪些字段
//solrQuery.setFields("prod_catalog_name","prod_pname");
//设置默认字段,就不再需要在q中指定prod_pname了
solrQuery.set("df","prod_pname");
//设置高亮
//启用高亮
solrQuery.setHighlight(true);
//指定高亮字段
solrQuery.addHighlightField("prod_pname");
//指定高亮字段前面的字符
solrQuery.setHighlightSimplePre("<font color='red'");
//指定高亮字段后面的字符
solrQuery.setHighlightSimplePost("</font>");
QueryResponse response = solrClient.query(solrQuery);
List<Products> products = response.getBeans(Products.class);
Map<String, Map<String, List<String>>> map = response.getHighlighting();
System.out.println(products.size());
for (Products product : products) {
Map<String, List<String>> map1 = map.get(product.getId());
List<String> list = map1.get("prod_pname");
if (list!=null){
System.out.println(list.get(0)+"\t"+product.getCatalogName()+"\t"+product.getPname()+"\t"+product.getPrice());
}
else {
System.out.println(product.getCatalogName()+"\t"+product.getPname()+"\t"+product.getPrice());
}
}
// solrClient.commit();
// solrClient.close();
}
}
这个测试类实现了对solr索引库的增删改查,增加和修改是同一个方法,就是看id是否存在,不存在就是增加,存在就是修改。
2.5 编写service
ProductsServiceImpl.java
package com.qianfeng.service.impl;
import com.qianfeng.entity.Products;
import com.qianfeng.dao.ProductsDao;
import com.qianfeng.service.ProductsService;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* (Products)表服务实现类
*
* @author makejava
* @since 2020-04-27 17:51:03
*/
@Service("productsService")
public class ProductsServiceImpl implements ProductsService {
@Resource
private HttpSolrClient solrClient;
@Resource
private ProductsDao productsDao;
/**
* 通过ID查询单条数据
*
* @param pid 主键
* @return 实例对象
*/
@Override
public Products queryById(Integer pid) {
return this.productsDao.queryById(pid);
}
/**
* 查询多条数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
@Override
public List<Products> queryAllByLimit(int offset, int limit) {
return this.productsDao.queryAllByLimit(offset, limit);
}
/**
* 新增数据
*
* @param products 实例对象
* @return 实例对象
*/
@Override
public Products insert(Products products) {
this.productsDao.insert(products);
return products;
}
/**
* 修改数据
*
* @param products 实例对象
* @return 实例对象
*/
@Override
public Products update(Products products) {
this.productsDao.update(products);
return this.queryById(products.getPid());
}
/**
* 通过主键删除数据
*
* @param pid 主键
* @return 是否成功
*/
@Override
public boolean deleteById(Integer pid) {
return this.productsDao.deleteById(pid) > 0;
}
@Override
public List<Products> getProductsFromSolrByPage(String keyWord, String categoryName, String priceStr, int priceSort, int currentPage){
SolrQuery solrQuery = new SolrQuery();
//关键字查询,查询商品名称
if(StringUtils.isEmpty(keyWord)){
solrQuery.set("q","*");
}else {
solrQuery.set("q",keyWord);
}
if(!StringUtils.isEmpty(categoryName)){
solrQuery.addFilterQuery("prod_catalog_name:"+categoryName);
}
if(!StringUtils.isEmpty(priceStr)){
String fq = "";
String[] split = priceStr.split("-");
if(split.length==1){
fq = "prod_price:["+split[0]+" TO *]";
}else if (split.length==2){
String prefix = split[0];
if(StringUtils.isEmpty(split[0])){
prefix = "*";
}
fq = "prod_price:["+prefix+" TO "+split[1]+"]";
}
solrQuery.addFilterQuery(fq);
}
if(priceSort==1){
solrQuery.addSort("prod_price", SolrQuery.ORDER.asc);
}else if(priceSort==2){
solrQuery.addSort("prod_price", SolrQuery.ORDER.desc);
}
int pageSize = 5;
//计算开始的索引
int start = pageSize*(currentPage-1);
solrQuery.setStart(start);
solrQuery.setRows(pageSize);
solrQuery.set("df","prod_pname");
//设置高亮
//启用高亮
solrQuery.setHighlight(true);
//指定高亮字段
solrQuery.addHighlightField("prod_pname");
//指定高亮字段前面的字符
solrQuery.setHighlightSimplePre("<font color='red'>");
//指定高亮字段后面的字符
solrQuery.setHighlightSimplePost("</font>");
QueryResponse response = null;
try {
response = solrClient.query(solrQuery);
//返回总的条目数,这个条目数是未分页前的总条目数
long numFound = response.getResults().getNumFound();
System.out.println(numFound);
List<Products> products = response.getBeans(Products.class);
Map<String, Map<String, List<String>>> map = response.getHighlighting();
for (Products product : products) {
Map<String, List<String>> map1 = map.get(product.getId());
List<String> list = map1.get("prod_pname");
//list有可能为空,因为用户什么也不输入的情况下就是这样
if (list!=null){
product.setPname(list.get(0));
}
}
return products;
} catch (SolrServerException | IOException e) {
e.printStackTrace();
}
return null;
}
}
getProductsFromSolrByPage这个方法就是根据商品名、类型名、价格范围进行查询,然后根据价格升序还是降序与当前页数进行分页查询,此外还对查询关键字进行了高亮显示。
2.6 编写controller
ProductsController.java
package com.qianfeng.controller;
import com.qianfeng.entity.Products;
import com.qianfeng.service.ProductsService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* (Products)表控制层
*
* @author makejava
* @since 2020-04-27 17:51:03
*/
@RestController
@RequestMapping("products")
public class ProductsController {
/**
* 服务对象
*/
@Resource
private ProductsService productsService;
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("selectOne")
public Products selectOne(Integer id) {
return this.productsService.queryById(id);
}
@GetMapping("search")
public List<Products> getProductsFromSolrByPage(@RequestParam(value = "keyWord",defaultValue = "")String keyWord,
@RequestParam(value="cn",defaultValue = "")String categoryName,
@RequestParam(value = "priceStr",defaultValue = "")String priceStr,
@RequestParam(value = "priceSort",defaultValue = "0")int priceSort,
@RequestParam(value = "cp",defaultValue = "1") int currentPage){
List<Products> products = productsService.getProductsFromSolrByPage(keyWord, categoryName,priceStr, priceSort, currentPage);
return products;
}
}
由于使用了RestFul风格,并且前后端分离,所以直接返回的是对象,fastjson会自动帮我们转换为json字符串
2.7 编写前端页面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.js"></script>
</head>
<body>
<form action="#">
商品名:<input type="text" id="pname"> <br>
种类:<input type="text" id="cn"> <br>
价格范围: <input type="text" id="priceStr"> <br>
<input type="button" onclick="search()" value="提交">
</form>
<table>
<tr>
<th>商品名</th>
<th>商品种类</th>
<th>商品图片</th>
<th>商品价格</th>
</tr>
<tbody id="tb">
</tbody>
</table>
</body>
<script>
function search() {
var keyWord = $("#pname").val();
var cn = $("#cn").val();
var priceStr = $("#priceStr").val();
$.ajax({
url:"/products/search",
type:"get",
data:{keyWord:keyWord,cn:cn,priceStr:priceStr},
dataType:"json",
success:function (data) {
$("#tb").empty();
$.each(data,function (n,p) {
var tr = $("<tr></tr>");
var pname = $("<td></td>").html(p.pname);
tr.append(pname);
var cn = $("<td></td>").text(p.catalogName);
tr.append(cn);
var picture = $("<td></td>").text(p.picture);
tr.append(picture);
var price = $("<td></td>").text(p.price);
tr.append(price);
$("#tb").append(tr);
})
}
})
}
</script>
</html>
由于是前后端分离项目,所以使用ajax异步获取json字符串,并进行遍历添加元素。注意,我在这里使用的是get方式请求,如果使用Tomcat插件的话需要加上在configuration
节点里面加上<uriEncoding>UTF-8</uriEncoding>
,否则会出现乱码,当然,你也可以直接使用post方式
3、SpringBoot整合solr
SpringBoot整合solr和上面的方式只有一点不一样,就是依赖,其余增删改查方式一模一样
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
添加spring-boot-starter-data-solr这个依赖,然后在application.yml文件中这样配置:
spring:
data:
solr:
host: http://localhost:8081/solr/core_demo
指定了solr服务器core的地址。
网友评论