ElasticSearch的Java API

1.与ES服务集群交互方式

可以通过两种方式来连接到elasticsearch(简称es)集群,第一种是通过在你的程序中创建一个嵌入es节点(Node),使之成为es集群的一部分,然后通过这个节点来与es集群通信。第二种方式是用TransportClient这个接口和es集群通信。

1.1 Node方式

创建嵌入节点的方式如下:

    import staticorg.elasticsearch.node.NodeBuilder.*;   
    //启动节点  
    Node node = nodeBuilder().node();  
    Client client = node.client();   
    //关闭节点 
    node.close(); 

当你启动一个节点,它会自动加入同网段的es集群,一个前提就是es的集群名(cluster.name)这个参数要设置一致。

默认的话启动一个节点,es集群会自动给它分配一些索引的分片,如果你想这个节点仅仅作为一个客户端而不去保存数据,你就可以设置把node.data设置成false或 node.client设置成true。下面是例子:

Node node =nodeBuilder().clusterName(clusterName).client(true).node();  

还有一种情况是你并不想把节点加入集群,只想用它进行单元测试时,就要启动一个“本地”的es,这里“本地”指的是在jvm的级别下运行,即两个不同的es节点运行在同一个JVM中时会组成一个集群。它需要把节点的local参数设置成true,下面是例子:

Node node =nodeBuilder().local(true).node();

1.2 TransportClient方式

通过TransportClient这个接口,我们可以不启动节点就可以和es集群进行通信,它需要指定es集群中其中一台或多台机的ip地址和端口,例子如下:

    Client client = new TransportClient()  
            .addTransportAddress(newInetSocketTransportAddress("host1", 9300))  
            .addTransportAddress(newInetSocketTransportAddress("host2", 9300));  
    client.close(); 

如果你需要更改集群名(默认是elasticsearch),需要如下设置:

    Settings settings =ImmutableSettings.settingsBuilder()  
                    .put("cluster.name","myClusterName").build();  
    Client client = newTransportClient(settings); 

你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。代码实例如下:

	Settings settings = ImmutableSettings.settingsBuilder()  
                   .put("client.transport.sniff", true).build();  
	TransportClientclient = new TransportClient(settings);

2. put Mapping定义索引字段属性

Mapping,就是对索引库中索引的字段名及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,不过es的mapping比数据库灵活很多,它可以动态添加字段。一般不需要要指定mapping都可以,因为es会自动根据数据格式定义它的类型,如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping。有两种添加mapping的方法,一种是定义在配置文件中,一种是运行时手动提交mapping,两种选一种就行了。
先介绍在配置文件中定义mapping,你可以把[mapping名].json文件放到config/mappings/[索引名]目录下,这个目录要自己创建,一个mapping和一个索引对应,你也可以定义一个默认的mapping,把自己定义的default-mapping.json放到config目录下就行。json格式如下:
    {  
       "mappings":{  
          "properties":{  
             "title":{  
                "type":"string",  
                "store":"yes"  
             },  
             "description":{  
                "type":"string",  
                "index":"not_analyzed"  
             },  
             "price":{  
                "type":"double"  
             },  
             "onSale":{  
                "type":"boolean"  
             },  
             "type":{  
                "type":"integer"  
             },  
             "createDate":{  
                "type":"date"  
             }  
          }  
       }  
    }  
接下来介绍通过请求添加mapping,下面为一个添加productIndex索引库的mapping的json格式请求。其中productIndex为索引类型,properties下面的为索引里面的字段,type为数据类型,store为是否存储,"index":"not_analyzed"为不对该字段进行分词。
    {  
       "productIndex":{  
          "properties":{  
             "title":{  
                "type":"string",  
                "store":"yes"  
             },  
             "description":{  
                "type":"string",  
                "index":"not_analyzed"  
             },  
             "price":{  
                "type":"double"  
             },  
             "onSale":{  
                "type":"boolean"  
             },  
             "type":{  
                "type":"integer"  
             },  
             "createDate":{  
                "type":"date"  
             }  
          }  
       }  
    } 
