此版本仍处于开发阶段,尚未被视为稳定版本。如需使用最新稳定版本,请采用 Spring Data Couchbase 6.0.4spring-doc.cadn.net.cn

Couchbase 存储库

Spring Data 仓库抽象的目标是显著减少为各种持久化存储实现数据访问层所需的样板代码量。spring-doc.cadn.net.cn

默认情况下,如果操作是单文档操作且已知 ID,则操作由键/值存储支持。 对于所有其他操作,默认会生成 N1QL 查询,因此必须创建适当的索引以实现高效的数据访问。spring-doc.cadn.net.cn

请注意,您可以为查询调整所需的强一致性(参见 使用一致性进行查询),并且可以使用不同的存储桶支持不同的仓库(参见 [couchbase.repository.multibucket]spring-doc.cadn.net.cn

配置

虽然对仓库的支持始终存在,但您需要在一般层面或针对特定命名空间启用它们。 如果您扩展了 AbstractCouchbaseConfiguration,只需使用 @EnableCouchbaseRepositories 注解即可。 它提供了多种选项来缩小或自定义搜索路径,其中最常见的一种是 basePackagesspring-doc.cadn.net.cn

另外请注意,如果您在 Spring Boot 内部运行,自动配置支持已经为您设置了该注解,因此只有在需要覆盖默认值时才需要使用它。spring-doc.cadn.net.cn

示例 1. 基于注解的仓储配置
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.couchbase.example.repos"})
public class Config extends AbstractCouchbaseConfiguration {
    //...
}

QueryDSL 配置

Spring Data Couchbase 支持 QueryDSL 以构建类型安全的查询。要启用代码生成,需要将 CouchbaseAnnotationProcessor 设置为注解处理器。 此外,运行时需要 querydsl-apt 才能在存储库上启用 QueryDSL。spring-doc.cadn.net.cn

示例 2. Maven 配置示例
    . existing depdendencies including those required for spring-data-couchbase
    .
    .
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>${querydslVersion}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <id>annotation-processing</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <proc>only</proc>
                            <annotationProcessors>
                                <annotationProcessor>org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor</annotationProcessor>
                            </annotationProcessors>
                            <generatedTestSourcesDirectory>target/generated-sources</generatedTestSourcesDirectory>
                            <compilerArgs>
                                <arg>-Aquerydsl.logInfo=true</arg>
                            </compilerArgs>
                        </configuration>
                    </execution>
                </executions>
        </plugin>
    </plugins>
</build>
示例 3. Gradle 配置示例
dependencies {
    annotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}'
    annotationProcessor 'org.springframework.data:spring-data-couchbase'
    testAnnotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}'
    testAnnotationProcessor 'org.springframework.data:spring-data-couchbase'
}
tasks.withType(JavaCompile).configureEach {
    options.compilerArgs += [
            "-processor",
            "org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor"]
}

用法

在最简单的情况下,你的仓库将继承 CrudRepository<T, String>,其中 T 是你想要暴露的实体。 让我们来看一个 UserInfo 的仓库示例:spring-doc.cadn.net.cn

示例 4. 一个 UserInfo 仓库
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<UserInfo, String> {
}

请注意,这仅仅是一个接口而非实际类。 在后台,当您的上下文被初始化时,会为您的仓库描述创建实际的实现类,并且您可以通过常规 Bean 访问它们。 这意味着您将在向服务层和应用程序暴露完整 CRUD 语义的同时,节省大量样板代码。spring-doc.cadn.net.cn

现在,让我们假设我们将@Autowire注入到一个使用它的类中。 我们有哪些可用的方法?spring-doc.cadn.net.cn

表 1. UserRepository 上暴露的方法
方法 描述

UserInfo save(UserInfo entity)spring-doc.cadn.net.cn

保存给定的实体。spring-doc.cadn.net.cn

保存 (Iterable<UserInfo> entity)spring-doc.cadn.net.cn

保存实体列表。spring-doc.cadn.net.cn

UserInfo findOne(String id)spring-doc.cadn.net.cn

通过其唯一 ID 查找实体。spring-doc.cadn.net.cn

boolean exists(String id)spring-doc.cadn.net.cn

通过其唯一 ID 检查给定实体是否存在。spring-doc.cadn.net.cn

findAll() 获取所有用户信息spring-doc.cadn.net.cn

在存储桶中查找该类型的所有实体。spring-doc.cadn.net.cn

Iterable<UserInfo> findAll(Iterable<String> ids)spring-doc.cadn.net.cn

