全文检索--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();
  }

}