Solr简介与Solr搜索与分页,
1.solr简介
solr是以lucene为内核开发的企业级搜索应用 应用程序可以通过http请求方式来提交索引,查询索引,提供了比lucene更丰富的查询语言,是一个高性能,高可用环境全文搜索引擎
2.倒排索引的原理
- 实例文章表
| 文章id | 文章标题 | 文章内容 |
|---|---|---|
| 1 | 我爱中国 | 中国地大物博 |
| 2 | 香港是中国一部分 | 香港的英文是hongkong |
索引 文章id
我 1
爱 1
中国 1,2
香港 2
是 2
一部分 2
3.分词器
- 将中文拆分成有意义的词 常用的IK分词器,庖丁解牛分词器。
4.lucene
-
lucene是一个将text数据类型,分词建立索引的一个库,不适合企业级使用,企业级考虑高可用问题。
-
solr是一个企业级应用的搜索引擎,支持使用json格式提交数据。
-
json格式:
-
[] 代表数组
-
{} 对象(文档 document)
-
键值对 属性
-
模拟json
[
{
id:1,
title:'我爱中国',
content:'中国地大物博'
},
{
id:2,
title:'香港是中国一部分',
content:'香港的英文是hongkong'
}
]
- solr高可用版本参考网址:
https://blog.csdn.net/liaomin416100569/article/details/77301756
5.solr安装(docker)
核(core):是用于存储json格式的数据,等价于mysql中数据库的概念。
文档:一个json对象就是一个文档 相同属性的json数组集合就是一个表。
检测端口命令: netstat -aon | grep 8983 下载netstat :yum -y install net-tools telnet
[root@localhost ~]# docker exec -it --user=solr my_solr bin/solr create_core -c mycore
Copying configuration to new core instance directory:
/opt/solr/server/solr/mycore
Creating new core 'mycore' using command:
http://localhost:8983/solr/admin/cores?action=CREATE&name=mycore&instanceDir=mycore
{
"responseHeader":{
"status":0,
"QTime":2137},
"core":"mycore"}
6.solr搜索
- q表示按照什么字段来搜索
- 字段名:值 (where 列名=值)
- 支持or 和and语法
- 比如 i:1 and j:2
- 模拟数据
7.solr中文分词器配置
- 默认solr 没有使用中文分词器 所有搜索的词 都是整个句子就是一个词 搜索时 将单词全部写入才能搜索或者使用* 需要配置中文分词器
- 目前比较好用的分词器 是IK 2012年停更 只支持到 Lucene4.7 所有 solr5.5 需要lucene5支持 需要修改部分源码来支持solr5.5
<dependencies>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>5.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.5.5</version>
</dependency>
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
</dependencies>
- 重写IKAnalyzer类和IKTokenizer类
注意这里建包必须是:org.wltea.analyzer.lucene
IKAnalyzer类(类名必须要一样)
//修改后的类
package org.wltea.analyzer.lucene;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;
/**
* IK分词器,Lucene Analyzer接口实现
* 兼容Lucene 4.0版本
*/
public final class IKAnalyzer extends Analyzer {
private boolean useSmart;
public boolean useSmart() {
return useSmart;
}
public void setUseSmart(boolean useSmart) {
this.useSmart = useSmart;
}
/**
* IK分词器Lucene Analyzer接口实现类
*
* 默认细粒度切分算法
*/
public IKAnalyzer() {
this(false);
}
/**
* IK分词器Lucene Analyzer接口实现类
*
* @param useSmart 当为true时,分词器进行智能切分
*/
public IKAnalyzer(boolean useSmart) {
super();
this.useSmart = useSmart;
}
/**
* 重载Analyzer接口,构造分词组件
*/
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer _IKTokenizer = new IKTokenizer(this.useSmart());
return new TokenStreamComponents(_IKTokenizer);
}
}
IKTokenizer类(同上)
/**
* IK 中文分词 版本 5.0.1
* IK Analyzer release 5.0.1
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* 源代码由林良益(linliangyi2005@gmail.com)提供
* 版权声明 2012,乌龙茶工作室
* provided by Linliangyi and copyright 2012 by Oolong studio
*
*
*/
package org.wltea.analyzer.lucene;
import java.io.IOException;
import java.io.Reader;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;
/**
* IK分词器 Lucene Tokenizer适配器类
* 兼容Lucene 4.0版本
*/
public final class IKTokenizer extends Tokenizer {
// IK分词器实现
private IKSegmenter _IKImplement;
// 词元文本属性
private final CharTermAttribute termAtt;
// 词元位移属性
private final OffsetAttribute offsetAtt;
// 词元分类属性(该属性分类参考org.wltea.analyzer.core.Lexeme中的分类常量)
private final TypeAttribute typeAtt;
// 记录最后一个词元的结束位置
private int endPosition;
/**
* Lucene 4.0 Tokenizer适配器类构造函数
* @param in
* @param useSmart
*/
public IKTokenizer(boolean useSmart) {
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);
_IKImplement = new IKSegmenter(input, useSmart);
}
/*
* (non-Javadoc)
* @see org.apache.lucene.analysis.TokenStream#incrementToken()
*/
@Override
public boolean incrementToken() throws IOException {
// 清除所有的词元属性
clearAttributes();
Lexeme nextLexeme = _IKImplement.next();
if (nextLexeme != null) {
// 将Lexeme转成Attributes
// 设置词元文本
termAtt.append(nextLexeme.getLexemeText());
// 设置词元长度
termAtt.setLength(nextLexeme.getLength());
// 设置词元位移
offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
// 记录分词的最后位置
endPosition = nextLexeme.getEndPosition();
// 记录词元分类
typeAtt.setType(nextLexeme.getLexemeTypeString());
// 返会true告知还有下个词元
return true;
}
// 返会false告知词元输出完毕
return false;
}
/*
* (non-Javadoc)
* @see org.apache.lucene.analysis.Tokenizer#reset(java.io.Reader)
*/
@Override
public void reset() throws IOException {
super.reset();
_IKImplement.reset(input);
}
@Override
public final void end() {
// set final offset
int finalOffset = correctOffset(this.endPosition);
offsetAtt.setOffset(finalOffset, finalOffset);
}
}
- 将ikanalyzer里的IKAnalyzerClass文件和IKTokenizerClass文件替换成自己写的Class文件(记得去你自己的镜像仓库拿原jar包)
替换到
将替换好的jar包上传到linux里面
6. 进入你放jar包的目录在执行拷贝命令:docker cp ./ikanalyzer-2012_u6.jar my_solr:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib (将linux拷贝过来的jar包拷进server/solr-webapp/webapp/WEB-INF/lib目录下)
7. 重启容器: docker stop my_solr(关闭) docker start my_solr(开启)
8. 拷贝my_solr容器里的managed-schema文件到linux /opt/ika/目录下去修改,配置文分词 命令:docker cp my_solr:/opt/solr/server/solr/mycore/conf/managed-schema ./
9. 修改文件 managed-schema
定义分词器数据类型
<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>
定义动态字段
<dynamicField name="*_ik" type="text_ik" indexed="true" stored="true"/>
//index代表倒排索引
//stored代表存储数据
- 修改完成把文件拷贝回原目录:docker cp ./managed-schema my_solr:/opt/solr/server/solr/mycore/conf/
- 重启容器
- 进行引擎搜索测试
图1
图2
8.数据库数据迁移solr
- cp /opt/solr/dist/solr-dataimporthandler-5.5.5.jar /opt/solr/server/solr-webapp/webapp/WEB-INF/lib
- cp /opt/solr/dist/solr-dataimporthandler-extras-5.5.5.jar /opt/solr/server/solr-webapp/webapp/WEB-INF/lib
- exit退出 下载jar包当前目录(https://mvnrepository.com/artifact/mysql/mysql-connector-java/5.1.24)
docker cp ./mysql-connector-java-5.1.24.jar my_solr:/opt/solr/server/solr-webapp/webapp/WEB-INF/lib
- 新建data-c.xml创建连接数据的四要素
<?xml version="1.0" encoding="UTF-8"?>
<dataConfig>
<dataSource name="source1" type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://192.168.1.3:3306/test" user="root" password="123456" batchSize="-1" />
<document>
<entity name="book" pk="newid" dataSource="source1"
query="select * from mynew" >
<field column="newid" name=""/>
<field column="newtitle" name="title_ik"/>
</entity>
</document>
</dataConfig>
4.创建成功后拷贝到my_solr:/opt/solr/server/solr/mycore/conf目录下 命令:docker cp ./data-c.xml my_solr:/opt/solr/server/solr/mycore/conf
5. 修改solrconfig.xml 指定data-c.xml文件
6. docker cp my_solr:/opt/solr/server/solr/mycore/conf/solrconfig.xml . 把要修改的文件拷贝到当前目录
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
<lst name="defaults">
<str name="config">data-c.xml</str>
</lst>
</requestHandler>
修改成功后拷贝回原地方: docker cp ./solrconfig.xml my_solr:/opt/solr/server/solr/mycore/conf 重启:docker restart my_solr
9.代码实现Solr查询与分页
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.11.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
</dependencies>
- 创建资源文件连接linux 里面的solr
spring:
data:
solr:
host: http://192.168.163.131:8983/solr
server:
port: 8888
- 创建main方法
package cn.pkl;
import org.apache.solr.client.solrj.SolrClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.solr.core.SolrTemplate;
@SpringBootApplication
public class SokrMain {
@Bean
public SolrTemplate solrTemplate(SolrClient client) {
return new SolrTemplate(client);
}
public static void main(String[] args) {
SpringApplication.run(SokrMain.class, args);
}
}
- 创建dao层接口
package cn.pkl.dao;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.solr.repository.Query;
import org.springframework.data.solr.repository.SolrCrudRepository;
import cn.pkl.entity.Person;
public interface MoneyDap extends SolrCrudRepository<Person, String> {
//方法名必须固定(findBy+字段名)
//public List<Person> findByDesc(String keyword);
//分页查询加排序
//public Page<Person> findByDesc(String keyword,Pageable page);
//单独排序
public List<Person> findByDesc(String keyword,Sort sor);
@Query("name_ik:?0")
public List<Person> queryByDesc(String keyword);
}
- 创建实体类
package cn.pkl.entity;
import org.apache.solr.client.solrj.beans.Field;
import org.springframework.data.solr.core.mapping.SolrDocument;
@SolrDocument(solrCoreName="mycore")
public class Person {
private String id;
@Field("country_ik")
private String country;
@Field("name_ik")
private String name;
@Field("desc_ik")
private String desc;
@Field("age_i")
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
- 创建控制层
package cn.pkl.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.pkl.dao.MoneyDap;
import cn.pkl.entity.Person;
@RestController
public class SolrController {
/*@Autowired
private SolrTemplate st;
@GetMapping("/queryMoney")
public List<Money> queryMoney(String keyword) {
SimpleQuery sq=new SimpleQuery("uname_ik:"+keyword);
Page<Money> query=st.query(sq, Money.class);
return query.getContent();
}*/
@Autowired
private MoneyDap my;
//自动查询
/*@GetMapping("/queryMoney")
public List<Person> findByUname(String keyword) {
List<Person> findByDesc = my.findByDesc(keyword);
return findByDesc;
}*/
//根据名字查询
/*@GetMapping("/queryMoney")
public List<Person> findByUname(String keyword) {
List<Person> findByDesc = my.queryByDesc(keyword);
return findByDesc;
}*/
//分页查询 加排序
/*@GetMapping("/queryMoney")
public Page<Person> findByUname(String keyword) {
PageRequest qr=new PageRequest(0, 2,new Sort(Direction.ASC,"age_i"));
Page<Person> findByDesc = my.findByDesc(keyword,qr);
return findByDesc;
}*/
//单独排序
@GetMapping("/queryMoney")
public List<Person> findByUname(String keyword) {
List<Person> findByDesc = my.findByDesc(keyword,new Sort(Direction.ASC,"age_i"));
return findByDesc;
}
}
7.创建HTML页面(记得写在static文件下面)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script type="text/javascript">
function query(){
$.ajax({
url:'queryMoney',
dataType:'json',
type:'get',
data:'keyword='+$("#b").val(),
success:function(r){
$("#a").html(JSON.stringify(r));
}
});
}
</script>
</head>
<body>
新闻:<input id="b" type="text" name="keyword"><button onclick="query()">搜索</button>
<div id="a">
</div>
</body>
</html>