通过此类型和给定的 ID 列表查找所有实体。spring-doc.cadn.net.cn

long count()spring-doc.cadn.net.cn

统计桶中的实体数量。spring-doc.cadn.net.cn

void delete(String id)spring-doc.cadn.net.cn

根据其 ID 删除实体。spring-doc.cadn.net.cn

void delete(UserInfo 实体)spring-doc.cadn.net.cn

删除实体。spring-doc.cadn.net.cn

void delete(Iterable<UserInfo> 实体)spring-doc.cadn.net.cn

删除所有给定的实体。spring-doc.cadn.net.cn

void deleteAll()spring-doc.cadn.net.cn

按类型删除存储桶中的所有实体。spring-doc.cadn.net.cn

ow! Just by defining an interface we get full CRUD functionality on top of our managed entity.spring-doc.cadn.net.cn

虽然暴露的方法为您提供多种访问模式,但您经常需要定义自定义模式。 您可以通过在接口中添加方法声明来实现这一点,这些声明将在后台自动解析为请求,正如我们将在下一节中看到的那样。spring-doc.cadn.net.cn

仓库与查询

基于 N1QL 的查询

前提条件是在存储实体的桶上创建主索引。spring-doc.cadn.net.cn

这是一个示例:spring-doc.cadn.net.cn

示例 5. 一个使用 N1QL 查询扩展的 UserInfo 仓库
public interface UserRepository extends CrudRepository<UserInfo, String> {

    @Query("#{#n1ql.selectEntity} WHERE role = 'admin' AND #{#n1ql.filter}")
    List<UserInfo> findAllAdmins();

    List<UserInfo> findByFirstname(String fname);
}

在这里我们看到了两种基于 N1QL 的查询方式。spring-doc.cadn.net.cn

第一种方法使用 Query 注解来提供内联的 N1QL 语句。 通过用 #{} 包围 SpEL 表达式块,支持 SpEL(Spring 表达式语言)。 一些 N1QL 特定的值通过 SpEL 提供:spring-doc.cadn.net.cn

  • #n1ql.selectEntity 允许轻松确保该语句会选择构建完整实体所需的所有字段(包括文档 ID 和 CAS 值)。spring-doc.cadn.net.cn

  • #n1ql.filter 在 WHERE 子句中会添加一个条件,用于匹配实体类型以及 Spring Data 用于存储类型信息的字段。spring-doc.cadn.net.cn

  • #n1ql.bucket 将被替换为存储该实体的桶的名称,并使用反引号进行转义。spring-doc.cadn.net.cn

  • #n1ql.scope 将被替换为实体存储的命名空间名称,并使用反引号进行转义。spring-doc.cadn.net.cn

  • #n1ql.collection 将被替换为实体存储所在的集合名称,并使用反引号进行转义。spring-doc.cadn.net.cn

  • #n1ql.fields 将被替换为重建实体所需的字段列表(例如,用于 SELECT 子句)。spring-doc.cadn.net.cn

  • #n1ql.delete 将被 delete from 语句替换。spring-doc.cadn.net.cn

  • #n1ql.returning 将被替换为用于重构实体所需的返回子句。spring-doc.cadn.net.cn

我们建议您始终使用 selectEntity SpEL 和带有 filter SpEL 的 WHERE 子句(否则您的查询可能会受到来自其他存储库的实体的影响)。

基于字符串的查询支持参数化查询。 您可以使用位置占位符(如"$1"),此时每个方法参数将按顺序映射到 $1$2$3…;或者,您也可以使用"$someString"语法的命名占位符。 方法参数将通过其名称与相应的占位符进行匹配,该名称可以通过为每个参数(PageableSort 除外)添加 @Param 注解来覆盖(例如:@Param("someString"))。 您不能在查询中混合使用这两种方法,否则将收到 IllegalArgumentExceptionspring-doc.cadn.net.cn

请注意,您可以混合使用 N1QL 占位符和 SpEL。N1QL 占位符仍会考虑所有方法参数,因此请务必像以下示例那样使用正确的索引:spring-doc.cadn.net.cn

示例 6. 混合使用 SpEL 和 N1QL 占位符的内联查询
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
public List<User> findUsersByDynamicCriteria(String criteriaField, Object criteriaValue)

这允许您通过单个方法声明生成类似于 AND name = "someName"AND age = 3 的查询。spring-doc.cadn.net.cn