用java api调用的代码如下:
先创建空索引库
client.admin().indices().prepareCreate("productIndex").execute().actionGet();  
put mapping:
    XContentBuilder mapping = jsonBuilder()  
           .startObject() 
             .startObject("properties")         
               .startObject("title").field("type", "string").field("store", "yes").endObject()    
               .startObject("description").field("type", "string").field("index", "not_analyzed").endObject()  
               .startObject("price").field("type", "double").endObject()  
               .startObject("onSale").field("type", "boolean").endObject()  
               .startObject("type").field("type", "integer").endObject()  
               .startObject("createDate").field("type", "date").endObject()                 
             .endObject()  
            .endObject(); 
    PutMappingRequest mappingRequest = Requests.putMappingRequest("productIndex").type("productIndex").source(mapping);  
    client.admin().indices().putMapping(mappingRequest).actionGet();  

3. 索引数据

es索引数据非常方便,只需构建个json格式的数据提交到es就行,下面是个java api的例子
    XContentBuilder doc = jsonBuilder()  
          .startObject()       
              .field("title", "this is a title!")  
              .field("description", "descript what?")   
              .field("price", 100)  
              .field("onSale", true)  
              .field("type", 1)  
              .field("createDate", new Date()) 
         .endObject();  
    client.prepareIndex("productIndex","productType").setSource(doc).execute().actionGet();  
其中productIndex为索引库名,一个es集群中可以有多个索引库。productType为索引类型,是用来区分同索引库下不同类型的数据的,一个索引库下可以有多个索引类型。

4. 删除索引数据

删除api允许从特定索引通过id删除json文档。有两种方法,一是通过id删除,二是通过一个Query查询条件删除,符合这些条件的数据都会被删除。

一、通过id删除

下面的例子是删除索引名为twitter,类型为tweet,id为1的文档:
    DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")   
            .execute()   
            .actionGet();  

二、通过Query删除

下面的例子是删除索引名为productIndex,title中包含query的所有文档:
    QueryBuilder query = QueryBuilders.fieldQuery("title", "query");  
    client.prepareDeleteByQuery("productIndex").setQuery(query).execute().actionGet();
设置线程
当删除api在同一个节点上执行时(在一个分片中执行一个api会分配到同一个服务器上),删除api允许执行前设置线程模式(operationThreaded选项),operationThreaded这个选项是使这个操作在另外一个线程中执行,或在一个正在请求的线程(假设这个api仍是异步的)中执行。默认的话operationThreaded会设置成true,这意味着这个操作将在一个不同的线程中执行。下面是设置成false的方法:
    DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")   
            .setOperationThreaded(false)   
            .execute()   
            .actionGet();  
官方文档:
http://www.elasticsearch.org/guide/reference/api/delete.html
http://www.elasticsearch.org/guide/reference/java-api/delete.html

5. 搜索

elasticsearch的查询是通过执行json格式的查询条件,在java api中就是构造QueryBuilder对象,elasticsearch完全支持queryDSL风格的查询方式,QueryBuilder的构建类是QueryBuilders,filter的构建类是FilterBuilders。下面是构造QueryBuilder的例子:

    import static org.elasticsearch.index.query.FilterBuilders.*;   
    import static org.elasticsearch.index.query.QueryBuilders.*;   
           
    QueryBuilder qb1 = termQuery("name", "kimchy");   
           
    QueryBuilder qb2 = boolQuery()   
                            .must(termQuery("content", "test1"))   
                            .must(termQuery("content", "test4"))   
                            .mustNot(termQuery("content", "test2"))   
                            .should(termQuery("content", "test3"));   
           
    QueryBuilder qb3 = filteredQuery(   
                    termQuery("name.first", "shay"),    
                    rangeFilter("age")   
                        .from(23)   
                        .to(54)   
                        .includeLower(true)   
                        .includeUpper(false)   
                    );  
