附上示例程序的github地址:https://github.com/bjtudujunlin/SpringDataExample
1、简介
Elasticsearch(简称ES) 是一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎,可以说 Lucene 是当今最先进,最高效的全功能开源搜索引擎框架。
ES可以做哪些工作呢,概括起来有以下四点:
(1) 全文搜索功能,这个是最简单也最重要的功能;
(2) 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
(3) 实时分析的分布式搜索引擎
(4) 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
所有这些功能,最低配置下在一台服务器上就能跑起来。客户端怎么与ES进行交互呢,可以看看ES的教程http://www.learnes.net/getting_started/what_is_it.html,ES支持java客户端也支持Restful协议进行交互,使用起来还是蛮方便的。就是命令有点多原始的。
在这里,隆重退出Spring Data与ES进行整合,让操作变得简单。通过两者进行整合,用户可以像操作关系型数据库一样操作ES,CURD操作、排序、分页操作统统一步到位。唯一有一点不足的是,Spring Data目前不支持ES的高亮和全文检索功能,这两个功能需要利用ES自身的客户端来实现,后面例子我会提到。前面标个红,我觉得这个对于系统选型有点作用,根据自己实际情况选择是否用这种方案。对于Spring Data为什么不支持ES的高亮和全文等操作,我的理解是为了统一,因为Spring Data还需要继承各类关系型数据库、非关系型数据库,高亮等操作是其它数据库不支持的,所以也就不方便提供统一的接口,为了保持接口的一致性,Spring Data所有集成案例能做到的都是提供关系型数据库的操作。具体操作来看后面的。
2、首先添加maven依赖
这里可以注意到,dependency标签里面没有添加版本信息,因为版本信息都在parent的pom文件里面进行了统一配置,解决不同jar包版本不兼容的问题。详细的配置文件大家参考github的pom文件吧,这里主要有两个依赖库,一是spring-boot-starter-data-elasticsearch,这个是spring-data提供的es集成库,另一个是lombok,这个库比较有意思,它通过注解的方式为java类自动生成构造函数,为成员变量生成get、set方法,并且按照建造者模式提供java类的访问接口,省去了苦憋呵呵的写各类方法,例子用用在了Conference类上。
<dependency> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-elasticsearchartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency> |
3、配置数据源
这里我们用java配置类的方式在生成数据源,而不是像spring-data和mysql集成中采用的配置文件方式 。首先需要配置一个Client类,这个类是由ES提供的,作用就是以客户端方式与ES建立连接,调用时设置好ES的IP地址和端口就行,当然还有中结点方式加入ES,再生成Client,这里就不介绍了。然后配置ElasticsearchTemplate类,该类的参数有一个Client,就是之前注入的。
@Bean public Client client() { TransportClient client = null; try { client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.1.144"), 9300)); } catch (Exception e) { log.error(e.toString()); } return client; }
@Bean public ElasticsearchTemplate elasticsearchTemplate(Client client) throws Exception { return new ElasticsearchTemplate(client); } |
4、创建实体类
这里实体类为Conference,包含了多个成员变量,大家注意下@Data、@Builder、@NoArgsConstructor、@AllArgsConstructor,这是个就是lombok的注解,自动生成大量方法,字面意思挺清楚的,想了解更多lombok的注解,可以看看这篇博客:
http://www.blogjava.net/fancydeepin/archive/2012/07/12/382933.html。
@Document这个注解呢是配置了ES相关信息,包括索引、类型、分片、备份等。ES和关系型数据库的元素可以这样简单对等:ES中的索引对应数据库,类型对应表,文档就对应一条记录
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import java.util.List;
import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;
@Data @Builder @NoArgsConstructor @AllArgsConstructor @Document(indexName = "conference-index", type = "geo-class-point-type", shards = 1, replicas = 0, refreshInterval = "-1") public class Conference {
private @Id String id; private String name; private @Field(type = Date) String date; private GeoPoint location; private List } |
5、定义Repository接口
这里继承了ElasticsearchRepository,包括了关系型数据库基本操作
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
interface ConferenceRepository extends ElasticsearchRepository |
扩展方式跟mysql集成文章中提到的一样,列一个例子出来,主要根据名字来扩展,比如,
List |
附带一些扩展例子:
关键字 |
例子 |
Elasticsearch查询语句 |
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?","analyze_wildcard" : true}}}}} |
In |
findByNameIn(Collectionnames) |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn(Collectionnames) |
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
暂不支持 |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
开篇提到了,springdata支持的只是关系型数据库相关的接口,那么ES常用的全文检索,结果高亮等操作怎么实现呢,暂时只能用ES原生的接口来做了,附一段代码给大家参考一下, operations就是ElasticsearchOperations对象,这里实现了高亮、排序、分页、全文检索等操作。
public List SearchResponse response = operations.getClient().prepareSearch("testindex").setTypes("testtype") .setSearchType(SearchType.DFS_QUERY_THEN_FETCH).setQuery(QueryBuilders.matchQuery("content", content)) // 全文检索 .addHighlightedField("content")// 高亮,可以设置前缀和后缀 .setFrom(0).setSize(10).setExplain(true)// 分页 .addSort(new ScoreSortBuilder().order(SortOrder.DESC))// 排序 .setTrackScores(true)// 获取得分 .execute().actionGet();
List< Test > chunk = new ArrayList<>(); for (SearchHit searchHit : response.getHits()) { if (response.getHits().getHits().length <= 0) { returnnull; } System.out.println(searchHit.getScore()); Test test = new Test(); test.setId(searchHit.getId()); test.setContent((String) searchHit.getSource().get("content")); test.setHighlight(searchHit.getHighlightFields().get("content").fragments()[0].toString()); chunk.add(test); } returnchunk; } |
。
6、运行程序
实体类和repository都有了,现在就剩把程序跑起来,定义Application类,内容如下。SpringBootApplication注解表明这是一个springboot的应用,EnableElasticsearchRepositories添加了jpa支持,demo方法利用@bean注解同时返回了CommandLineRunner对象,表明这个方法会在springboot启动前加载运行,同时它的参数repository自动注入,springboot会在当前目录和子目录下搜索ElasticsearchOperations类型的接口自动注入。前面注解类中并没有ElasticsearchOperations,原因是ElasticsearchTemplate是从它派生来的。
@SpringBootApplication @EnableElasticsearchRepositories public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication.run(Application.class); }
/** * 返回CommandLineRunner的Bean,在spring boot启动前加载并且执行 * * @param repository * @return */ @Bean public CommandLineRunner demo(ElasticsearchOperations repository) { return (args) -> { // Remove all documents repository.deleteAll(); operations.refresh(Conference.class);
// Save data sample repository.save(Conference.builder().date("2014-11-06").name("Spring eXchange 2014 - London") .keywords(Arrays.asList("java", "spring")).location(new GeoPoint(51.500152D, -0.126236D)).build());
repository.save(Conference.builder().date("2014-12-07").name("Scala eXchange 2014 - London") .keywords(Arrays.asList("scala", "play", "java")).location(new GeoPoint(51.500152D, -0.126236D)) .build());
repository.save(Conference.builder().date("2014-11-20").name("Elasticsearch 2014 - Berlin") .keywords(Arrays.asList("java", "elasticsearch", "kibana")) .location(new GeoPoint(52.5234051D, 13.4113999)).build());
repository.save(Conference.builder().date("2014-11-12").name("AWS London 2014") .keywords(Arrays.asList("cloud", "aws")).location(new GeoPoint(51.500152D, -0.126236D)).build()); repository.save(Conference.builder().date("2014-10-04").name("JDD14 - Cracow") .keywords(Arrays.asList("java", "spring")).location(new GeoPoint(50.0646501D, 19.9449799)).build()); }; }
} |
在Application类中右键运行程序,就可以看见ES中已经创建了索引并且插入了几条数据了。