您还可以在 N1QL 查询中执行单字段投影(前提是该查询仅选择一个字段并仅返回一个结果,通常是聚合值,如 COUNTAVGMAX…​)。 此类投影将具有简单的返回类型,例如 longbooleanString。 这 适用于投影到 DTO。spring-doc.cadn.net.cn

另一个示例:
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
等同于
SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1spring-doc.cadn.net.cn

SpEL 与 Spring Security 的实际应用

当您希望根据其他 Spring 组件(如 Spring Security)注入的数据执行查询时,SpEL 会非常有用。 以下是扩展 SpEL 上下文以访问此类外部数据所需执行的步骤。spring-doc.cadn.net.cn

首先,您需要实现一个 EvaluationContextExtension(使用如下支持类):spring-doc.cadn.net.cn

class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {

  @Override
  public String getExtensionId() {
    return "security";
  }

  @Override
  public SecurityExpressionRoot getRootObject() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return new SecurityExpressionRoot(authentication) {};
  }
}

那么,为了让 Spring Data Couchbase 能够访问关联的 SpEL 值,您只需在配置中声明一个对应的 Bean:spring-doc.cadn.net.cn

@Bean
EvaluationContextExtension securityExtension() {
    return new SecurityEvaluationContextExtension();
}

例如,这可用于根据已连接用户的角色来构建查询:spring-doc.cadn.net.cn

@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND " +
"role = '?#{hasRole('ROLE_ADMIN') ? 'public_admin' : 'admin'}'")
List<UserInfo> findAllAdmins(); //only ROLE_ADMIN users will see hidden admins

删除查询示例:spring-doc.cadn.net.cn

@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND " +
"username = $1 #{#n1ql.returning}")
UserInfo removeUser(String username);

第二种方法使用 Spring Data 的查询派生机制,根据方法名和参数构建 N1QL 查询。 这将生成如下所示的查询:SELECT …​ FROM …​ WHERE firstName = "valueOfFnameAtRuntime"。 您可以组合这些条件,甚至可以使用类似 countByFirstname 的方法名进行计数,或使用类似 findFirst3ByLastname 的方法名进行限制……spring-doc.cadn.net.cn

实际上,生成的 N1QL 查询还将包含额外的 N1QL 条件,以便仅选择与存储库实体类匹配的文档。

支持大多数 Spring-Data 关键字: .在 @Query (N1QL) 方法名称中支持的关键字spring-doc.cadn.net.cn

关键字 示例 N1QL WHERE 子句片段

Andspring-doc.cadn.net.cn

findByLastnameAndFirstnamespring-doc.cadn.net.cn

lastName = a AND firstName = bspring-doc.cadn.net.cn

Orspring-doc.cadn.net.cn

findByLastnameOrFirstnamespring-doc.cadn.net.cn

lastName = a OR firstName = bspring-doc.cadn.net.cn

Is,Equalsspring-doc.cadn.net.cn

findByField,findByFieldEqualsspring-doc.cadn.net.cn

field = aspring-doc.cadn.net.cn

IsNot,Notspring-doc.cadn.net.cn

findByFieldIsNotspring-doc.cadn.net.cn

field != aspring-doc.cadn.net.cn

Betweenspring-doc.cadn.net.cn

findByFieldBetweenspring-doc.cadn.net.cn

field BETWEEN a AND bspring-doc.cadn.net.cn

IsLessThan,LessThan,IsBefore,Beforespring-doc.cadn.net.cn

findByFieldIsLessThan,findByFieldBeforespring-doc.cadn.net.cn

field < aspring-doc.cadn.net.cn

IsLessThanEqual,LessThanEqualspring-doc.cadn.net.cn

findByFieldIsLessThanEqualspring-doc.cadn.net.cn

field ⇐ aspring-doc.cadn.net.cn

IsGreaterThan,GreaterThan,IsAfter,Afterspring-doc.cadn.net.cn

findByFieldIsGreaterThan,findByFieldAfterspring-doc.cadn.net.cn

field > aspring-doc.cadn.net.cn

IsGreaterThanEqual,GreaterThanEqualspring-doc.cadn.net.cn

findByFieldGreaterThanEqualspring-doc.cadn.net.cn

field >= aspring-doc.cadn.net.cn

IsNullspring-doc.cadn.net.cn

findByFieldIsNullspring-doc.cadn.net.cn

field IS NULLspring-doc.cadn.net.cn

IsNotNull,NotNullspring-doc.cadn.net.cn

findByFieldIsNotNullspring-doc.cadn.net.cn

