附上示例程序的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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </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<String> keywords; } |
5、定義Repository接口
這裏繼承了ElasticsearchRepository,包括了關係型數據庫基本操作
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
interface ConferenceRepository extends ElasticsearchRepository<Conference, String> {} |
擴展方式跟mysql集成文章中提到的一樣,列一個例子出來,主要根據名字來擴展,比如,
List<Conference> findByNameAndDate (String name, String date); |
附帶一些擴展例子:
關鍵字 |
例子 |
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<Test> moreLikeByContent(String content) { 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中已經創建了索引並且插入了幾條數據了。