https://www.jianshu.com/p/27e1d583aafbjavascript
翻譯自官方文檔英文版,有刪減。html
BioMed Central Development Team version 2.1.3.RELEASE, 2017-04-19java
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.node
要求Elasticsearch 0.20.2或者以上版本。算法
首先添加依賴:spring
<dependencies> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> <version>2.1.3.RELEASE</version> </dependency> </dependencies>
首先咱們介紹通用的Spring Data repository,Elasticsearch repository是構建在它的基礎之上。它的目標是針對不一樣的持久化存儲顯著的減小數據訪問層實現的樣板代碼的數量。mongodb
Spring Data repository抽象的中央接口是Repository
。它須要一個域類來管理也須要這個域類的id類型做爲類型參數。這個接口主要扮演一個標記者接口來捕獲須要處理的類型而且幫助你發現拓展的接口。CrudRepository
接口爲它管理的實體類提供了複雜的CRUD功能。json
CrudRepository 接口app
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { // 保存實體 <S extends T> S save(S entity); // 根據id查詢實體 T findOne(ID primaryKey); // 返回全部實體類 Iterable<T> findAll(); // 返回全部實體類的數量 Long count(); // 刪除指定的實體類 void delete(T entity); // 根據指定的id判斷某個實體是否存在 boolean exists(ID primaryKey); // … 更多的方法省略 }
CrudRepository
接口還有一個子類PagingAndSortingRepository
添加了額外的方法來簡化分頁訪問實體:框架
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }
訪問User表第二頁的數據,每頁20條記錄,你能夠這樣寫:
PagingAndSortingRepository<User, Long> repository = Page<User> users = repository.findAll(new PageRequest(1, 20));
做爲對查詢方法的補充,查詢派生出了計數和刪除查詢。
派生計數查詢
public interface UserRepository extends CrudRepository<User, Long> { Long countByLastname(String lastname); }
派生刪除查詢
public interface UserRepository extends CrudRepository<User, Long> { Long deleteByLastname(String lastname); List<User> removeByLastname(String lastname); }
標準的CRUD功能repositories一般包含了查詢。經過Spring Data,聲明查詢須要4步處理:
一、聲明一個接口繼承Repository
或者它的一個子接口,傳入域類和id類型:
interface PersonRepository extends Repository<Person, Long> { … }
二、聲明一個查詢方法:
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
三、配置Spring爲這些接口建立代理實例。
JavaConfig:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
XML 配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
這裏使用了jpa命名空間。若是你想使用別的存儲技術,假如是mongodb
,你只須要將jpa
替換爲mongodb
。
注意在JavaConfig配置方式裏並無明確指定註解類默認掃描的包,能夠經過@EnableJpaRepositories
註解的basePackage
屬性來指定。
四、得到注入的repository實例而且使用它。
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
下面的章節將詳細的解釋上面每一個步驟。
做爲第一步你定義一個特定域類repository接口。接口必須繼承Repository
而且輸入域類和其ID的類型。若是你想曝露這個域類的CRUD方法,你能夠繼承CrudRepository
來替代Repository
。
典型的,你的接口通常會繼承Repository
, CrudRepository
或者 PagingAndSortingRepository
。兩者選一,若是你不想繼承Spring Data的接口,你能夠在你的repository接口增長@RepositoryDefinition
註解。繼承CrudRepository
曝露了一套完整的方法來操做你的實體類。若是你想選擇性的曝露一些方法,只須要將你想要曝露的方法從CrudRepository
裏複製到你的域repository。
@NoRepositoryBean interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> { T findOne(ID id); T save(T entity); } interface UserRepository extends MyBaseRepository<User, Long> { User findByEmailAddress(EmailAddress emailAddress); }
第一步爲你的全部的域repositories定義一個通用的基礎接口,而且曝露findOne(…)
和save(…)
方法。這樣一來UserRepository
接口將只能保存用戶,根據id查詢單個用戶和根據電子郵件地址查詢用戶。
注意,中間的repository接口被@NoRepositoryBean
註解標記。確保Spring Data不會爲你的基本接口建立實例。
若是隻使用一個Spring Data模塊讓事情變得簡單,全部的repository接口在定義的範圍內被約束到一個Spring Data模塊。有時候應用程序須要使用更多的Spring Data模塊。例如,須要一個repository定義區分開兩個不一樣的持久化技術。Spring Data若是發如今類路徑下有多個repository工廠那麼它會進入嚴格repository配置模式。對於某個repository定義嚴格配置須要repository的詳細信息或者域類來決定Spring Data模塊綁定:
@Entity
或者Spring Data本身提供的註解,例如Spring Data MongoDB/Spring Data Elasticsearch的@Document
。使用特定的模塊接口定義Repository:
interface MyRepository extends JpaRepository<User, Long> { } @NoRepositoryBean interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { … } interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository
和 UserRepository
繼承自JpaRepository
。它們都是Spring Data JPA有效的候選者。
使用通用接口定義Repository:
interface AmbiguousRepository extends Repository<User, Long> { … } @NoRepositoryBean interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { … } interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository
和 AmbiguousUserRepository
繼承自Repository
和CrudRepository
。然而使用一個獨一無二的Spring Data接口才是完美的,不然沒法區分這些repositories屬於那個特定的Spring Data模塊。
使用域類加註解定義Repository:
interface PersonRepository extends Repository<Person, Long> { … } @Entity public class Person { … } interface UserRepository extends Repository<User, Long> { … } @Document public class User { … }
PersonRepository
引用了Person
而且它使用了JPA註解@Entity
那麼這個repository顯然屬於Spring Data JPA。UserRepository
使用了User
而且被Spring Data MongoDB的@Document
註解(我私下裏認爲這部分文檔是從Spring Data MongoDB那複製過來的)。
將上面講到的這兩個註解混合使用:
interface JpaPersonRepository extends Repository<Person, Long> { … } interface MongoDBPersonRepository extends Repository<Person, Long> { … } @Entity @Document public class Person { … }
這將使Spring Data沒法區分出到底該用哪一個Spring Data模塊,致使不明確的行爲。
最後一種區分repositories的方式是限定repository所在的包。例如以前的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
JavaConfig:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa") @EnableMongoRepositories(basePackages = "com.acme.repositories.mongo") interface Configuration { }
從方法的名稱中repository代理有兩種方式去派生一個指定存儲技術的查詢。它能夠直接從方法的名稱派生查詢,或者使用一個手動定義的查詢。可用的選項依賴於使用的存儲技術。可是這裏有一個策略決定了什麼樣的查詢被建立。讓咱們看下這些選項。
下面的策略對於repository底層解析查詢是可用的。你能夠配置這些策略,在XML配置中使用query-lookup-strategy
屬性或者使用Enable${store}Repositories註解的queryLookupStrategy
屬性在java 配置中。一些策略可能不支持某些特別的數據存儲。
CREATE
嘗試從查詢方法的名稱構建一個特定存儲技術的查詢。通常的方法是從查詢方法名稱中移除一系列約定好的前綴而且解析剩下的字符。
USE_DECLARED_QUERY
嘗試去找到一個已聲明的查詢而且在找不到時拋出一個異常。這個查詢可使用一個註解定義或者經過其餘方式來聲明。查詢特定存儲技術的文檔來得到更多可用的選項。若是repository底層找不到某個方法對應的已聲明的查詢,啓動時將會失敗。
CREATE_IF_NOT_FOUND
(默認)組合了CREATE
和USE_DECLARED_QUERY
策略。它首先查找是否有已經聲明的查詢語句,若是沒有它將會根據方法名稱建立一個查詢。
查詢構建器機制內建於Spring Data repository底層,對全部repository實體構建約束查詢是頗有用的。這個機制祛除方法名稱中的前綴:find…By
,read…By
,query…By
, count…By
和get…By
而後開始解析剩下的字符。這個邏輯還能夠包含一些更多的表達式,例如Distinct
表示建立一個沒有重複數據的查詢。可是第一個By
扮演着一個分隔符,指明瞭真正的條件。最基本的用法是能夠定義一些條件在實體類的屬性上,而後用And
或者Or
連接。
一些例子:
public interface PersonRepository extends Repository<User, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // 啓用distinct查詢 List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // 忽略某個字段大小寫 List<Person> findByLastnameIgnoreCase(String lastname); // 忽略多個字段大小寫 List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // 啓用排序 List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname); }
真正的解析結果依賴於你使用的持久化存儲技術。若是是JPA那麼就會轉成相應的SQL語句(譯者注)。可是這裏有一些通用的事情須要注意。
表達式一般是實體類的屬性加上操做符。你能夠用And
或者Or
連接屬性。相似的還有Between
, LessThan
, GreaterThan
和Like
。
IgnoreCase
這個並非全部持久化存儲技術都支持,因此請查詢特定的用戶指南文檔。
屬性表達式只能引用實體類中的屬性。假設一個Person
有一個Address
對象,Address
裏有一個ZipCode
對象。若是想根據ZipCode
查詢人那麼可能會這樣寫:
List<Person> findByAddressZipCode(ZipCode zipCode);
解析算法會開始解釋AddressZipCode
總體做爲一個屬性。若是沒找到會根據駝峯寫法從繼續進行分割爲AddressZip
和Code
。若是仍是沒有找到怎會繼續進行分割,此次結果是Address
和ZipCode
。這顯然不是理想狀況。
爲了解決模棱兩可的屬性名稱,你可使用_
在你的方法名稱中手動的進行分割,那麼上面的語句將會是:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
上面的查詢方法中,僅僅定義了方法的參數來指定查詢的參數。除此以外還會識別Pageable
和Sort
這兩個參數來動態的進行分頁與排序。下面是一些例子:
Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);
第一個方法容許你傳遞一個org.springframework.data.domain.Pageable
實例到查詢方法來動態的進行分頁。一個Page
知道總數量和一共多少頁可用。它之因此知道這些是由於底層框架觸發了一個技計數查詢來計算所有的數量。根據使用的數據存儲技術不一樣這可能消耗很大時間,可使用Slice
來替代它。Slice
只知道是否存在下一個Slice
可用,這將會很是適合查詢超大結果集。
排序操做也能夠經過Pageable
接口來處理。若是你僅僅須要排序那麼可使用org.springframework.data.domain.Sort
實例做爲方法參數。如你所見,查詢方法也能夠僅僅返回一個List
。這將約束查詢方法只查詢給定範圍的數據。
查詢的結果數量能夠經過關鍵字first
或者top
來限制。一個可選的數字能夠追加到first
或者top
後面來指定查詢結果確切的數量。若是這個數字被省略了,將會默認指定1爲返回結果的數量。下面是一些例子:
User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); Slice<User> findTop3ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);
經過使用Java 8 Stream<T>
做爲查詢方法的結果,能夠遞增地對結果處理。下面是一些例子:
@Query("select u from User u") Stream<User> findAllByCustomQueryAndStream(); Stream<User> readAllByFirstnameNotNull(); @Query("select u from User u") Stream<User> streamAllPaged(Pageable pageable);
注意Stream
使用完後要調用close()
方法關閉它。不是全部的Spring Data 模塊都支持Stream<T>
做爲返回結果。
本章講建立接口和repository接口bean的定義。
每個Spring Data模塊都包含一個repositories元素,容許你簡單的定義Spring將要自動掃描的基礎包。
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <repositories base-package="com.acme.repositories" /> </beans:beans>
對於每一個發現的接口,底層框架註冊特定持久化技術的FactoryBean
來建立合適的代理處理查詢方法的調用。例如UserRepository
將會被註冊爲一個名爲userRepository
的bean。base-package
屬性支持通配符。
使用@Enable${store}Repositories
註解能夠觸發指定數據存儲技術的repository。
一個簡單的配置:
@Configuration @EnableJpaRepositories("com.acme.repositories") class ApplicationConfiguration { @Bean public EntityManagerFactory entityManagerFactory() { // … } }
這個例子代表咱們使用的Spring Data JPA。
爲了添加一個適用於全部repository的自定義行爲,首先添加一箇中介接口來聲明通用的行爲。
@NoRepositoryBean public interface MyRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> { void sharedCustomMethod(ID id); }
如今你本身的repository接口將會繼承這個中介接口而不是繼承Repository
接口。而後建立這個中介接口的實現類繼承特定持久化技術的repository父類。這個類將會扮演一個repository代理的自定義父類。
public class MyRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> { private final EntityManager entityManager; public MyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); // Keep the EntityManager around to used from the newly introduced methods. this.entityManager = entityManager; } public void sharedCustomMethod(ID id) { // implementation goes here } }
Spring <repositories />
命名空間默認的行爲是爲base-package
下全部接口提供一個實現類。這意味着MyRepository
接口的實現將會由Spring建立。這不是咱們想要的。爲了不這種狀況的發生你能夠增長一個@NoRepositoryBean
註解或者將它移出base-package
。
最後一步是讓Spring Data底層框架認識這個自定義repository父類。下面是一個JavaConfig的例子:
@Configuration @EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class) class ApplicationConfiguration { … }
XML配置的例子:
<repositories base-package="com.acme.repository" base-class="….MyRepositoryImpl" />
終於要開始介紹Elasticsearch repository實現的詳細內容了。
Spring Data Elasticsearch模塊包含一個自定義命名空間容許用戶定義repository bean和ElasticsearchServer
實例。
使用命名空間設置Elasticsearch repositories的例子:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd"> <elasticsearch:repositories base-package="com.acme.repositories" /> </beans>
使用Transport Client或者Node Client元素類註冊一個Elasticsearch Server實例。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd"> <elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" /> </beans>
直接上例子:
@Configuration @EnableElasticsearchRepositories(basePackages = "com/acme/repositories") static class Config { @Bean public ElasticsearchOperations elasticsearchTemplate() { return new ElasticsearchTemplate(nodeBuilder().local(true).node().client()); } }
Elasticsearch module支持全部的基本查詢構建特性,好比從方法名稱或者手動聲明一個查詢條件。
通常的Elasticsearch生成查詢機制與1.2 查詢方法章節描述的同樣。下面是一些小例子:
根據方法名稱建立查詢
public interface BookRepository extends Repository<Book, String> { List<Book> findByNameAndPrice(String name, Integer price); }
它將會被轉換成Elasticsearch json查詢:
{ "bool" : { "must" : [ { "field" : {"name" : "?"} }, { "field" : {"price" : "?"} } ] } }
下面的表格是Elasticsearch module根據方法名建立查詢的關鍵字。
關鍵字 | 示例 | Elasticsearch json查詢 |
---|---|---|
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" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
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(Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn(Collection<String>names) | {"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}}}} |
使用@Query
註解查詢的例子:
public interface BookRepository extends ElasticsearchRepository<Book, String> { @Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}") Page<Book> findByName(String name,Pageable pageable); }
條件構建器提升了查詢的效率。
private ElasticsearchTemplate elasticsearchTemplate; SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()) .withFilter(boolFilter().must(termFilter("id", documentId))) .build(); Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
Elasticsearch針對超大結果集提供了scan 和 scroll特性。
ElasticsearchTemplate
有scan 和 scroll方法,請看下面的例子:
SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()) .withIndices("test-index") .withTypes("test-type") .withPageable(new PageRequest(0,1)) .build(); String scrollId = elasticsearchTemplate.scan(searchQuery,1000,false); List<SampleEntity> sampleEntities = new ArrayList<SampleEntity>(); boolean hasRecords = true; while (hasRecords){ Page<SampleEntity> page = elasticsearchTemplate.scroll(scrollId, 5000L , new ResultsMapper<SampleEntity>() { @Override public Page<SampleEntity> mapResults(SearchResponse response) { List<SampleEntity> chunk = new ArrayList<SampleEntity>(); for(SearchHit searchHit : response.getHits()){ if(response.getHits().getHits().length <= 0) { return null; } SampleEntity user = new SampleEntity(); user.setId(searchHit.getId()); user.setMessage((String)searchHit.getSource().get("message")); chunk.add(user); } return new PageImpl<SampleEntity>(chunk); } }); if(page != null) { sampleEntities.addAll(page.getContent()); hasRecords = page.hasNextPage(); } else{ hasRecords = false; } } }
到此告一段落。