其中qb1构造了一个TermQuery,对name这个字段进行项搜索,项是最小的索引片段,这个查询对应lucene本身的TermQuery。 qb2构造了一个组合查询(BoolQuery),其对应lucene本身的BooleanQuery,可以通过must、should、mustNot方法对QueryBuilder进行组合,形成多条件查询。 qb3构造了一个过滤查询,就是在TermQuery的基础上添加一个过滤条件RangeFilter,这个范围过滤器将限制查询age字段大于等于23,小于等于54的结果。除了这三个,elasticsearch还支持很多种类的查询方式,迟点写个介绍。
构造好了Query就要传到elasticsearch里面进行查询,下面是例子:
    SearchResponse response = client.prepareSearch("test")   
            .setQuery(query)   
            .setFrom(0).setSize(60).setExplain(true)   
            .execute()   
            .actionGet();  
这句的意思是,查询test索引,查询条件为query,从第0条记录开始,最多返回60条记录。返回结果为SearchResponse,下面解析SearchResponse:
    SearchHits hits = searchResponse.hits();  
    for (int i = 0; i < 60; i++) {  
        System.out.println(hits.getAt(i).getSource().get("field"));  
    }  
获得SearchResponse中的SearchHits,然后hits.getAt(i).getSource().get("field")获得field字段的值。

6. 批量添加索引

elasticsearch支持批量添加或删除索引文档,java api里面就是通过构造BulkRequestBuilder,然后把批量的index/delete请求添加到BulkRequestBuilder里面,执行BulkRequestBuilder。下面是个例子:
    import static org.elasticsearch.common.xcontent.XContentFactory.*;   
               
    BulkRequestBuilder bulkRequest = client.prepareBulk();   
               
    bulkRequest.add(client.prepareIndex("twitter", "tweet", "1")   
                    .setSource(jsonBuilder()   
                                .startObject()   
                                    .field("user", "kimchy")   
                                    .field("postDate", new Date())   
                                    .field("message", "trying out Elastic Search")   
                                .endObject()   
                              )   
                    );   
               
    bulkRequest.add(client.prepareIndex("twitter", "tweet", "2")   
                    .setSource(jsonBuilder()   
                                .startObject()   
                                    .field("user", "kimchy")   
                                    .field("postDate", new Date())   
                                    .field("message", "another post")   
                                .endObject()   
                              )   
                    );   
                       
    BulkResponse bulkResponse = bulkRequest.execute().actionGet();   
    if (bulkResponse.hasFailures()) {   
         //处理错误   
    }  

7. 与MongoDB同步数据

elasticsearch提供river这个模块来读取数据源中的数据到es中,es官方有提供couchDB的同步插件,因为项目用到的是mongodb,所以在找mongodb方面的同步插件,在git上找到了elasticsearch-river-mongodb。
这个插件最初是由aparo写的,最开始的功能就是读取mongodb里面的表,记录最后一条数据的id,根据时间间隔不断访问mongodb,看看有没有大于之前记录的id的数据,有的话就索引数据,这种做法的缺点就是只能同步最新的数据,修改或删除的就不能同步。后来又由richardwilly98等人修改成通过读取mongodb的oplog来同步数据。因为mongodb是通过oplog这个表来使集群中的不同机器数据同步的,这样做的话可以保证es里面的数据和mongodb里面的是一样的,因为mongodb中的数据一有改变,都会通过oplog反映到monogodb中。他们还添加了个索引mongodb gridfs里文件的功能,非常好。
但他们修改完后的插件还是有些不满意的地方。他把local库(放oplog的)和普通库的访问密码都设置成同一个,如果local库和普通库的用户名和密码不同那这个插件就不能用了。还有一个就是同步时会把mongodb的表中所有的字段都同步过去,但是有些字段我们并不想把它放到索引中,于是对这个插件再作修改,把local库和普通库的鉴权分开,添加可选字段功能。
运行环境:Elasticsearch 0.19.X
        集群环境下的MongoDB 2.X
注意:该插件只支持集群环境下的mongodb,因为集群环境下的mongodb才有oplog这个表。
安装方法:
安装elasticsearch-mapper-attachments插件(用于索引gridfs里的文件)
%ES_HOME%\bin\plugin.bat -install elasticsearch/elasticsearch-mapper-attachments/1.4.0
安装elasticsearch-river-mongodb(同步插件)
%ES_HOME%\bin\plugin.bat -install laigood/elasticsearch-river-mongodb/laigoodv1.0.0

创建river方法:


