Repositories
Repositories
是整个Spring Data大家族的核心概念。几乎所有基础级别的Repositories
都是由它衍生出来的。本文基于3.1.0.RC2文档学习总结而来。
Spring Data中的核心仓库是Repository
,仓库可以将域类以及域类的ID类型作为类型参数进行管理,这个接口主要用作标记接口,捕获要使用的类型,并帮助发现扩展此接口的接口(说那么复杂,就我个人里接下来,就2点,第一,一个继承此接口的都是Repository
,第二,但凡继承这个接口的,那都能自动被Spring识别为一个Bean
)。比如CrudRepository
就是继承自Repository
接口,它为正在管理的实体类提供了复杂的CRUD功能,这里的“管理的实体类”就是上述的“域类以及域类的ID”,下面是CrudRepository
中的一些方法:
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
// 保存它所管理的实体类T类型的对象,然后泛型后面接的是实体类的注解类型,这点在后面会有体现
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
所以在自定义实现Repository
接口时会写上泛型,格式为<管理的实体类的类型,实体类ID的类型>
,这就是“仓库可以将域类以及域类的ID类型作为类型参数进行管理”这句话的体现。
除此以外,Spring Data这个大家族为一些主流的持久化技术提供了特定的仓库Repository
,比如为MySQL提供了JpaRepository
,为MongoDB提供了MongoRepository
等,这些接口除了具备相当通用的持久性技术(如CrudRepository
)之外,还扩展了CrudRepository
功能。在Repository
接口之上有一个额外的PagingAndSortingRepository
接口,它添加了额外的方法来简化对实体类的分页访问,里面就两个方法:
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
如果有一个User
多个实例进行分页,想要获取第二页的内容(每页显示20条数据),那可以这么干:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
除了上述的查询方法,还可以派生出计数的功能:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
标准的CRUD功能仓库通常对底层数据库进行查询,使用Spring Data,声明这些查询分为四个步骤:
1.声明一个接口继承Repository
或者Repository
的子类,并用泛型标注它管理的类的类型和类的ID的类型,比如:
interface PersonRepository extends Repository<Person, Long> { … }
2.在第一步的接口中定义抽象方法,比如:
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
3.使用JavaConfig或XML配置,让Spring为我们创建的接口创建代理实例,比如:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config {}
【注意】JavaConfig没有显式的配置包,因为默认情况下使用带注释的类的包,如果想要定制包扫描,就需要使用特定持久化层仓库中的basePackages
配置扫描的目录(可以是多个路径),比如@EnableJpaRepositories(basePackages = "com.glodon.repositories")
;
4.注入第三步中Spring为我们创建的接口实例对象并使用,比如:
class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
// 下面的方式或许更常见
@Autowired
BookRepository bookRepository;
@RequestMapping(value = "/add_index", method = RequestMethod.POST)
public ResponseEntity<String> indexDoc(@RequestBody Book book) {
bookRepository.save(book);
return new ResponseEntity<>("save executed!", HttpStatus.OK);
}
【注意】
第4步中提及,Spring会为接口创建实例化对象,是隐式的创建,只要继承了Data自带的某个仓库接口XXXRepository
,那几乎不用再去管具体实现,因为这些接口Data内部是有这些接口的默认实现的,就比如在自定义接口继承ElasticsearchRepository
时,我们只在里面写了一些抽象方法,具体实现其实是在SimpleElasticsearchRepository
类中实现的。所以如果说不继承Data中任何仓库Repository
,自己写了接口用注解进行标注,那这里的标注应该标在具体实现类上(你需要自己写接口中的实现方法,这简直和MVC中的Server层中的接口和ServiceImpl一模一样,也好理解)。
就像上面所说,自定义仓库接口必须实现Repository
接口或者它的子接口,比如想使用Data为我们提供的
CRUD功能,那就可以直接实现CrudRepository
子接口,然后为这个接口加上格式为<管理的实体类的类型,实体类ID的类型>
的泛型。
由于Data为我们提供了一系列核心仓库Repository
的实现接口,有CrudRepository
和PagingAndSortingRepository
,它们为继承它们的接口提供了一套完备的方法去操作所管理的实体类,我们自定义的仓库接口可以去实现Repository
和CrudRepository
以及PagingAndSortingRepository
中的任意一个接口(或者去实现具体持久化层的Data接口,比如Data ES中提供的ElasticsearchCrudRepository
或者ElasticsearchRepository
),当然,我们也可以不依赖Data提供的上述仓库接口,我就想自己去写仓库接口,然后自己写这个仓库的实现,当然也可以,但是接口的实现类必须使用注解@RepositoryDefinition
进行标注。如果实现Data提供的接口,但需压对已有的方法进行一些定制,那只需要将里面方法复制到自定义的仓库中进行定制即可。下面就是一个自定义的方法的具体实现:
// 自己定制已有方法
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
中间的仓库接口(这里的中间仓库接口是指Data的基层接口Repository
到具体实现或自定义的接口之间所有的接口)都使用了@NoRepositoryBean
注解,一般用作父类的repository(但不包括基层接口Repository
),有这个注解,Spring Data不会在运行时实例化有这个注解的仓库接口repository。
从Spring Data 2.0开始,仓库的CRUD方法就可以使用Java 8中的Optional
表示可能为空的对象,除此之外,Spring Data还支持在查询方法使用以下的包装类包装返回结果:
com.google.common.base.Optional
;scala.Option
;io.vavr.control.Option
;javaslang.control.Option
(不推荐使用);当然,我们也可以不使用上述的包装类,如果对象为空我们直接返回一个null
,仓库方法返回集合、可选集合、包装类以及流的方式确保了仓库方法的返回值永远不会是null
,而是返回相应的空表示,具体参看仓库的返回值类型。
【可空注解】
可以使用Spring Framework的可空性注释来表示仓库方法的可空性约束。它们在运行时提供了一种工具友好的方法和选择性的null
检查,如下所示:
@NonNullApi
:在包级别上使用,以声明参数和返回值的默认行为是不接受或生成null
值的;@NonNull
:用于参数或返回值上,表示该参数或者返回值不能为null
(在已经在包级别上有@NonNullApi
注解的类上中就不需要再用这个注解了);@Nullable
:用于参数或返回值上,表示参数或返回值可以为null
;Spring注解是带有JSR 305的元注解,JSR 305元注解让类似于IDEA、Eclipse以及Kotlin这类工具提供商提了通用的空安全支持,无须对Spring注解具备硬编码支持。为了使查询方法可以在运行时进行空检测,必须使用Sprinpackage-info.java
中的@NonNullApi
在包级别上激活非空性检测,比如下面在package-info.java
中声明不可为空性:
// com.acme包以及它的子包中的方法参数以及返回值都不允许为空!!
@org.springframework.lang.NonNullApi
package com.acme;
【注意】包级别的注解不能像普通java文件那样去创建,因为IDEA中命令是不允许创建的类名含有-
字符,所以需要在外部创建好了package-info.java
文件然后托进来,再加上包级别的注解。
一旦像上述存在非空默认,就会在调用仓库中查询方时(运行时)验证可空性约束,如果一个查询方法的返回结果违反了定义的非空限制,就会抛异常(返回值为空抛出的异常为org.springframework.dao.EmptyResultDataAccessException
,参数为空抛出来的异常为IllegalArgumentException
)。上述注解是对整个包级别而言都不能为空,但如果想选择性的允许个别返回值为空,那可以在个别方法上使用@Nullable
注解。下面是具体的案例使用:
package com.acme;
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress);
// 这个方法的返回值和参数允许为空,其他方法不允许为空(包注解已经整体加了@NonNullApi注解)
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress);
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress);
}
如果像上面使用Optional
来包装返回值,如果没有响应的返回值,那就返回Optional.empty()
。
因为Data是一个大家族,除了Data ES还有其他的模块(比如Data Couchbase、Data JPA等等),在实际开发中可能不仅仅只用到Data中的Data ES模块,还可能有其他的模块,此时不同持久化层的仓库区分开,当Data在类路径上检测到多个仓库时(即在@EnableElasticsearchRepositories
注解上使用basePackages
标明的包路径),Spring Data将进入严格仓库配置模式,严格配置模式使用仓库或域类(即仓库所管理的实体类)的详细信息来确定仓库定义的Spring Data模块绑定:
ElasticsearchRepository
,那Data就会直接将这个自定义接口绑定到Data ES模块上);@Entity
)或提供自己的注释(例如Spring Data MongoDB和Spring Data Elasticsearch的@Document
)。简单的说就仓库接口的域类(所管理的类)上使用的特定的注解,Data就会根据域类的注解将它的仓库接口帮到特定的Data模块上。下面是使用多模块的接口的例子(以JPA为例),说明上述情况中的第一种情况:
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就是有效的绑定模块。
下面是使用通用接口的例子,说明上述绑定情况的第二种:
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
(不像上述第一个例子继承了特定模块的接口),如果项目中只有Data家族中的一个模块,那Data很轻松的就可以将自定义的仓库绑定到那个Data模块上,因为只有这一个模块,但是如果项目中有多个Data模块,那Spring Data就会很懵逼啊,因为上述和两个接口是通用仓库接口,只要是Data家族中的成员都继承了这些通用接口。看下面的例子:
interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
class User {
…
}
PersonRepository
继承的虽然是通用仓库接口Repository
,无法确定绑定的Data模块,但是它管理的是Person
类,Person
类上使用的注解@Entity
是JPA模块的注解,所以Data也很聪明,会将PersonRepository
绑定到Data JPA模块上,同样的UserRepository
管理的实体类User
使用了@Document
注解,可以推断出是Data MongoDB或者Data ES模块,具体看注解内部的属性判断。
【问题】这里我其实有个疑问,如果域类上有多个类型的注解,那这个仓库接口怎么确定绑到哪个模块上呢?
关于上面的疑问,Data也给出了域类混合注解使用情况,这样确实是不被允许的,比如下面的:
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
class Person {
…
}
上面的代码片定义了两个仓库接口JpaPersonRepository
和MongoDBPersonRepository
,域类使用了@Entity
和@Document
注解,分别是JPA和MongoDB的注解,Data将不能将二者区分开,导致未定义的错误。
关于上述提出的疑问,官方给出和解答是:
Using multiple persistence technology-specific annotations on the same domain type is possible and enables reuse of domain types across multiple persistence technologies. However, Spring Data can then no longer determine a unique module with which to bind the repository.
最新的解决方案是将不同持久化仓库接口定义到不同的包中,然后再配置文件中通过不同的@EnableXXXRepositories
注解标出各个持久化仓库的路径,这样就比较明确了,比如:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
repository
定义了2种按名字派生出方法的策略:
Spring Data reppository支持直接按名字解析,但是起的方法名要按照一定的规则来取,解析的时候会剥离以下的套词,然后对剩下的部分进行解析,查询的套词如下:find…By
,read…By
, query…By
, count…By
和get…By
,
反正不管怎样,By
后面就是实际查询条件的开始,一般都是基于实体的属性作条件,条件之间使用And
或者Or来连接,比如findBookByIdAndName
和findBookByNameOrAuth
,除此之外,还可以使用Distinct
设置不同的查询方法标识,下面是官方给出具体查询案例:
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);
}
具体的解析结果和持久化工具有关,但查询这一块也有一些通用的注意点:
And
和Or
来组成表达式,更深层次的还可以使用Between
、LessThan
、GreaterThan
和Like
这样的词组成表达式并获得解析支持,当然具体要看持久化层的支持;IgnoreCase
关键字(这是对实体类的某些属性或者是某类属性而言,通常是指String
类属性),比如findByLastnameIgnoreCase(…)
和findByLastnameAndFirstnameAllIgnoreCase(…)
,和上述的一样,具体还是要看持久化层的支持;OrderByXxx
并提供Asc
或Desc
进行静态排序,关于静态排序,参看特殊参数处理章节; 除了上一小节中简单使用单属性,还可以适用复合属性(即实体类的一个属性是另一个实体,比如Person
有一个带ZipCode
的Address
),那么正常的方法名就写成这样:
List<Person> findByAddressZipCode(ZipCode zipCode);
那么解析算法首先将整个部分(AddressZipCode
)解释为属性,并检查域类中是否具有该名称的属性(未大写),如果解析成功就认为Person中确有这个属性字段,如果没有(即没有解析到AddressZipCode
这个属性),解析算法将来自右侧的驼峰案例部分的源分成头部和尾部并试图找到相应的属性(这个案例中就是分隔为AddressZip
和Code
,注意这里是分为头部和尾部2个部分!所以不存在Address
和Zip
以及Code
这样的分隔情况,另外是从右侧开始分隔,所以不会出现Address
和ZipCode
这样的情况,从右侧向左侧移,发现驼峰搞一个出来,剩下的就是另一个了),如果使用头部(Code
)在Person
类中找到了对应属性,那继续采取尾部并继续从那里建造分隔,按照刚刚描述的方式将尾巴分开。上述是第一次分隔,如果第一次的分割不匹配,算法会将分割点移动到左侧(Address
,ZipCode
)并继续上述的过程。
上面的场景说明了Data解析算法的流程,虽然这个解析算法可以应对大多数的场景,但是如果Person
类中刚好还有一个addressZip
属性呢,那此时这个解析算法解析的第一轮时候就会匹配,然后失败(第一次分隔就可以匹配上addressZip
,但可能没有code
这个属性)。为了解决上述歧义的问题,可以在方法名中使用_
手动定义遍历点,此时的方法名应该为:
// 认为指定分隔为Address和ZipCode
List<Person> findByAddress_ZipCode(ZipCode zipCode);
之前已经有部分参数的设置,除此之外,ES Data还可识别某些特定类型参数,如使用Pageable
和Sort
动态进行分页或排序,如果需要分页,那直接在对应的方法中丢一个Pageable对象进去,排序也是如此,比如:
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);
查询方法的结果可以通过使用first
或top
关键字来限制,这些关键字可以互换使用。可选的数值可以附加到top
或first
后面,以指定要返回的结果的前几个数据,比如下面的:
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);
这些限制表达式也支持Distinct
使用,此外,对于将结果集限制为一个实例的查询,支持使用Optional
关键字将结果进行包装。
可以使用Java 8的Stream
作为返回类型以递增方式处理查询方法的结果,而不是将查询结果包装在Stream中,数据存储特定的方法用于执行流式处理,如以下例所示:
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
这里需要注意的是Stream
可能包装着特定持久化层的资源,所以用完必须关闭,可以手动调用Stream.close()
,或者使用try-catch
块:
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
【注意】不是所有的Spring Data模块都支持结果流式化的。
Repository可以使用Spring异步方法的执行能力进行异步查询,这意味着该方法在调用时立即返回,而实际的查询执行命令以task
的形式提交给Spring的TaskExecutor
任务中。异步式查询不能和响应式查询(即不管怎样,一定要返回一个结果)混用,下面是一些异步查询的案例:
// 返回结果为java.util.concurrent.Future类型
@Async
Future<User> findByFirstname(String firstname);
// 返回结果为java.util.concurrent.CompletableFuture类型
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
// 返回结果为org.springframework.util.concurrent.ListenableFuture类型
@Async
ListenableFuture<User> findOneByLastname(String lastname);
Repository
实例 可以通过XML和JavaConfig配置,后者是较为推荐的配置方式,使用Spring Data的Repository
配置简单如下:
@Configuration
// 扫描基类包,实际根据使用的Repositories进行更换,比如ES使用的是@EnableElasticsearchRepositories
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
// ES的配置是
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.glodon.repositories")
public interface ESConfig {
// ...
}
Repository
接口自定义自用的Repository
接口当查询的方法需要不同的行为或无法通过自带查询派生实现时,就需要提供一个自定义实现。Spring Data repositories允许我们自定义repository,并将其与通用CRUD抽象和查询方法功能集成。我们可以自定义接口,完全不依赖Spring Data,比如:
// 自定义的接口
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
// 实现自定义接口中的具体方法
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
除此之外,还可以自定义接口,并实现原来Data自带的Repository
接口和自定义的接口,这样可以不断加强我们的接口能力,比如:
// CrudRepository是Data自带的
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
然后可以不断自定义接口,添加到最后的接口中,这样最终的接口就会有丰富的能力:
// 接口1
interface HumanRepository {
void someHumanMethod(User user);
}
// 实现1
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
// 接口2
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
// 实现2
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
// 最终的使用的Repository接口
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
【注意】 我们自定义的接口优先级是高于Data自带的基本级别的Repository
,这样可以让我们可以覆盖掉原来自带的Repository
里面的某些方法。
目前官方给出的版本搭配为:
spring data elasticsearch | elasticsearch |
---|---|
3.1.x | 6.2.2 |
3.0.x | 5.5.0 |
2.1.x | 2.4.0 |
2.0.x | 2.2.0 |
1.3.x | 1.5.2 |
基于JavaConfig的配置方式repositories
:
@Configuration
@EnableElasticsearchRepositories(basePackages = "org/springframework/data/elasticsearch/repositories")
static class Config {
@Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
}
}
上述配置了内嵌了ES Server,它是被ES模板ElasticsearchTemplate使用的,使用@EnableElasticsearchRepositories
注解可以激活Spring Data ES中的Repositories,如果这里不指定基类包,那么它将会扫描该配置文件所在目录。
除此之外,还可以使用CDI设置Spring Data es的repositories
,比如下面的:
class ElasticsearchTemplateProducer {
@Produces
@ApplicationScoped
public ElasticsearchOperations createElasticsearchTemplate() {
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
}
}
class ProductService {
private ProductRepository repository;
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}
@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}
查询策略,Data ES支持所有的基本查询功能,如String、Abstract、Criteria,从方法名派生的查询并不总是高效的,And
和Or
可能导致不可读的方法名称,这种场景下可以尝试一下@Query
注解。查询时基本和Data的查询大差不差,比如:
public interface BookRepository extends Repository<Book, String>
{
List<Book> findByNameAndPrice(String name, Integer price);
}
上面的findByNameAndPrice
这个方法将会被Data ES翻译成下面的json查询(bool
查询):
{ "bool" :
{ "must" :
[
{ "field" : {"name" : "?"} },
{ "field" : {"price" : "?"} }
]
}
}
使用@Query
注解的,可以写成这样:
public interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
Page<Book> findByName(String name,Pageable pageable);
}
其中方法中ES可以支持的关键字有:
关键字 | 小栗子 | ES查询语句 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
无法通过repository
接口直接访问ES进行的操作,建议作为自定义repository
中一部分自己去实现。
过滤器可以加快查询速度:
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可以为块中大数据量的结果集提供扫描和滚动功能,ElasticsearchTemplate
也有扫描和滚动方法,比如下面的:
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;
}
}
}