引言
续上一个博文,原生的实在是太麻烦了,这次我们用spring集成的工具类。这里我使用了SpringBoot。
正经一点的搜索
分词器
我们这样搜索类似于写SQL语句,真正的在搜索栏中查询难道要用"*"或者是指定那个字段嘛?当然不是,为了更符合我们的习惯,我们要配置IK中文分词器和相关的域
IK下载地址:https://github.com/EugenePig/ik-analyzer-solr5
编译就不再赘述了,可以使用Maven打包进行生成jar文件,然后放到Solr的server/solr-webapp/webapp/WEB-INF/lib目录中。
接下来要把IK能力声明到我们的核心中,把下面的代码放到server/solr/mycore/conf/managed-schema中
<fieldType name="text_ik" class="solr.TextField">
<analyzer type="index" useSmart="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
<analyzer type="query" useSmart="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
然后重启我们的Solr
Solr stop -all
然后我们在自己定义的核心中选择Analysis,我们随便写一句话,让IK分词器为我们分词。
配置域
域大致分为三种:普通域、复制域、静态域
普通域:普通域就是我们在数据库中存放的字段,也就是能够显示在前端页面上的,但是不一定是作为查询依据的
复制域:作为搜索检索的字段
动态域:如果某个字段是不确定的参数,需要用动态域进行声明,好比我们生产很多产品,但是电视有尺寸,手机有网络制式,电视里的规格对于手机并不适用吧,所以像这样的规格就要用到动态域。
实战
因为之前我们没有配置域,所以是默认生成的,我们需要自己手动创建,所以我们要删除原来的内容。在web中的Documents输入下面的代码,并且选择类型为XML
<delete><query>*:*</query></delete><commit/>
设置域
因为我这里的实例时乌云镜像的数据库,所以我只对于相关的进行配置,大家做个参考就好。
找到自己定义的核心下面的managed-schema
路径\server\solr\mycore\conf\managed-schema
这是标准域,也就是我需要反馈到前端页面上的数据字段。注意一下数据类型嗷
<field name="wybug_title" type="string" indexed="true" stored="true" multiValued="false" docValues="false" /> <field name="wybug_author" type="text_ik" indexed="true" stored="true" multiValued="false" docValues="false" /> <field name="wybug_date" type="pdate" indexed="true" stored="true" multiValued="false" docValues="false" /> <field name="wybug_type" type="string" indexed="false" stored="true" multiValued="false" docValues="false" /> <field name="wybug_level" type="string" indexed="true" stored="true" multiValued="false" docValues="false" /> <field name="wybug_detail" type="text_ik" indexed="true" stored="true" multiValued="false" docValues="false" />
这是复制域,就是我们提供搜索关键字的字段。
<field name="keywords" type="text_general" indexed="true" stored="false" multiValued="true" /> <copyField source="wybug_level" dest="keywords" /> <copyField source="wybug_title" dest="keywords" /> <copyField source="wybug_detail" dest="keywords" />
在这个Demo中没有用到动态域
数据初始化
设置好后重启solr,并且执行原来的从数据库导入solr的代码// 由于时间关系,对于标准域中的字段,我没有完全添加,注意一下嗷。
String solrUrl = "http://localhost:8983/solr/mycore";
HttpSolrClient solrClient = new HttpSolrClient.Builder(solrUrl)
.withConnectionTimeout(10000) // 连接超时时间
.withSocketTimeout(60000) // 通讯超时时间
.build(); // 构造
SolrInputDocument solrInputFields = new SolrInputDocument();
DruidDataSource source = new DruidDataSource();
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/wooyun?serverTimezone=UTC");
source.setUsername("study");
source.setPassword("123456");
DruidPooledConnection conn = source.getConnection();
Statement statement = conn.createStatement();
String sql = "SELECT * FROM bugs";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
solrInputFields = new SolrInputDocument();
solrInputFields.addField("id", resultSet.getInt("id"));
solrInputFields.addField("wybug_title", resultSet.getString("wybug_title"));
solrInputFields.addField("wybug_date", resultSet.getString("wybug_date"));
solrInputFields.addField("wybug_level", resultSet.getString("wybug_level"));
solrInputFields.addField("wybug_detail", resultSet.getString("wybug_detail"));
solrClient.add(solrInputFields);
}
solrClient.commit();
创建工程
现在我以springBoot作为实例进行演示,创建一个SpringBoot工程,进行如下配置。
导入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alc</groupId>
<artifactId>springboot-solr</artifactId>
<version>1.0</version>
<name>springboot-solr</name>
<description>Demo4SpringBoot2Solr</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置application.yml
spring:
data:
solr:
host: http://localhost:8983/solr/mycore
server:
port: 8088
配置启动器(SpringbootApplication)
package com.alc.springbootsolr;
import org.apache.solr.client.solrj.SolrClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.solr.core.SolrTemplate;
import org.springframework.data.solr.repository.config.EnableSolrRepositories;
@SpringBootApplication
@EnableSolrRepositories
public class SpringbootSolrApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSolrApplication.class, args);
}
// 这个不写也会进行自动注入的,只是IDE会报错,加上这个就会扫描到Bean,加上也不算错,不加也没问题。
// @Autowired
// private SolrClient solrClient;
//
// @Bean
// public SolrTemplate getSolrTemplate() {
// return new SolrTemplate(solrClient);
// }
}
定义实体
package com.alc.springbootsolr.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.solr.client.solrj.beans.Field;
import org.springframework.data.annotation.Id;
import org.springframework.data.solr.core.mapping.SolrDocument;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@SolrDocument(solrCoreName = "mycore")
public class Bugs implements Serializable {
@Id
@Field
private Integer id;
private String wybug_id;
@Field
private String wybug_title;
private String wybug_corp;
private String wybug_author;
@Field
private String wybug_date;
private String wybug_open_date;
private String wybug_type;
private String wybug_level;
private String wybug_rank_0;
private String wybug_status;
private String wybug_from;
private String wybug_tags;
@Field
private String wybug_detail;
private String wybug_reply;
private String replys;
private String wybug_level_fromcorp;
private String wybug_rank_fromcorp;
@Field
private Integer Ranks;
}
定义solr操作的工具类
package com.alc.springbootsolr.search;
import com.alc.springbootsolr.entity.Bugs;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.SolrParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.solr.core.SolrTemplate;
import org.springframework.data.solr.core.query.Criteria;
import org.springframework.data.solr.core.query.Query;
import org.springframework.data.solr.core.query.SimpleQuery;
import org.springframework.data.solr.core.query.result.ScoredPage;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Component
public class SolrUtil {
@Autowired
private SolrTemplate solrTemplate; // solr的工具类,由Spring.data.solr提供
public List<Bugs> search(String keywords, Long page) {
System.out.println(keywords + "<===>" + page); // 显示一下我们的关键字和页码
Query query = new SimpleQuery();
Criteria filter; // 定义我们的过滤器
if (keywords.matches("[0-9]+")) {
filter = new Criteria("id").is(keywords); // 如果是数字的话,就判断id
} else { // 否则判断标题和内容
filter = new Criteria("wybug_title").contains(keywords);
filter.or("wybug_detail").contains(keywords); // 或包含的关系,相当于满足一个就够了,不需要解释叭。
}
query.setOffset(page);// 设置页码
query = query.addCriteria(filter); // 把我们的条件加进去
ScoredPage<Bugs> bugs = solrTemplate.queryForPage("", query, Bugs.class);
List<Bugs> content = bugs.getContent();
return content;
}
public boolean deleteAll() {
Query query = new SimpleQuery("*:*");
solrTemplate.delete("", query);
solrTemplate.commit("");
return true;
}
}
定义service层
package com.alc.springbootsolr.service;
import com.alc.springbootsolr.entity.Bugs;
import com.alc.springbootsolr.search.SolrUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SearchService {
@Autowired
private SolrUtil solrUtil;
public List<Bugs> search(String keywords,Long page) {
return solrUtil.search(keywords,page);
}
public boolean deleteAll() {
return solrUtil.deleteAll();
}
}
定义controller层
package com.alc.springbootsolr.controller;
import com.alc.springbootsolr.entity.Bugs;
import com.alc.springbootsolr.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class SearchController {
@Autowired
private SearchService searchService;
@GetMapping("/search")
public List<Bugs> search(String keywords,Long page) {
return searchService.search(keywords,page);
}
@GetMapping("/deleteAll")
public boolean deleteAll() {
return searchService.deleteAll();
}
}
测试
访问我们的接口:
http://localhost:8088/search?keywords=sina&page=1
http://localhost:8088/search?keywords=sina&page=2
http://localhost:8088/search?keywords=sina&page=3
http://localhost:8088/search?keywords=sina&page=4
http://localhost:8088/deleteAll
http://localhost:8088/search?keywords=sina&page=1
http://localhost:8088/search?keywords=sina
我们发现在删除之前是可以访问到数据的,删除之后就无法访问得到数据了。
解释
Q:为什么在查询、删除和提交的时候总有一个参数("")呢?
A:在我用的老版本确实不存在第一个参数,但是根据源码分析,String collection,里面是集合的名称,所以能够联想到是我们定义的core,因为我们在yml中配置了core了,所以就不需要进行写入任何内容了,如果说host是这样配置的 http://localhost:8983/solr/ ,那么就需要配置collection了,切记,不要重复配置,因为这样肯定会报错的,可以尝试一下。
当配置出现问题的时候,或者是域配置错了,就会出现这样的错误
ERROR 19596 --- [nio-8088-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: Error from server at http://localhost:8983/solr/mycore: Expected mime type application/octet-stream but got text/html. <html> ****这里是HTML代码****
Q:@Field是干嘛的?
A:我们在写入对象的时候,定义实体bean和索引的关系,但是我的这个实例中,没有封装查询,所以在这个样例中是没有效果的,但是建议添加上。