curl方式:
    $ curl -XPUT "localhost:9200/_river/mongodb/_meta" -d '  
    {  
      type: "mongodb",  
      mongodb: {   
        db: "test",   
        host: "localhost",   
        port: "27017",   
        collection: "testdb",  
        fields:"title,content",  
        gridfs: "true",  
        local_db_user: "admin",  
        local_db_password:"admin",  
        db_user: "user",  
        db_password:"password"  
      },   
      index: {   
        name: "test",   
        type: "type",  
        bulk_size: "1000",   
        bulk_timeout: "30"  
      }  
    }  
db为同步的数据库名,
host mongodb的ip地址(默认为localhost),
port mongodb的端口,
collection 要同步的表名
fields 要同步的字段名(用逗号隔开,默认全部)
gridfs 是否是gridfs文件(如果collection是gridfs的话就设置成true)
local_db_user local数据库的用户名(没有的话不用写)
local_db_password local数据库的密码(没有的话不用写)
db_user 要同步的数据库的密码(没有的话不用写)
db_password 要同步的数据库的密码(没有的话不用写)
name 索引名(不能之前存在)
type 类型
bulk_size 批量添加的最大数

bulk_timeout 批量添加的超时时间


java api方式:
    client.prepareIndex("_river", "testriver", "_meta")  
            .setSource(  
                jsonBuilder().startObject()  
                    .field("type", "mongodb")  
                    .startObject("mongodb")  
                            .field("host","localhost")  
                            .field("port",27017)  
                            .field("db","testdb")  
                            .field("collection","test")  
                            .field("fields","title,content")  
                            .field("db_user","user")  
                            .field("db_password","password")  
                            .field("local_db_user","admin")  
                            .field("local_db_password","admin")  
                            .endObject()                                              
                     .startObject("index")  
                            .field("name","test")  
                            .field("type","test")  
                            .field("bulk_size","1000")  
                            .field("bulk_timeout","30")  
                            .endObject()  
                     .endObject()  
            ).execute().actionGet();  
本插件git地址:https://github.com/laigood/elasticsearch-river-mongodb

8. 使用More like this实现基于内容的推荐

基于内容的推荐通常是给定一篇文档信息,然后给用户推荐与该文档相识的文档。Lucene的api中有实现查询文章相似度的接口,叫MoreLikeThis。Elasticsearch封装了该接口,通过Elasticsearch的More like this查询接口,我们可以非常方便的实现基于内容的推荐。
先看一个查询请求的json例子:
    {   
        "more_like_this" : {   
            "fields" : ["title", "content"],   
            "like_text" : "text like this one",   
        }   
    } 
其中:
fields是要匹配的字段,如果不填的话默认是_all字段
like_text是匹配的文本。
除此之外还可以添加下面条件来调节结果
percent_terms_to_match:匹配项(term)的百分比,默认是0.3
min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
stop_words:设置停止词,匹配时会忽略停止词
min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
min_word_len:最小的词语长度,默认是0
max_word_len:最多的词语长度,默认无限制
boost_terms:设置词语权重,默认是1
boost:设置查询权重,默认是1
analyzer:设置使用的分词器,默认是使用该字段指定的分词器
下面介绍下如何用java api调用,一共有三种调用方式,不过本质上都是一样的,只不过是做了一些不同程度的封装。
	MoreLikeThisRequestBuilder mlt = new MoreLikeThisRequestBuilder(client, "indexName", "indexType", "id");  
	mlt.setField("title");//匹配的字段  
	SearchResponse response = client.moreLikeThis(mlt.request()).actionGet();  
这种是在查询与某个id的文档相似的文档。这个接口是直接在client那调用的,比较特殊。还有两种就是构造Query进行查询
    MoreLikeThisQueryBuilder query = QueryBuilders.moreLikeThisQuery();  
    query.boost(1.0f).likeText("xxx").minTermFreq(10);  

这里的boost、likeText方法完全和上面的参数对应的。下面这种就是把要匹配的字段作为参数传进来,参数和MoreLikeThisQueryBuilder是一样的。

    MoreLikeThisFieldQueryBuilder query = QueryBuilders.moreLikeThisFieldQuery("fieldNmae");


你可能感兴趣的:(ElasticSearch)