全文检索--Lucene & ElasticSearch
全文检索--Lucene
2.1 全文检索和以前高级查询的比较
1.高级查询
缺点:1.like让数据库索引失效
2.每次查询都是查询数据库 ,如果访问的人比较多,压力也是比较大
2.全文检索框架:Apache - Lucene
优点:
1.可以相关度排序
2.可以对摘要进行截取
3.关键字高亮显示
2.2 Lucene测试
1.引入jar包
lucene-analyzers-common-5.5.0.jar
lucene-core-5.5.0.jar
lucene-queryparser-5.5.0.jar
2.创建索引,搜索索引
package cn.itsource.lucene;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import java.nio.file.Paths;
public class TestLucene {
String doc1 = "hello world";
String doc2 = "hello java world";
String doc3 = "hello lucene world";
//用来存放索引文件
private String path ="D:\\lucene";
//创建索引
//步骤:
//1、把文本内容转换为Document对象
//2、准备IndexWriter(索引写入器)
//3、通过IndexWriter,把Document添加到缓冲区并提交
@Test
public void testCreateIndex() throws Exception{
//得到Director目录
FSDirectory directory = FSDirectory.open(Paths.get(path));
//(Directory d, IndexWriterConfig conf)
//得到分词器
Analyzer analyzer = new SimpleAnalyzer();
//得到写入器配置对象
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
//得到写入器对象
IndexWriter indexWriter = new IndexWriter(directory,conf);
//查询所有的Deparmtent 10 数据库的数据 -- 转换成10document
//把数据存入文档
Document doc11 = new Document();
// public TextField(String name, String value, Store store) {
//参数:标题名,内容,是否存储
doc11.add(new TextField("title","doc1", Field.Store.YES));
doc11.add(new TextField("content",doc1,Field.Store.YES));
Document doc12 = new Document();
// public TextField(String name, String value, Store store) {
doc12.add(new TextField("title","doc2", Field.Store.YES));
doc12.add(new TextField("content",doc2,Field.Store.YES));
Document doc13 = new Document();
// public TextField(String name, String value, Store store) {
doc13.add(new TextField("title","doc3", Field.Store.YES));
doc13.add(new TextField("content",doc3,Field.Store.YES));
indexWriter.addDocument(doc11);
indexWriter.addDocument(doc12);
indexWriter.addDocument(doc13);
//提交写的索引
indexWriter.commit();
//关闭写的索引
indexWriter.close();
}
//搜索索引
@Test
public void testIndexSearch() throws Exception{
//1.查询索引
//得到search搜索对象
FSDirectory directory = FSDirectory.open(Paths.get(path));
IndexReader r = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(r);
//搜索 (Query query, int n)
//String f, Analyzer a -> 查询的字段,分词对象
String keyWord = "lucene";
String f = "content";
//得到一个分词器
Analyzer analyzer = new SimpleAnalyzer();
//得到query对象
Query query = new QueryParser(f,analyzer).parse("content:"+keyWord);
TopDocs topDocs = searcher.search(query, 100);
//2.把索引转换为文档
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//得到文档编号
int docId = scoreDoc.doc;
//通过文档编号得到文档
Document doc = searcher.doc(docId);
System.out.println(doc.get("title")+"----"+doc.get("content"));
}
}
}
注:需要更改idea的版本:设置 -> Build,Execution,Deployment -> Compiler -> Java Compiler -> Target bytecode version -> 1.8
1.Lucene介绍
Lucene:它是全文检索的工具包
2.Lucene使用场景
需要检索的结构化数据(非结构化数据:如:图片,结构化数据就是非结构化数据外的数据)----> 提高查询效率
3. 全文检索的核心
创建索引和搜索索引
4. 对Lucene的API认识
1. 索引库-Directory
MMapDirectory : 针对64系统,它在维护索引库时,会结合“内存”与硬盘同步来处理索引。
SimpleFSDirectory : 传统的文件系统索引库。 window系统就是使用
RAMDirectory : 内存索引库
2.文档-Document,字段-Field
3.字段-Field
数据库 字段 有类型 有长度
索引库 字段 有类型 其他属性(字段类型 是否分词 是否索引 是否存储)
4.属性设置方式
1.方式一
document3.add(new TextField("title","doc3Title",Field.Store.YES));
2.方式二:对字段单独处理:
Document document = new Document();
FieldType fieldType = new FieldType();
fieldType.setStored(true);//设置是否存储
fieldType.setTokenized(true);//设置是否分词
fieldType.setIndexOptions(IndexOptions.DOCS);//设置是否创建索引
document.add(new Field("name",department.getName(),fieldType));
5.什么时候使用索引、分词、存储?
1.什么时候使用索引?
判断是否需要搜索的字段
2.是否分词?
考虑什么内容需要分词? 比如:模糊查询的内容,like "% keywords %"
3.是否存储?
判断是否把内容存储到索引库(内容比较长的字段不适合放到索引库)
5.分词器
1.分词器是什么
按照一定的
2.使用分词器
1.引入依赖
IKAnalyzer2012_V5.jar
2.测试
public class AnalyzerTest {
//创建索引的数据 现在写死,以后根据实际应用场景
private String en = "oh my lady gaga"; // oh my god
private String cn = "迅雷不及掩耳盗铃儿响叮当仁不让";
private String str = "源代码教育FullText Search Lucene框架的学习"; /**
* 把特定字符串按特定的分词器来分词
* @param analyzer:分词对象
* @param str:需要分词的字符串
* @throws Exception
*/
public void testAnalyzer(Analyzer analyzer,String str) throws Exception {
//把内容转换成流
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(str));
// 在读取词元流后,需要先重置/重加载一次
tokenStream.reset();
while(tokenStream.incrementToken()){
System.out.println(tokenStream);
}
}
//IK分词:从词典中查找
// 简单使用:拷贝两个配置文件,拷贝一个jar包
//
// 扩展词,停止词
//拷贝两个文件到项目resource中:IKAnalyzer.cfg.xml,stopword.dic
//在ext.dic文件里面添加自定义的分词内容,实现自定义分词
// 注意:打开方式,不要使用其他的,直接使用eclipse的text Editor,修改以后要刷新一下让项目重新编译 /**
* 配置停止词
* 配置扩展词
* @throws Exception
*/
@Test
public void test()throws Exception{
//userSmart为true走最大匹配原则,有大不要小的
//userSmart为false,默认值,细粒度匹配
testAnalyzer(new IKAnalyzer(false),str);
}
}
}
6.Lucene中的查询
1.上次的搜索方式,传入keyword
// 先做一个准备工作,提供两个search方法
//一个传入搜索关键字进行搜索
public void search(String keyWord) throws Exception{
//封装查询提交为查询对象
String f = "content";
SimpleAnalyzer analyzer = new SimpleAnalyzer();
QueryParser queryParser = new QueryParser(f, analyzer);
Query query = queryParser.parse(keyWord);
System.out.println("query生成对象"+query.getClass());
FSDirectory directory = FSDirectory.open(Paths.get(path));
//得到IndexReader
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 1000);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document doc = indexSearcher.doc(docId);
System.out.println("title:"+doc.get("title")+"===========content:"+doc.get("content"));
}
}
2.可以出入Query对象
public void search(Query query) throws Exception{
//封装查询提交为查询对象
//String f = "content";
/* SimpleAnalyzer analyzer = new SimpleAnalyzer();
QueryParser queryParser = new QueryParser(f, analyzer);
Query query = queryParser.parse(keyWord);*/
FSDirectory directory = FSDirectory.open(Paths.get(path));
//得到IndexReader
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 1000);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
int docId = scoreDoc.doc;
Document doc = indexSearcher.doc(docId);
System.out.println("title:"+doc.get("title")+"===========content:"+doc.get("content"));
}
}
//创建Query对象
new TermQuery(new Term("context","java"));
1.查询方式分类
1.单词查询:传入Query对象
2.段落查询:多个单词当做一个整体
//1.使用转义符,把多个单词当做一个整体
String s = "\"hello word"\";
//2.使用builder对象,把多个单词当做一个整体
//静态内部类
PhraseQuery.Builder builder = new PhraseQuery.Builder();
builder.add(new Term("content","hello"));
builder.add(new Term("content","world"));
3.通配符查询:
//通配符查询:
// *:代表零个或者多个 --> 注意:不能把*放在最前面,必须在*前面放内容,比如:*e不对,e*才对
// ?:代表一个
4.容错查询:
//容错2个
String s = "lucexx~2";
//也可以用Query对象,名字叫content的字段,容错2个
FuzzyQuery query = new FuzzyQuery(new Term("content","lucexx"),2);
5.临近查询:
在段落查询的基础上用“~”后面跟一个1到正无穷的正整数。代表段落中,单词与单词之间最大的间隔数
//最大间隔2个
String s = "\"hello world\"~2";
//builder对象
PhraseQuery.Builder builder = new PhraseQuery.Builder();
builder.add(new Term("content","hello"));
builder.add(new Term("content","world"));
//设置最大间隔数
builder.setSlop(2);
PhraseQuery query = builder.build();
6.组合查询:
//写法一:
//字段前面带个加号:必须包含的内容
//字段前带个减号:必须不包含
String s1 = "content:java content:lucene";//什么都不加
String s2 = "+content:java +content:lucene";//前面带加号:必须包含
String s3 = "-content:java -content:lucene";//前面带减号:必须不包含
//写法二:使用BooleanQuery.Builer对象
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new TermQuery(new Term("content","hello")),Occur.MUST);//必须包含
builder.add(new TermQuery(new Term("content","lucene")),Occur.MUST_NOT);//必须不包含
2.项目里使用全文检索
1.lucene在项目中的使用场景
1.查询的时候:搜索量比较大的地方/数据比较多的地方,比如:日志管理
2 什么时候把数据同步到索引库
1.即时同步,在操作数据库进行crud的时候,同步到索引库
2.自动同步,借助定时器
3.手动同步,页面添加按钮进行同步
3. 项目引入Luence索引库
1.导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.wltea/IKAnalyzer -->
<dependency>
<groupId>org.wltea</groupId>
<artifactId>IKAnalyzer</artifactId>
<version>2012_V5</version>
</dependency>
<dependency>
<groupId>cn.itsource</groupId>
<artifactId>itsource_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
手动打包IKAnalyzer到maven仓库:
cmd -> mvn install:install-file -Dfile=IKAnalyzer2012_V5.jar -DgroupId=org.wltea -DartifactId=IKAnalyzer -Dversion=2012_V5 -Dpackaging=jar
命令结构:mvn install:install-file -Dfile=jar包名字加后缀名 -Dgroupld=项目名 -DartifactId=组名 -Dversion=版本号 -Dpackaging=打包方式
<- service层要依赖Luence ->
<dependency>
<groupId>cn.itsource</groupId>
<artifactId>itsource_fullTextIndex</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.集成工具类
package cn.itsource.lucene.util;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class LuceneUtil {
private static final String INDEX_DIRCTORY = "F:/lucene/index";
private static Directory directory;//存放索引的目录
private static IndexWriter indexWriter;//索引写对象,线程安全
private static IndexReader indexReader;//索引读对象,线程安全
private static IndexSearcher indexSearcher;//索引查询对象,线程安全
private static Analyzer analyzer;//分词器对象
static{
try {
//如果父目录不存在,先创建父目录
File file = new File(INDEX_DIRCTORY);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
directory = FSDirectory.open(Paths.get(INDEX_DIRCTORY));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取IndexWriter对象
* @return
* @throws IOException
*/
public static IndexWriter getIndexWriter(){
try {
Analyzer analyzer = getAnalyzer();
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
return new IndexWriter(directory,conf);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取IndexReader
* @return
* @throws Exception
*/
public static IndexReader getIndexReader(){
try {
if(indexReader==null){
indexReader = DirectoryReader.open(directory);
}else {
//如果不为空,就使用DirectoryReader打开一个索引变更过的IndexReader类
DirectoryReader newIndexReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader);
if(newIndexReader!=null){
//把旧的索引读对象关掉
indexReader.close();
indexReader = newIndexReader;
}
}
return indexReader;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取IndexSearcher对象
* @return
* @throws IOException
*/
public static IndexSearcher getIndexSearcher(){
if(indexSearcher==null){
indexSearcher = new IndexSearcher(getIndexReader());
}
return indexSearcher;
}
/**
* 获取分词器对象
* @return
*/
public static Analyzer getAnalyzer() {
if(analyzer!=null){
return analyzer;
}
return new IKAnalyzer();
}
/**
* 创建QueryParser对象
* @param field
* @return
*/
public static QueryParser createQueryParser(String field){
return new QueryParser(field,getAnalyzer());
}
/**
* 创建Query对象
* @param field
* @param queryStr
* @return
*/
public static BooleanQuery createQuery(String field[],String queryStr){
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (String f : field) {
builder.add(new TermQuery(new Term(f,queryStr)), BooleanClause.Occur.SHOULD);
}
return builder.build();
}
/**
* 分页查询索引
* @param field
* @param queryStr
* @param pageNum
* @param pageSize
* @return
* @throws IOException
*/
public static List<Document> getHitDocuments(String[] field,String queryStr,int pageNum,int pageSize){
List<Document> list = new ArrayList<>();
try {
IndexSearcher indexSearcher = getIndexSearcher();
Query query = createQuery(field,queryStr);
System.out.println(query);
// 查询数据, 结束页面自前的数据都会查询到,但是只取本页的数据
TopDocs topDocs = indexSearcher.search(query, pageNum * pageSize);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//总条目数
int totalHits = topDocs.totalHits;
int start = (pageNum-1)*pageSize;
int end = (pageNum*pageSize)>totalHits?totalHits:(pageNum*pageSize);
for(int i=start;i<end;i++){
ScoreDoc scoreDoc = scoreDocs[i];
Document document = indexSearcher.doc(scoreDoc.doc);
list.add(document);
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 总共命中的条目数
* @param field
* @param queryStr
* @return
* @throws IOException
*/
public static long totalHits(String[] field,String queryStr){
try {
IndexSearcher indexSearcher = getIndexSearcher();
Query query = createQuery(field,queryStr);
TopDocs topDocs = indexSearcher.search(query, 10);
return topDocs.totalHits;
} catch (IOException e) {
e.printStackTrace();
return 0;
}
}
/**
* 删除索引
* @param field
* @param queryStr
* @throws IOException
*/
public static void deleteIndex(String[] field,String queryStr){
try {
IndexWriter indexWriter = getIndexWriter();
Query query = createQuery(field,queryStr);
indexWriter.deleteDocuments(query);
indexWriter.commit();
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除所有索引
* @throws IOException
*/
public static void deleteAllIndex()throws IOException{
IndexWriter indexWriter = getIndexWriter();
indexWriter.deleteAll();
indexWriter.commit();
indexWriter.close();
}
/**
* 更新索引文档
* @param term
* @param document
*/
public static void updateIndex(Term term,Document document) {
try {
IndexWriter indexWriter = getIndexWriter();
indexWriter.updateDocument(term, document);
indexWriter.commit();
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 更新索引文档
* @param field
* @param value
* @param document
*/
public static void updateIndex(String field,String value,Document document) {
updateIndex( new Term(field, value), document);
}
/**
* 添加索引文档
* @param document
*/
public static void addIndex(Document document) {
updateIndex(null, document);
}
/**
* 关闭资源
*/
public static void closeAll(){
try {
if (indexWriter!=null)
indexWriter.close();
if(indexReader!=null)
indexReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.IDepartmentIndexHelper接口
package cn.itsource.lucene.index;
import cn.itsource.basic.PageList;
import cn.itsource.domain.Department;
import cn.itsource.query.DepartmentQuery;
import java.util.Map;
/**
* 对索引库里面Department进行进行crud接口
*/
public interface IDepartmentIndexHelper {
void save(Department department);
void remove(Long id);
void update(Department department);
PageList<Department> list(DepartmentQuery query);
}
4.刚才接口的实现类:DepartmentIndexHelper类
package cn.itsource.lucene.index.impl;
import cn.itsource.basic.PageList;
import cn.itsource.domain.Department;
import cn.itsource.domain.Employee;
import cn.itsource.domain.Tenant;
import cn.itsource.lucene.index.IDepartmentIndexHelper;
import cn.itsource.lucene.util.LuceneUtil;
import cn.itsource.query.DepartmentQuery;
import org.apache.lucene.document.*;
import org.apache.lucene.index.MergePolicy;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Component
public class DepartmentIndexHelper implements IDepartmentIndexHelper {
@Override
public void save(Department department) {
try {
Document document = department2doc(department);
LuceneUtil.addIndex(document);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void remove(Long id) {
try {
// id字段中存在id就删除
LuceneUtil.deleteIndex(new String[]{"id"},id+"");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void update(Department department) {
try{
Term term = new Term("id", department.getId() + "");
LuceneUtil.updateIndex(term,department2doc(department));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public PageList<Department> list(DepartmentQuery query) {
String keywords = query.getKeywords();
Query indexQuery = null;
if (StringUtils.isEmpty(keywords)){
//name中包含*
indexQuery = new WildcardQuery(new Term("name","*"));
}else{
indexQuery = LuceneUtil.createQuery(
new String[]{"name","sn","companyName","pname","username"}, keywords);
}
int pageNum = query.getPage().intValue();
int pageSize = query.getPageSize().intValue();
try {
IndexSearcher indexSearcher = LuceneUtil.getIndexSearcher();
System.out.println(query);
//pageNum 1 pageSize 10 10(pageNum * pageSize) 1-10 (page-1)*pageSize,pageSize
//pageNum 2 pageSize 10 20 10-20
//pageNum 3 pageSize 10 30 20-30
// 查询数据, 结束页面自前的数据都会查询到,但是只取本页的数据
TopDocs topDocs = indexSearcher.search(indexQuery, pageNum * pageSize);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//总条目数
int totalHits = topDocs.totalHits;
if (totalHits<1){
return new PageList<>();
}else{
int start = (pageNum-1)*pageSize;
int end = (pageNum*pageSize)>totalHits?totalHits:(pageNum*pageSize);
List<Department> departments = new ArrayList<>();
for(int i=start;i<end;i++){
ScoreDoc scoreDoc = scoreDocs[i];
Document document = indexSearcher.doc(scoreDoc.doc);
departments.add(document2Department(document));
}
return new PageList<Department>(Long.parseLong(totalHits+""),departments);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Department document2Department(Document document) {
Department department = new Department();
department.setId(Long.parseLong(document.get("id")));
department.setSn(document.get("sn"));
department.setName(document.get("name"));
department.setDirPath(document.get("dirPath"));
department.setState(Integer.parseInt(document.get("state")));
if (!StringUtils.isEmpty(document.get("mid"))){
Employee manager = new Employee(); // 部门经理 员工对象
manager.setId(Long.parseLong(document.get("mid")));
manager.setUsername(document.get("username"));
department.setManager(manager);
}
if (!StringUtils.isEmpty(document.get("pid"))){
Department parent = new Department(); // 部门经理 员工对象
parent.setId(Long.parseLong(document.get("pid")));
parent.setName(document.get("pname"));
department.setParent(parent);
}
if (!StringUtils.isEmpty(document.get("tid"))){
Tenant tenant = new Tenant(); // 部门经理 员工对象
tenant.setId(Long.parseLong(document.get("tid")));
tenant.setCompanyName(document.get("companyName"));
department.setTenant(tenant);
}
return department;
}
private Document department2doc(Department department) {
Document document = new Document();
document.add(new LongField("id",department.getId(), Field.Store.YES));
document.add(new StringField("sn",department.getSn(), Field.Store.YES));
document.add(new TextField("name",department.getName(), Field.Store.YES));
if (department.getManager()!=null){
document.add(new LongField("mid",department.getManager().getId(), Field.Store.YES));
document.add(new TextField("username",department.getManager().getUsername(), Field.Store.YES));
}
if (department.getParent()!=null){
document.add(new LongField("pid",department.getParent().getId(), Field.Store.YES));
document.add(new TextField("pname",department.getParent().getName(), Field.Store.YES));
}
if (department.getTenant()!=null){
document.add(new LongField("tid",department.getTenant().getId(), Field.Store.YES));
document.add(new TextField("companyName",department.getTenant().getCompanyName(), Field.Store.YES));
}
document.add(new StringField("dirPath",department.getDirPath(), Field.Store.YES));
document.add(new IntField("state",department.getState(), Field.Store.YES));
return document;
}
}
5.项目中实现类:DepartmentServiceImpl,中做操作数据库CRUD时,修改索引库
package cn.itsource.service.impl;
import cn.itsource.basic.PageList;
import cn.itsource.basic.query.BaseQuery;
import cn.itsource.basic.service.impl.BaseServiceImpl;
import cn.itsource.domain.Department;
import cn.itsource.lucene.index.IDepartmentIndexHelper;
import cn.itsource.mapper.DepartmentMapper;
import cn.itsource.query.DepartmentQuery;
import cn.itsource.service.IDepartmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.List;
@Service
/**
* 做增删改的时候同步修改索引库
*/
public class DepartmentServiceImpl extends BaseServiceImpl<Department> implements IDepartmentService {
@Autowired
private DepartmentMapper departmentMapper;
@Autowired
private IDepartmentIndexHelper indexHelper;
@Override
public void add(Department department) {
departmentMapper.save(department);
indexHelper.save(department);
}
@Override
public void del(Serializable id) {
departmentMapper.remove(id);
indexHelper.remove(Long.parseLong(id.toString()));
}
@Override
public void update(Department department) {
departmentMapper.update(department);
indexHelper.update(department);
}
//不需要到数据库查询,直接到索引库查询
@Override
public PageList<Department> query(BaseQuery query) {
DepartmentQuery departmentQuery = (DepartmentQuery) query;
return indexHelper.list(departmentQuery);
}
}
6.测试
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext-service.xml","classpath:applicationContext-mybatis.xml"})
public class BaseTest {
}
测试方法:放入索引库
@Test
public void testAddExsitDepartmentIndexs()throws Exception{
List<Department> departments = departmentService.getAll();
for (Department department : departments) {
indexHelper.save(department);
}
}
4.高亮显示
1.引入依赖
lucene-highlighter-5.5.0.jar lucene-memory-5.5.0.jar
2.创建高亮器
3.使用高亮器对查询结果进行高亮处理
4.高亮放到项目中
前端代码:
<el-table-column prop="state" label="状态">
<template slot-scope="scope">
<span v-if="scope.row.state!=null && scope.row.state==-1" style="color: red">停用</span>
<span v-else>正常</span>
</template>
</el-table-column>
<el-table-column prop="manager.username" label="部门经理">
<template slot-scope="scope">
<div v-if="scope.row.manager!=null" v-html="scope.row.manager.username"></div>
</template>
</el-table-column>
2. 全文检索--ElasticSearch(即ES)
2.1 什么是ES
ElasticSearch :做全文检索的框架,是由Lucene封装的,支持分布式支持集群
索引库管理支持,还是Lucene的索引库
2.1.1 什么是Lucene
Lucene:api比较麻烦,操作全文检索的最底层技术,核心:创建索引,搜索索引
2.1.2 类似框架solr
solr是最流行的企业级搜索引擎,Solr4还增加了NoSQL的支持。
和Solr和ES比较?
Solr利用Zookeeper(注册中心)进行分布式管理,支持更多格式的数据(HTML/PDF/CSV)
ES是轻量级的,只支持json操作格式,在实时搜索方面比solr效率高
2.13 其实类似框架-Katta和HadoopContrib
Katta基于Lucene,支持分布式,可扩展,具有容错功能,准实时的搜索方案,配合Hadoop使用
HadoopContrib跟 Katta 配合使用
2.2 ES的优势
1.做全文检索的Lucene的API操作特别繁琐,用起来不方便,ES可以用来替换Lucene,并优化调用方式
2.ES支持高并发的分布式集群
3.ES通过发送restfull风格就可以完成的数据操作:put/delete/post/get
2.3 ES的特点
1.分布式的实时文件存储,每个字段都被索引并可被搜索
2.分布式的实时分析搜索引擎
3.可以扩展到上百台服务器,处理PB级结构化或非结构化数据
4.高度集成化的服务,应用可以通过简单的RESTfull API、各种语言的客户端甚至命令行与之交互
5.上手ElasticSearch非常容易
2.4 ES和其他全文检索框架的比较
1.ES和Solr的比较:
ES在实时搜索效率高于solr
solr是重量级,用起来做很多配置,但是功能很强大
它们都是注册分布式
2.5 ES安装服务器
1.ES依赖JDK,推荐使用JDK1.7+
2.官方下载安装包:https://www.elastic.co/downloads/elasticsearch,elasticsearch-5.2.2.zip
3.修改内存配置:
elasticsearch-5.2.2\config\jvm.options中更改: -Xms2g 和 -Xmx2g
4.运行ES:bin目录下:elasticsearch.bat
5.验证(ES提供了两个端口,web访问9200,java访问9300):web访问:http://localhost:9200/ ,java是访问:http://localhost:9300/
2.5.1 集群健康状态
Elasticsearch 中其实有专门的衡量索引健康状况的标志,分为三个等级:
1.green,绿色。这代表所有的主分片(primary shared)和副本分片(从分片replica shared)都已分配。集群是 100% 可用的。
2.yellow,黄色。所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。
3.red,红色。至少一个主分片以及它的全部副本都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常
2.5.2 操作ES的客户端工具
1.Curl命令方式
windows中使用,需要在火狐的“扩展”中搜索“POSTER”,并安装改扩展工具,使用Curl命令
2.Java API
3.Kibana
4.head:可以直接看到shard和replica + postman
2.5.3 Restful认识
2.5.3.1 ES里面的Restful风格
ES里面Restful风格只有:GET,POST,DELETE,PUT操作
2.5.3.2 使用Restful的好处:
透明性,暴露资源存在。
充分利用 HTTP 协议本身语义。
无状态,这点非常重要。在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了复杂度。
HTTP 本身提供了丰富的内容协商手段,无论是缓存,还是资源修改的乐观并发控制,都可以以业务无关的中间件来实现。
2.5.3.3 ES里面的Restful风格举例
GET 用来获取资源,
POST 用来新建资源(也可以用于修改资源),
PUT 用来修改资源,
DELETE 用来删除资源。
get -> GET /rest/api/dogs 获取所有小狗狗
post -> POST /rest/api/dogs 添加一个小狗狗
put -> PUT /rest/api/dogs/12 修改一个小狗狗
delete -> DELETE /rest/api/dogs/12 删除一个小狗狗
2.6 辅助管理工具Kibana5安装
① Kibana5.2.2 下载地址:https://www.elastic.co/downloads/kibana :kibana-5.2.2-windows-x86.zip
② 解压并编辑config/kibana.yml,设置elasticsearch.url的值为已启动的ES
③ 启动 : bin文件夹下:kibana.bat
④ 默认访问地址:http://localhost:5601
Kibana5客户端里面的选项:
1.Discover:可视化查询分析器
2.Visualize:统计分析图表
3.Dashboard:自定义主面板(添加图表)
4.Timelion:Timelion是一个kibana时间序列展示组件(暂时不用)
5.Dev Tools :Console(同CURL/POSTER,操作ES代码工具,代码提示,很方便)
6.Management:管理索引库(index)、已保存的搜索和可视化结果(save objects)、设置 kibana 服务器属性。
//各行内容的涵义
"_index": ".kibana", //索引库
"_type": "config", //表名
"_id": "5.2.2", //具体哪条数据
"_score": 1, //得分
"_source": { //字段
"buildNum": 14723
}
2.7 head工具的安装
head工具:查看集群信息
进入head页面 进行安装
1)安装
下载:
npm install --时间有点久
npm run start
2) 配置:跨域访问
修改 elasticsearch/config/elasticsearch.yml 文件:
增加:
1.http.cors.enabled: true
2.http.cors.allow-origin: "*"
3) 使用:访问:http://localhost:9100
3 ES的操作
传统数据库和ES的对应关系:
关系数据库(MYSQL) -> 数据库DB-> 表TABLE-> 行ROW-> 列Column
Elasticsearch -> 索引库Indices -> 类型Types -> 文档Documents -> 字段Fields
database数据库 | 索引库 | ES索引库 | |
---|---|---|---|
表(类型) | table | documents | type |
字段(某一个数据) | column | document(field) | id |
3.2 文档的增删改
语法:
①使用自己的ID创建:
PUT index/type/id :自己指定一个id值(id唯一标识)
②ES内置ID创建(id自动生成):
POST index/type :自动生成唯一标识
③ 获取指定ID的文档
GET itsource/employee/123?pretty
返回文档的部分字段:
GET默认返回整个文档,通过GET /itsource/employee/123?_source=fullName,email
只返回文档内容,不要元数据:
GET itsource/employee/123/_source
④ 修改文档
更新整个文档:
同PUT {index}/{type}/{id}
局部更新文档:
接受一个局部文档参数 doc,它会合并到现有文档中,对象合并在一起,存在的标量字段被覆盖,新字段被添加。
POST itsource/employee/123/_update
{
“doc”:{
"email" : ["nixianhua@itsource.cn",](mailto:\)
"salary": 1000
}
}
⑤删除
DELETE index/type/id
3.3 代码演示
#新增
PUT crm/user/1
{
"name":"小明",
"age":18
}
#查询
GET crm/user/1
#修改
POST crm/user/1
{
"name":"小红",
"age":28
}
#删除
DELETE crm/user/1
#不指定id的新增,会自动生成id
POST crm/user
{
"name":"娃哈哈",
"age":17
}
#查询所有
GET _search
#查询没有指定id的新增
GET crm/user/AW8i3Gt6KOmHoXbO0PFk
#漂亮格式->没有太多变化
GET crm/user/AW8i3Gt6KOmHoXbO0PFk?pretty
#带元数据的查询指定列
GET crm/user/AW8i3Gt6KOmHoXbO0PFk?_source=name,age
#不带元数据的查询指定列
GET crm/user/AW8i3Gt6KOmHoXbO0PFk/_source
#修改覆盖之前的json
#更改一个字段会全部覆盖
POST crm/user/AW8i3Gt6KOmHoXbO0PFk
{
"name":"爽歪歪"
}
#修改和新增都可以用post
POST crm/user/AW8i3Gt6KOmHoXbO0PFk
{
"name":"皮皮怪",
"age":39
}
#局部修改
POST crm/user/AW8i3Gt6KOmHoXbO0PFk/_update
{
"doc":{
"age":20
}
}
#脚本更新---了解
POST crm/user/AW8i3Gt6KOmHoXbO0PFk/_update
{
"script":"ctx._source.age += 2"
}
#删除文档
DELETE crm/user/AW8i3Gt6KOmHoXbO0PFk
GET _search
#批量插入(不推荐),不要分行,不然会报错
POST _bulk
{"delete":{"_index":"xinyang","_type":"job","_id":"123"}}
{"create":{"_index":"xinyang","_type":"student","_id":"123"}}
{"title":"我的学生"}
{"index":{"_index":"xinyang","_type":"student","_id":"456"}}
{"title":"我的另一个学生"}
#批量查询
GET xinyang/student/_mget
{
"ids":["123","456"]
}
GET _search
1.ElasticSearch
1.0 总结ES的语法
1.分页: size:每页条数,from:从哪一个索引开始
2.模糊查询: wildcard
3.DSL(domian specific language特定领域语言),DSL查询:query+match_all/match
4.DSL过滤:query+bool+must/must_not
1.1 ES文档的简单查询
1.通过文档ID获取:get index/type/id(index:索引库名,type:表名)
2.批量获取:
1.同一个索引库的,不同表:略
2.同一个索引库的,同一张表:GET index/type/_mget
3.空搜索:查询全部
没有指定任何的查询条件,只返回集群索引中的所有文档: GET _search
4.分页搜索
size : 每页条数,默认 10
from : 跳过开始的结果数,默认 0
GET _search?size=5&from=5
5.查询字符串搜索
条件查询:(年龄为25) GET itsource/employee/_search?q=age:25
条件查询(范围查询:age:20~30):age[20 TO 30]
## 查询指定索引库
get itsource/blog/_search
## 批量查询(查询同一个库,同一张表)
GET itsource/blog/_mget
{
"ids":["123","AW0fiJ4ZOy02M7ta5jyb"]
}
## 查询所有数据(空搜索)
GET _search
## 分页查询:size表示每页最大条数,from:从哪一条索引开始
GET itsource/blog/_search?size=2&from=0
PUT crm/employee/1
{
"id":1,
"name":"xxx",
"age":18,
"sex":false,
"hobbys":"美女"
}
PUT crm/employee/2
{
"id":2,
"name":"xxx",
"age":28,
"sex":false,
"hobbys":"游戏"
}
PUT crm/employee/3
{
"id":3,
"name":"xxx",
"age":38,
"sex":false,
"hobbys":"购物"
}
## 查询字符串搜索
GET crm/employee/_search?q=age:18
## 范围查询
GET crm/employee/_search?q=age[20 TO 40]
GET crm/employee/_search?q=age[20 To 40]&from=0&size=2
2.DSL查询与过滤
代码实现:
#分页查询
DELETE xinyang
#先创建几条数据
PUT xinyang/student/1
{
"name":"wangwamg",
"age":1
}
PUT xinyang/student/2
{
"name":"wamgxoap",
"age":2
}
PUT xinyang/student/3
{
"name":"wamgxoap12",
"age":3
}
PUT xinyang/student/4
{
"name":"wamgxoap33",
"age":4
}
PUT xinyang/student/5
{
"name":"wamgxoap44",
"age":5
}
PUT xinyang/student/6
{
"name":"wamgxoap13332",
"age":6
}
PUT xinyang/student/7
{
"name":"wamgxoap22222",
"age":7
}
#分页查询实现,不带from默认为0,得到结果是随机的,第1页,每页3条
GET xinyang/student/_search?size=3
#分页查询实现,第4页,每页2条
GET xinyang/student/_search?from=3&size=2
#根据条件查询,如果条件比较多就不适合
#q是指代字符串
GET xinyang/student/_search?q=age:4
#条件查询,先写size,再写条件,用&连接
#查询范围时,必须用TO,而且必须是大写
GET xinyang/student/_search?size=2&q=age[2 TO 5]
#DSL查询
#1.原语句
GET xinyang/student/_search?q=name:3王
#2.DSL查询语句:match:完全匹配
#测试的时候最好不用中文,避免因为分词引起结果不对
#相当于SQL语句:select * from student where name="wamgxoap13332"
GET xinyang/student/_search
{
"query" : {
"match" : {
"name" : "wamgxoap13332"
}
}
}
#wildcard:模糊查询
#相当于SQL:select * from student where name like "*1333*"
GET xinyang/student/_search
{
"query": {
"wildcard": {
"name": "*1333*"
}
}
}
#DSL查询+分页+排序,不支持缓存
#相当于SQL:select * from student order by age desc limit 0,3
GET xinyang/student/_search
{
"query":{
"match_all": {}
},
"from": 0,
"size": 3,
"_source": ["name","age"],
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
#DSL过滤
#相当于SQL:select * from student where age = 7 and name = xinyang
GET xinyang/student/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "wamgxoap22222"
}
}
],
"filter": {
"term": {
"age": "7"
}
}
}
}
, "from": 0,
"size": 3,
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
#must_not:必定不存在
GET xinyang/student/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"name": "wamg"
}
}
],
"filter": {
"term": {
"age": "7"
}
}
}
}
, "from": 0,
"size": 3,
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
2.1 DSL
DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现
2.1 DSL查询和DSL过滤的区别
DSL=DSL查询+DSL过滤
DSL查询:精确查询
DSL过滤:模糊查询
DSL过滤和DSL查询在性能上的区别 :
1.过滤结果可以缓存并应用到后续请求。
2.查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
3.过滤语句可有效地配合查询语句完成文档过滤(DSL查询和DSL过滤可以共用起来使用)
DSL查询语法:query+match_all/math
## DSL查询的写法
GET crm/employee/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 3,
"_source": ["name","age"],
"sort": [
{
"age": "desc"
}
]
}
DSL过滤语法:query+bool+must/must_not
gt:> gte:>= lt:< lte:<=
## DSL过滤的写法
GET crm/employee/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "xxx"
}}
],
"filter": {
"term": {
"age": "18"
}
},
"from": 20,
"size": 10,
"_source": ["fullName", "age", "email"],
"sort": [{"join_date": "desc"},{"age": "asc"}]
}
}
}
3.分词与映射
3.1 IK分词器
① Maven打包IK插件
② 解压target/releases/elasticsearch-analysis-ik-5.2.2.zip文件
并将其内容放置于ES根目录/plugins,把放进去的文件夹改名为:ik
③ 配置插件:
插件配置:plugin-descriptor.properties
④ 分词器(可默认)
词典配置:config/IKAnalyzer.cfg.xml
⑤ 重启ES
⑥ 测试分词器
#这里也可以用GET
POST _analyze
{
"analyzer":"ik_smart",
"text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}
3.2 文档映射Mapper
ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型
3.2.1 ES字段类型
①基本字段类型
字符串:text(分词),keyword(不分词) StringField(不分词文本),TextFiled(要分词文本)
text默认为全文文本,keyword默认为非全文文本
数字:long,integer,short,double,float
日期:date
逻辑:boolean
② 复杂数据类型
对象类型:object
数组类型:array
地理位置:geo_point,geo_shape
3.2.2 ES默认映射字段类型
GET {indexName}/_mapping/{typeName}
#查询映射类型
GET xinyang/_mapping/student
3.2.3 ES自定义映射
查看系统默认映射类型:
#查询映射类型:
GET shop/goods/_mapping
#修改映射类型
#1.删除库
Delete shop;
#2.创建库
PUT shop;
#3.查询默认映射
POST shop/goods/_mapping
{
"goods": {
"properties": {
"price": {
"type": "integer"
},
"name": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}
#加入数据
put shop/goods/1
{
"price":88,
"name": "iphone8"
}
自定义字段映射:
# 删除索引库
DELETE shop;
# 创建索引库
PUT shop;
## 自定义字段映射
POST shop/goods/_mapping
{
"goods": {
"properties": {
"price3": {
"type": "double"
},
"name3": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}
3.2.4 ES全局映射
全局映射可以通过动态模板和默认设置两种方式实现。
动态模板:提示把设定属性的规则设定好,加入数据的时候,会按照我们设置的规则映射
#//创建名为global_template的模板
PUT _template/global_template
{
#//匹配所有索引库
"template": "*",
#//匹配到的索引库只创建1个主分片
"settings": { "number_of_shards": 1 },
"mappings": {
"_default_": {
"_all": {
#//关闭所有类型的_all字段
"enabled": false
},
"dynamic_templates": [
{
"string_as_text": {
#//匹配类型string
"match_mapping_type": "string",
#//匹配字段名字以_text结尾
"match": "*_text",
"mapping": {
#//将类型为string的字段映射为text类型
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
},
{
"string_as_keyword": {
#//匹配类型string
"match_mapping_type": "string",
"mapping": {
#//将类型为string的字段映射为keyword类型
"type": "keyword"
}
}
}
]
}
}}
(1) 拷贝上面代码执行
(2) 删除库 delete shop
(3) 创建库 put shop
(4) 指定动态模板
(5) 加入数据,字段类型 ,根据指定类型做相应
(6) 在java创建索引 或者添加数据, 分词存起来,查询也会根据分词去查
加入数据测试:
POST shop/goods/5
{
"id":12,
"name_text":"iphone x",
"local":"cnsssss"
}
3.2.5 优先级
优先级:1.自定义 --> 2.全局 ---> 默认
最佳实践:先全局(根据公司规定),再自己自定义 ,再使用默认
4.ElasticSearch集群
4.1 为什么需要集群
1.处理高并发,大量数据,提高性能 2.解决单点故障
4.2 elasticsearch集群
4.2.1 ES的节点
ES节点类型Node有三种节点:
master Node:主节点,维护集群信息 索引库操作
data node:数据节点, 文档crud
client node:只负责处理用户请求
node.master:true:该节点有资格成为主节点,如果是false,则该节点不能进行选举,不能成为主节点
node.data:true:该节点可以存储数据,如果是false,则该节点不能存储数据
ES默认是:node.master:true ,node.data:true,即:该节点可以成为主节点,可以存储数据
4.2.2 集群主从分片分配机制: shard&replica
主分片和从分片不能在一个节点(单词:primary shard:主分片,replica shard:从分片)
4.2.3 虚拟集群
1.准备3个ES节点:拷贝3个ES文件,分别取名为node1,node2,node3
2.修改配置:jvm.options修改内存配置,并配置elasticsearch.yml
1.jvm.options修改内存配置:
-Xms1g
-Xmx1g
2.配置文件:elasticsearch.yml,有3个节点,都有不同配置
1.node-1配置:
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-1
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9201
#集群间通讯端口号
transport.tcp.port: 9301
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]
注:如果启动后没有从分片,可以在配置文件elasticsearch.yml增加以下配置(3个节点配置文件都要加)
#磁盘的配置
cluster.routing.allocation.disk.threshold_enabled: false
2.node-2配置:
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-2
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9202
#集群间通讯端口号
transport.tcp.port: 9302
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]
3.node-3配置:
# 统一的集群名
cluster.name: my-ealsticsearch
# 当前节点名
node.name: node-3
# 对外暴露端口使外网访问
network.host: 127.0.0.1
# 对外暴露端口
http.port: 9203
#集群间通讯端口号
transport.tcp.port: 9303
#集群的ip集合,可指定端口,默认为9300
discovery.zen.ping.unicast.hosts: [“127.0.0.1:9301”,”127.0.0.1:9302”,”127.0.0.1:9303”]
3.启动elasticSearch-head测试,在安装目录输入命令cdm,再输入npm run start,启动head,在浏览器输入:http://localhost:9100,查看3个节点,主从分片的分配信息(正常的搭配是:同一个节点的主从分片是分开的)
5.ES的javaAPI
5.1需要的jar包
引入maven项目中:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
5.2 使用ES文档索引增删改查,批量操作,条件查询
public class EsTest {
//得到一个连接
public TransportClient GetClient() throws Exception{
//创建Settings配置对象
//做嗅的配置
Settings settings = Settings.builder().put("client.transport.sniff", true).build();
//创建传输客户端
//指定集群中的一个节点
//通过TransportClient来访问时需要使用9300
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"),9300));
System.out.println(client);
//client.close();
return client;
}
//保存数据
@Test
public void testAdd() throws Exception{
TransportClient client = GetClient();
//索引库,表名,id
IndexRequestBuilder builder = client.prepareIndex("test2", "table2", "" + 1);
Map map = new HashMap();
map.put("id",1 );
map.put("name","xxx" );
IndexResponse response = builder.setSource(map).get();
client.close();
}
//查询数据
@Test
public void testQuery() throws Exception{
TransportClient client = GetClient();
//索引库,表名,id
GetResponse response = client.prepareGet("test2", "table2", "1").get();
System.out.println(response.getSource());
client.close();
}
//修改
@Test
public void testUpdate() throws Exception{
TransportClient client = GetClient();
IndexRequest indexRequest = new IndexRequest("test2", "table2", "1");
Map map = new HashMap();
map.put("id",1 );
map.put("name", "YYY");
UpdateRequest upsert = new UpdateRequest("test2", "table2", "1").doc(map).upsert(indexRequest);
client.update(upsert).get();//发送请求
client.close();
}
//删除
@Test
public void testDelete() throws Exception{
TransportClient client = GetClient();
DeleteResponse response = client.prepareDelete("test2", "table2", "1").get();
client.close();
}
//批量操作插入数据
@Test
public void testBulk() throws Exception{
TransportClient client = GetClient();
BulkRequestBuilder BulkRequestBuilder = client.prepareBulk();
for (int i =1;i<50;i++){
Map map = new HashMap();
map.put("id",i );
map.put("name","dd"+i );
map.put("age", i+10);
BulkRequestBuilder.add( );
}
BulkResponse response = BulkRequestBuilder.get();
if (response.hasFailures()){
System.out.println("出错了");
}
client.close();
}
//DSL语法
@Test
public void testDSLQuery() throws Exception{
TransportClient client = GetClient();
SearchRequestBuilder searchRequestBuilder = client.prepareSearch("itsource").setTypes("blog10");
//获得条件查询对象
BoolQueryBuilder query = QueryBuilders.boolQuery();
//添加必须存在的查询条件
List<QueryBuilder> list = query.must();
list.add(QueryBuilders.matchAllQuery());
//添加过滤条件
query.filter(QueryBuilders.rangeQuery("age").gte(20).lte(50));
searchRequestBuilder.setQuery(query);
//排序
searchRequestBuilder.addSort("age", SortOrder.DESC);
//分页
searchRequestBuilder.setFrom(10).setSize(10);
//截取一些字段
searchRequestBuilder.setFetchSource(new String[]{"id","name"},null );
//获取查询响应
SearchResponse response = searchRequestBuilder.get();
//从查询响应对象中得到命中的对象
SearchHits hits = response.getHits();//得到命中的数据
//从命中对象中获取命中数据的数组
SearchHit[] hits1 = hits.getHits();
for (SearchHit hit : hits1) {
System.out.println(hit.getSource());
}
client.close();
}
}
全文检索--Lucene & ElasticSearch的更多相关文章
- 全文检索 Lucene(4)
经过了前面几篇文章的学习,我们基本上可以适用Lucene来开发我们的站内搜索应用了.但是观察一下目前的主流的搜索引擎,我们会发现查询结果会有高亮的显示效果.所以,今天我们就来学习一下,给Lucene添 ...
- 全文检索 Lucene(3)
看完前两篇博客之后,想必大家对于Lucene的使用都有了一个比较清晰的认识了.如果对Lucene的知识点还是有点模糊的话,个人建议还是先看看这两篇文章. 全文检索 Lucene(1) 全文检索 Luc ...
- 全文检索Lucene (2)
接着全文检索Lucene (1) . 下面我们来深入的研究一下,如何使用Lucene! 从全文检索Lucene (1)中我们可以看出,Lucene就好比一个双向的工作流,一方面是对索引库的维护,另一方 ...
- Lucene 全文检索 Lucene的使用
Lucene 全文检索 Lucene的使用 一.简介: 参考百度百科: http://baike.baidu.com/link?url=eBcEVuUL3TbUivRvtgRnMr1s44nTE7 ...
- 全文检索选择-------- Elasticsearch与Solr
Elasticsearch简介* Elasticsearch是一个实时的分布式搜索和分析引擎.它可以帮助你用前所未有的速度去处理大规模数据. 它可以用于全文搜索,结构化搜索以及分析,当然你也可以将这三 ...
- 全文检索及ElasticSearch框架学习
1. 全文检索的通用步骤: 1.建库步骤: a 分词 b 倒排索引 : 关键词和记录Id的对应关系,1对多. 2.查询步骤: a 分词 b 查索引 c 取交集或并集 2. 产品使用全文 ...
- 全文检索(elasticsearch入门)
Elasticsearch篇: Elasticsearch是一个采用java语言开发的,基于Lucene构造的开源,分布式的搜索引擎. 设计用于云计算中,能够达到实时搜索,稳定可靠. Elastics ...
- 全文检索方案Elasticsearch【Python-Django 服务端开发】
更详细请看 https://www.elastic.co/cn/ 1. 全文检索和搜索引擎原理 商品搜索需求 当用户在搜索框输入商品关键字后,我们要为用户提供相关的商品搜索结果. 商品搜索实现 可以选 ...
- [全文检索]Lucene基础入门.
本打算直接来学习Solr, 现在先把Lucene的只是捋一遍. 本文内容: 1. 搜索引擎的发展史 2. Lucene入门 3. Lucene的API详解 4. 索引调优 5. Lucene搜索结果排 ...
随机推荐
- 软件测试必须掌握的抓包工具Wireshark,你会了么?
作为软件测试工程师,大家在工作中肯定经常会用到各种抓包工具来辅助测试,比如浏览器自带的抓包工具-F12,方便又快捷:比如时下特别流行的Fiddler工具,使用各种web和APP测试的各种场景的抓包分析 ...
- Apache用户认证、域名跳转、Apache访问日志
5月29日任务 课程内容: 11.18 Apache用户认证11.19/11.20 域名跳转11.21 Apache访问日志扩展 apache虚拟主机开启php的短标签 http://ask.apel ...
- appium环境的搭建
appium环境的搭建,之前看过很多关于appium环境搭建的文章,一个感觉就是“乱”. 所以才想自己来写一篇appium环境的搭建,算是总结和备忘吧. 如下图,其实appium的搭建分三部分完成,各 ...
- ios注册通知NSNotificationCenter(一)
作用:NSNotificationCenter是专门供程序中不同类间的消息通信而设置的. 注册通知:即要在什么地方接受消息 [[NSNotificationCenter defaultCenter] ...
- PyCharm 2019.3发布,增加了哪些新功能呢?
Python的IDE(Integrated Development Environment 集成开发环境)非常多,如:VS Code.Sublime.NotePad.Python自带编辑器IDLE.J ...
- 微信小程序——template详细使用
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用减少冗余代码. 1.1定义模板 1.1.1.创建模板文件夹 1.1.2.使用 name 属性,作为模板的名字.然后 ...
- Python如何爬取实时变化的WebSocket数据【华为云技术分享】
一.前言 作为一名爬虫工程师,在工作中常常会遇到爬取实时数据的需求,比如体育赛事实时数据.股市实时数据或币圈实时变化的数据.如下图: Web 领域中,用于实现数据'实时'更新的手段有轮询和 WebSo ...
- 【nodejs原理&源码赏析(2)】KOA中间件的基本运作原理
[摘要] KOA中间件的基本运作原理 示例代码托管在:http://www.github.com/dashnowords/blogs 在中间件系统的实现上,KOA中间件通过async/await来在不 ...
- LightOJ 1186 Icreable Chess(Nim博弈)
You are given an n x n chess board. Only pawn is used in the 'Incredible Chess' and they can move fo ...
- 关于直线,V形线,Z形线,M形线分割平面的总结
一:N条直线分割平面 假设,x条线能将平面分为f(x)份,这对于份f(n) 第n条线,和其他n-1条线都有交点时,增加量最大,为n; 则: f(n)=f(n-1)+n; 有f(0)=1:得到:n 条直 ...