field IS NOT NULLspring-doc.cadn.net.cn

IsLike,Likespring-doc.cadn.net.cn

findByFieldLikespring-doc.cadn.net.cn

field LIKE "a" - a 应该是一个包含 % 和 _ 的字符串(分别匹配任意单个字符和任意长度的字符序列)spring-doc.cadn.net.cn

IsNotLike,NotLikespring-doc.cadn.net.cn

findByFieldNotLikespring-doc.cadn.net.cn

field NOT LIKE "a" - a 应该是一个包含 % 和 _ 的字符串(分别匹配任意单个字符和任意长度的字符序列)spring-doc.cadn.net.cn

IsStartingWith,StartingWith,StartsWithspring-doc.cadn.net.cn

findByFieldStartingWithspring-doc.cadn.net.cn

field LIKE "a%" - a 应该是一个字符串前缀spring-doc.cadn.net.cn

IsEndingWith,EndingWith,EndsWithspring-doc.cadn.net.cn

findByFieldEndingWithspring-doc.cadn.net.cn

field LIKE "%a" - a 应该是一个字符串后缀spring-doc.cadn.net.cn

IsContaining,Containing,Containsspring-doc.cadn.net.cn

findByFieldContainsspring-doc.cadn.net.cn

field LIKE "%a%" - a 应该是一个字符串spring-doc.cadn.net.cn

IsNotContaining,NotContaining,NotContainsspring-doc.cadn.net.cn

findByFieldNotContainingspring-doc.cadn.net.cn

field NOT LIKE "%a%" - a 应该是一个字符串spring-doc.cadn.net.cn

IsIn,Inspring-doc.cadn.net.cn

findByFieldInspring-doc.cadn.net.cn

field IN array - 注意,下一个参数值(如果是集合/数组,则为其子元素)必须兼容存储于 JsonArray 中)spring-doc.cadn.net.cn

IsNotIn,NotInspring-doc.cadn.net.cn

findByFieldNotInspring-doc.cadn.net.cn

field NOT IN array - 注意,下一个参数值(如果是集合/数组,则为其子元素)必须兼容存储于 JsonArray 中)spring-doc.cadn.net.cn

IsTrue,Truespring-doc.cadn.net.cn

findByFieldIsTruespring-doc.cadn.net.cn

field = TRUEspring-doc.cadn.net.cn

IsFalse,Falsespring-doc.cadn.net.cn

findByFieldFalsespring-doc.cadn.net.cn

field = FALSEspring-doc.cadn.net.cn

MatchesRegex,Matches,Regexspring-doc.cadn.net.cn

findByFieldMatchesspring-doc.cadn.net.cn

REGEXP_LIKE(field, "a") - 请注意,此处 ignoreCase 被忽略,a 是以字符串形式表示的正则表达式spring-doc.cadn.net.cn

Existsspring-doc.cadn.net.cn

findByFieldExistsspring-doc.cadn.net.cn

field IS NOT MISSING - 用于验证 JSON 是否包含此属性spring-doc.cadn.net.cn

OrderByspring-doc.cadn.net.cn

findByFieldOrderByLastnameDescspring-doc.cadn.net.cn

field = a ORDER BY lastname DESCspring-doc.cadn.net.cn

IgnoreCasespring-doc.cadn.net.cn

findByFieldIgnoreCasespring-doc.cadn.net.cn

LOWER(field) = LOWER("a") - a 必须是字符串spring-doc.cadn.net.cn

您可以将此方法与计数查询和 [repositories.limit-query-result] 功能结合使用。spring-doc.cadn.net.cn

使用 N1QL,存储库的另一个可能接口是 PagingAndSortingRepository(它扩展了 CrudRepository)。 它添加了两个方法:spring-doc.cadn.net.cn

表 2. PagingAndSortingRepository 暴露的方法
方法 描述

Iterable<T> findAll(排序 sort);spring-doc.cadn.net.cn

允许在按其中一个属性排序时检索所有相关实体。spring-doc.cadn.net.cn

Page<T> findAll(Pageable pageable);spring-doc.cadn.net.cn

允许以分页形式检索您的实体。返回的 Page 可轻松获取下一页的 Pageable 以及项目列表。首次调用时,请使用 new PageRequest(0, pageSize) 作为 Pageable。spring-doc.cadn.net.cn

您还可以在基于 N1QL 的存储库中将 PageSlice 用作方法返回类型。
如果在内联查询中使用了分页和排序参数,则内联查询本身不应包含任何 order by、limit 或 offset 子句,否则服务器会将该查询拒绝为格式错误。

