赞
踩
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
这里假设各位已经简单了解过elasticsearch,并不对es进入更多的,更深层次的解释,如有必要,会在写文章专门进行es讲解。
Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API
来隐藏Lucene的复杂性,从而让全文搜索变得简单。
index ==》索引 ==》Mysql中的一个库,库里面可以建立很多表,存储不同类型的数据,而表在ES中就是type。
type ==》类型 ==》相当于Mysql中的一张表,存储json类型的数据
document ==》文档 ==》一个文档相当于Mysql一行的数据
field ==》列 ==》相当于mysql中的列,也就是一个属性
这里多说下:
在Elasticsearch6.0.0或者或者更新版本中创建的索引只会包含一个映射类型(mappingtype)。在5.x中创建的具有多个映射类型的索引在Elasticsearch6.x中依然会正常工作。在Elasticsearch7.0.0中,映射类型type将会被完全移除。
开始的时候,我们说“索引(index)”类似于SQL数据库中的“数据库”,将“类型(type)”等同于“表”。
这是一个糟糕的类比,并且导致了一些错误的假设。在SQL数据库中,表之间是相互独立的。一个表中的各列并不会影响到其它表中的同名的列。而在映射类型(mappingtype)中却不是这样的。
在同一个Elasticsearch索引中,其中不同映射类型中的同名字段在内部是由同一个Lucene字段来支持的。换句话说,使用上面的例子,user类型中的user_name字段与tweet类型中的user_name字段是完全一样的,并且两个user_name字段在两个类型中必须具有相同的映射(定义)。
这会在某些情况下导致一些混乱,比如,在同一个索引中,当你想在其中的一个类型中将deleted字段作为date类型,而在另一个类型中将其作为boolean字段。
在此之上需要考虑一点,如果同一个索引中存储的各个实体如果只有很少或者根本没有同样的字段,这种情况会导致稀疏数据,并且会影响到Lucene的高效压缩数据的能力。
基于这些原因,将映射类型的概念从Elasticsearch中移除。
springboot | elasticsearch |
---|---|
2.0.0.RELEASE | 2.2.0 |
1.4.0.M1 | 1.7.3 |
1.3.0.RELEASE | 1.5.2 |
1.2.0.RELEASE | 1.4.4 |
1.1.0.RELEASE | 1.3.2 |
1.0.0.RELEASE | 1.1.1 |
1、None of the configured nodes are available 或者org.elasticsearch.transport.RemoteTransportException: Failed to deserialize exception response from stream
原因:spring data elasticSearch 的版本与Spring boot、Elasticsearch版本不匹配。
这是版本之间的对应关系。Spring boot 1.3.5默认的elasticsearch版本是1.5.2,此时启动1.7.2版本以下的Elasticsearch客户端连接正常。
注:注意java的es默认连接端口是9300,9200是http端口,这两个在使用中应注意区分。
2、Caused by: java.lang.IllegalArgumentException: @ConditionalOnMissingBean annotations must specify at least one bean (type, name or annotation)
原因:spring boot是1.3.x版本,而es采用了2.x版本。在es的2.x版本去除了一些类,而这些类在spring boot的1.3.x版本中仍然被使用,导致此错误
以上解决参考下面的对应关系:
Spring Boot Version (x) | Spring Data Elasticsearch Version (y) | Elasticsearch Version (z) |
---|---|---|
x <= 1.3.5 | y <= 1.3.4 | z <= 1.7.2* |
x >= 1.4.x | 2.0.0 <=y < 5.0.0** | 2.0.0 <= z < 5.0.0** |
请一定注意版本兼容问题。这关系到很多maven依赖。Spring Data Elasticsearch Spring Boot version matrix
ik 分词对应的版本关系:
Analyzer:
ik_smart
, ik_max_word
, Tokenizer: ik_smart
, ik_max_word
Versions
IK version | ES version |
---|---|
master | 6.x -> master |
6.2.2 | 6.2.2 |
6.1.3 | 6.1.3 |
5.6.8 | 5.6.8 |
5.5.3 | 5.5.3 |
5.4.3 | 5.4.3 |
5.3.3 | 5.3.3 |
5.2.2 | 5.2.2 |
5.1.2 | 5.1.2 |
1.10.6 | 2.4.6 |
1.9.5 | 2.3.5 |
1.8.1 | 2.2.1 |
1.7.0 | 2.1.1 |
1.5.0 | 2.0.0 |
1.2.6 | 1.0.0 |
1.2.5 | 0.90.x |
1.1.3 | 0.20.x |
1.0.0 | 0.16.2 -> 0.19.0 |
maven依赖:前提是依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --></parent><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency>
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Spring-data-elasticsearch为我们提供了@Document
、@Field
等注解,如果某个实体需要建立索引,只需要加上这些注解即可
1.类上注解:@Document (相当于Hibernate实体的@Entity/@Table)(必写),加上了@Document
注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词。
类型 | 属性名 | 默认值 | 说明 |
---|---|---|---|
String | indexName | 无 | 索引库的名称,建议以项目的名称命名 |
String | type | “” | 类型,建议以实体的名称命名 |
short | shards | 5 | 默认分区数 |
short | replica | 1 | 每个分区默认的备份数 |
String | refreshInterval | “1s” | 刷新间隔 |
String | indexStoreType | “fs” | 索引文件存储类型 |
2.主键注解:@Id (相当于Hibernate实体的主键@Id注解)(必写)
只是一个标识,并没有属性。
3.属性注解 @Field (相当于Hibernate实体的@Column注解)
@Field默认是可以不加的,默认所有属性都会添加到ES中。加上@Field之后,@document默认把所有字段加上索引失效,只有家@Field 才会被索引(同时也看设置索引的属性是否为no)
类型 | 属性名 | 默认值 | 说明 |
---|---|---|---|
FieldType | type | FieldType.Auto | 自动检测属性的类型 |
FieldIndex | index | FieldIndex.analyzed | 默认情况下分词 |
boolean | store | false | 默认情况下不存储原文 |
String | searchAnalyzer | “” | 指定字段搜索时使用的分词器 |
String | indexAnalyzer | “” | 指定字段建立索引时指定的分词器 |
String[] | ignoreFields | {} | 如果某个字段需要被忽略 |
实现方式比较多,已经存在的接口,使用根据需要继承即可:
1、CrudRepository接口
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S entity); Optional<T> findById(ID primaryKey); Iterable<T> findAll(); long count(); void delete(T entity); boolean existsById(ID primaryKey); // … more functionality omitted.}
2、PagingAndSortingRepository接口
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable);}
例子:
分页:
PagingAndSortingRepository<User, Long> repository = // … get access to a beanPage<User> users = repository.findAll(new PageRequest(1, 20));
计数:
interface UserRepository extends CrudRepository<User, Long> { long countByLastname(String lastname);}
3、其他,参考官网
自定义查询实现
那么我们如何自定义方法呢?我们只要使用特定的单词对方法名进行定义,那么Spring就会对我们写的方法名进行解析,
该机制条前缀find…By
,read…By
,query…By
,count…By
,和get…By
从所述方法和开始分析它的其余部分。引入子句可以包含进一步的表达式,如Distinct
在要创建的查询上设置不同的标志。然而,第一个By
作为分隔符来指示实际标准的开始。在非常基础的层次上,您可以定义实体属性的条件并将它们与And
和连接起来Or
。
interface PersonRepository extends Repository<User, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // Enables the distinct flag for the query List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // Enabling ignoring case for an individual property List<Person> findByLastnameIgnoreCase(String lastname); // Enabling ignoring case for all suitable properties List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // Enabling static ORDER BY for a query List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname);}
构建查询属性算法原理:
如上例所示。在查询创建时,确保解析的属性是托管类的属性。但是,你也可以通过遍历嵌套属性来定义约束。假设Person x
有一个Address
和 ZipCode
。在这种情况下,方法名称为
List<Person> findByAddressZipCode(ZipCode zipCode);
创建属性遍历x.address.zipCode
。解析算法首先将整个part(AddressZipCode
)作为属性进行解释,然后检查具有该名称属性的类。如果皮匹配成功,则使用该属性。如果不是属性,则算法拆分从右侧的驼峰部分头部和尾部,并试图找出相应的属性,在我们的例子,AddressZip
和Code
。如果算法找到具有该头部的属性,它将采用尾部并继续从那里构建树,然后按照刚刚描述的方式分割尾部。如果第一个分割不匹配,则算法将分割点移动到左侧(Address
,ZipCode
)并继续。
虽然这应该适用于大多数情况,但算法仍可能会选择错误的属性。假设这个Person
类也有一个addressZip
属性。该算法将在第一轮拆分中匹配,并且基本上选择错误的属性并最终失败(因为addressZip
可能没有code
属性的类型)。
为了解决这个歧义,你可以\_
在你的方法名称中使用手动定义遍历点。所以我们的方法名称会像这样结束:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
由于我们将下划线视为保留字符,因此我们强烈建议遵循标准的Java命名约定(即,不要在属性名称中使用下划线,而应使用驼峰大小写)其他分页查询
Page<User> findByLastname(String lastname, Pageable pageable);Slice<User> findByLastname(String lastname, Pageable pageable);List<User> findByLastname(String lastname, Sort sort);
也可以用Java8 Stream查询和sql语句查询
1 2 3 4 5 6 7 |
|
有些在复杂的可以使用es查询语句
我们可以使用@Query注解进行查询,这样要求我们需要自己写ES的查询语句
public interface BookRepository extends ElasticsearchRepository<Book, String> { @Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}") Page<Book> findByName(String name,Pageable pageable);}
方法和es查询转换:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在使用的时候没有找到直接的例子,由于between是转换成range,所以需要范围参数from和to,
举例如下:
Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(String recruitWork, String recruitCitys, Integer workType, Date fromXjTime, Date toXjTime,Pageable pageable);
注意:这里必须要注意的是:只要使用了between参数,****XjTimeBetween(......,from,to) ,使用该方法的时候,必须要传递范围参数from,to,不能同时为空。
否则异常
<strong>org.springframework.dao.InvalidDataAccessApiUsageException: Range [* TO *] is not allowed</strong> at org.springframework.data.elasticsearch.core.query.Criteria.between(Criteria.java:304) at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.from(ElasticsearchQueryCreator.java:127) at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.and(ElasticsearchQueryCreator.java:76) at org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator.and(ElasticsearchQueryCreator.java:46) at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109) at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88) at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73) at org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery.createQuery(ElasticsearchPartQuery.java:102) at org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery.execute(ElasticsearchPartQuery.java:51) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:499) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:477) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy86.findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(Unknown Source) at com.zhimingdeng.service.impl.EsIndexServiceImpl.findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(EsIndexServiceImpl.java:155)
因为底层要求参数不能同时为空
对于Elasticsearch原生支持date类型,json格式通过字符来表示date类型。所以在用json提交日期至elasticsearch的时候,es会隐式转换,把es认为是date类型的字符串直接转为date类型,间字段内容实际上就是转换成long类型作为内部存储的(所以完全可以接受其他时间格式作为时间字段的内容)。至于什么样的字符串es会认为可以转换成date类型,参考elasticsearch官网介绍
date类型是包含时区信息的,如果我们没有在json代表日期的字符串中显式指定时区,对es来说没什么问题,但是对于我们来说可能会发现一些时间差8个小时的问题。
Elastic本身有一种特殊的时间格式,其形式如"2016-01-25T00:00:00",此格式为ISO8601标准。具体时间日期格式要求可以参见es官方文档。
然而我们在计算日期间隔,甚至按日分类的时候,往往需要把这个String时间转化为Unix时间戳(Unix Timestamp(时间戳))的形式,再进行计算。而通常,这个时间戳会以毫秒的形式(Java)保存在一个long类型里面,这就涉及到了String与long类型的相互转化。
此外在使用Java Client聚合查询日期的时候,需要注意时区问题,因为默认的es是按照UTC标准时区算的,所以不设置的聚合统计结果是不正确的。默认不设置时区参数,es是安装UTC的时间进行查询的,所以分组的结果可能与预期不一样。
JSON
没有日期类型,因此在 Elasticsearch 中可以表达成:
long
类型或秒级别的 integer
类型,比如: 1515150699465, 1515150699; 实际上不管日期以何种格式写入,在 ES 内部都会先穿换成 UTC 时间并存储为 long
类型。日期格式可以自定义,如果没有指定的话会使用以下的默认格式:
"strict_date_optional_time||epoch_millis"
因此总结来说,不管哪种可以表示时间的格式写入,都可以用来表示时间
所以这里引出多种解决方案:
1、es 默认的是 utc 时间,而国内服务器是 cst 时间,首先有时间上的差距需要转换。但是如果底层以及上层都统一用时间戳,完美解决时区问题。但是时间戳对我们来说不直观
2、我们在往es提交日期数据的时候,直接提交带有时区信息的日期字符串,如:“2016-07-15T12:58:17.136+0800”
3、还有另外的一种:
直接设置format为你想要的格式,比如 "yyyy-MM-dd HH:mm:ss"
然后存储的时候,指定格式,并且Mapping 也是指定相同的format
。
第一次使用方式:
1 2 3 4 |
|
我这里是数据是从数据库直接读取,使用的datetime类型,原来直接使用的时候,抛异常:
MapperParsingException[failed to parse [***]]; nested: IllegalArgumentException[Invalid format: "格式"];
原因是: jackson库在转换为json的时候,将Date类型转为为了long型的字符串表示,而我们定义的是date_optional_time格式的字符串,所以解析错误,
具体的解决办法:去掉注解中的format=DateFormat.date_optional_time,让其使用默认的格式,也就是 'strict_date_optional_time||epoch_millis' , 既能接受 date_optional_time格式的,也能接受epoch_millis格式,由于为了查看更直观感受改为如下:
@Field( type = FieldType.Date, format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" )private Date gmtCreate;
改成这样后,底层多种格式都可以存储,如果没有根据时间进行范围查找,这里基本上已经就告一段落了。
时间范围查找需求:存储Date,和取出来也是Dete
存储的时候利用各种JSON对象,如 Jackson 等。存储的时候就可以用JSON Format一下再存储,然后取出来后
@Field( type = FieldType.Date, format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss" ) @JsonFormat (shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd HH:mm:ss",timezone="GMT+8") private Date xjTime;
有了这个注解后,
timezone="GMT+8" 主要是因为底层存放的数据日期时区是UTC,这里转换成GMT
真实存储格式如下(高能瞎眼):
时间范围查找需求注意:
根据条件查询的时候,时间范围需要传入range,这里涉及到了两种选择,底层查询方法实现的时候range的参数为
1.date:
传入的是date参数,然后就行查询的时候,会报异常,因为我把日期转成了yyyy-MM-dd HH:mm:ss,但是底层数据是2018-03-27T16:00:00.000Z这种格式,导致错误,详细异常如下
org.elasticsearch.action.search.SearchPhaseExecutionException: all shards failed at org.elasticsearch.action.search.AbstractSearchAsyncAction.onFirstPhaseResult(AbstractSearchAsyncAction.java:206) at org.elasticsearch.action.search.AbstractSearchAsyncAction$1.onFailure(AbstractSearchAsyncAction.java:152) at org.elasticsearch.action.ActionListenerResponseHandler.handleException(ActionListenerResponseHandler.java:46) at org.elasticsearch.transport.TransportService$DirectResponseChannel.processException(TransportService.java:855) at org.elasticsearch.transport.TransportService$DirectResponseChannel.sendResponse(TransportService.java:833) at org.elasticsearch.transport.TransportService$4.onFailure(TransportService.java:387) at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:39) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)<strong>Caused by: org.elasticsearch.ElasticsearchParseException: failed to parse date field [2018-03-27T16:00:00.000Z] with format [yyyy-MM-dd HH:mm:ss]</strong> at org.elasticsearch.common.joda.DateMathParser.parseDateTime(DateMathParser.java:203) at org.elasticsearch.common.joda.DateMathParser.parse(DateMathParser.java:67) at org.elasticsearch.index.mapper.core.DateFieldMapper$DateFieldType.parseToMilliseconds(DateFieldMapper.java:451) at org.elasticsearch.index.mapper.core.DateFieldMapper$DateFieldType.innerRangeQuery(DateFieldMapper.java:435) at org.elasticsearch.index.mapper.core.DateFieldMapper$DateFieldType.access$000(DateFieldMapper.java:199) at org.elasticsearch.index.mapper.core.DateFieldMapper$DateFieldType$LateParsingQuery.rewrite(DateFieldMapper.java:224) at org.apache.lucene.search.BooleanQuery.rewrite(BooleanQuery.java:278) at org.apache.lucene.search.IndexSearcher.rewrite(IndexSearcher.java:837) at org.elasticsearch.search.internal.ContextIndexSearcher.rewrite(ContextIndexSearcher.java:81) at org.elasticsearch.search.internal.DefaultSearchContext.preProcess(DefaultSearchContext.java:231) at org.elasticsearch.search.query.QueryPhase.preProcess(QueryPhase.java:103) at org.elasticsearch.search.SearchService.createContext(SearchService.java:676) at org.elasticsearch.search.SearchService.createAndPutContext(SearchService.java:620) at org.elasticsearch.search.SearchService.executeDfsPhase(SearchService.java:264) at org.elasticsearch.search.action.SearchServiceTransportAction$SearchDfsTransportHandler.messageReceived(SearchServiceTransportAction.java:360) at org.elasticsearch.search.action.SearchServiceTransportAction$SearchDfsTransportHandler.messageReceived(SearchServiceTransportAction.java:357) at org.elasticsearch.transport.TransportRequestHandler.messageReceived(TransportRequestHandler.java:33) at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:75) at org.elasticsearch.transport.TransportService$4.doRun(TransportService.java:376) at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) ... 3 common frames omitted<strong>Caused by: java.lang.IllegalArgumentException: Invalid format: "2018-03-27T16:00:00.000Z" is malformed at "T16:00:00.000Z"</strong> at org.joda.time.format.DateTimeParserBucket.doParseMillis(DateTimeParserBucket.java:187) at org.joda.time.format.DateTimeFormatter.parseMillis(DateTimeFormatter.java:826) at org.elasticsearch.common.joda.DateMathParser.parseDateTime(DateMathParser.java:200) ... 22 common frames omitted
实现参考:解决:传入的date参数格式化成底层的类型
Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(String recruitWork, String recruitCitys, Integer workType, Date fromXjTime, Date toXjTime,Pageable pageable);
2.String:
参数直接使用string,避免上层转换成不合适的时间格式,使用框架底层自己转换,避免错误。
实现参考:
Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(String recruitWork, String recruitCitys, Integer workType, String fromXjTime, String toXjTime,Pageable pageable);
https://stackoverflow.com/questions/29122071/elasticsearch-failed-to-parse-date
https://stackoverflow.com/questions/29496081/spring-data-elasticsearchs-field-annotation-not-working
https://stackoverflow.com/questions/32042430/elasticsearch-spring-data-date-format-always-is-long
个人认为springboot 这种集成es的方法,最大的优点是开发速度快,不要求对es一些api要求熟悉,能快速上手,即使之前对es不胜了解,也能通过方法名或者sql快速写出自己需要的逻辑,而具体转换成api层的操作,则有框架底层帮你实现。
缺点也显而易见首先,使用的springboot的版本对es的版本也有了要求,不能超过es的某些版本号,部署时需要注意。第二,速度提升的同时,也失去了一些实用api的灵活性。一些比较灵活的条件封装不能很容易的实现。各有利弊,各位权衡。
原文地址:http://www.cnblogs.com/guozp/p/8686904.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。