自动索引管理

默认情况下,期望用户为其查询创建并管理最优索引。特别是在开发的早期阶段,自动创建索引可能非常有用,以便快速启动。spring-doc.cadn.net.cn

对于 N1QL,提供了以下注解,需要将其附加到实体上(类或字段均可):spring-doc.cadn.net.cn

  • @QueryIndexed: 放置在字段上,以表示该字段应作为索引的一部分spring-doc.cadn.net.cn

  • @CompositeQueryIndex: 放置在类上,表示应创建包含多个字段(复合)的索引。spring-doc.cadn.net.cn

  • @CompositeQueryIndexes:如果应创建多个 CompositeQueryIndex,此注解将接受它们的列表。spring-doc.cadn.net.cn

例如,以下是如何在实体上定义复合索引的方法:spring-doc.cadn.net.cn

示例 7:在两个字段上创建带排序的复合索引
@Document
@CompositeQueryIndex(fields = {"id", "name desc"})
public class Airline {
   @Id
   String id;

	@QueryIndexed
	String name;

	@PersistenceConstructor
	public Airline(String id, String name) {
		this.id = id;
	}

	public String getId() {
		return id;
	}

	public String getName() {
		return name;
	}

}

默认情况下,索引创建是禁用的。如果您想启用它,需要在配置中进行覆盖:spring-doc.cadn.net.cn

示例 8. 启用自动索引创建
@Override
protected boolean autoIndexCreation() {
 return true;
}

使用一致性进行查询

默认情况下,使用 N1QL 的仓库查询采用 NOT_BOUNDED 扫描一致性。这意味着结果返回迅速,但索引中的数据可能尚未包含先前写入操作的数据(称为最终一致性)。如果您需要查询具备“读己之所写”的语义,则需要使用 @ScanConsistency 注解。示例如下:spring-doc.cadn.net.cn

示例 9. 使用不同的扫描一致性
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {

	@Override
	@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
	Iterable<Airport> findAll();

}

DTO 投影

Spring Data 仓库在使用查询方法时通常返回领域模型。 然而,有时您可能需要出于各种原因更改该模型的视图。 在本节中,您将学习如何定义投影,以提供资源的简化和精简视图。spring-doc.cadn.net.cn

请看以下的领域模型:spring-doc.cadn.net.cn

@Entity
public class Person {

  @Id @GeneratedValue
  private Long id;
  private String firstName, lastName;

  @OneToOne
  private Address address;
  …
}

@Entity
public class Address {

  @Id @GeneratedValue
  private Long id;
  private String street, state, country;

  …
}

Person 具有多个属性:spring-doc.cadn.net.cn

现在假设我们创建了一个对应的仓库,如下所示:spring-doc.cadn.net.cn

interface PersonRepository extends CrudRepository<Person, Long> {

  Person findPersonByFirstName(String firstName);
}

Spring Data 将返回包含其所有属性的领域对象。 若只需检索 address 属性,有两种可选方案。 其中一种方案是为 Address 对象定义如下仓库:spring-doc.cadn.net.cn

interface AddressRepository extends CrudRepository<Address, Long> {}

在这种情况下,使用 PersonRepository 仍将返回整个 Person 对象。 使用 AddressRepository 将仅返回 Addressspring-doc.cadn.net.cn

然而,如果您根本不想暴露 address 的详细信息怎么办? 您可以通过定义一个或多个投影(projections),为仓库服务的使用者提供替代方案。spring-doc.cadn.net.cn

示例 10. 简单投影
interface NoAddresses {  (1)

  String getFirstName(); (2)

  String getLastName();  (3)
}

此投影具有以下详细信息:spring-doc.cadn.net.cn

1 一个纯 Java 接口,使其具有声明式特性。
2 导出 firstName
3 导出 lastName

NoAddresses 投影仅包含 firstNamelastName 的 getter 方法,这意味着它不会提供任何地址信息。 在此情况下,查询方法定义返回的是 NoAdresses 而不是 Personspring-doc.cadn.net.cn

interface PersonRepository extends CrudRepository<Person, Long> {

  NoAddresses findByFirstName(String firstName);
}

投影声明了底层类型与暴露属性相关的方法签名之间的契约。 因此,必须根据底层类型的属性名称来命名 getter 方法。 如果底层属性名为 firstName,则 getter 方法必须命名为 getFirstName,否则 Spring Data 将无法查找源属性。spring-doc.cadn.net.cn