Couchbase repositories
Spring Data 存储库抽象的目标是显著减少针对各种持久性存储实现数据访问层所需的样板代码量。
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
默认情况下,如果操作是单文档操作并且 ID 已知,则操作由键/值支持。对于其他所有操作,默认情况下会生成 N1QL 查询,因此必须创建适当的索引以实现高性能数据访问。
By default, operations are backed by Key/Value if they are single-document operations and the ID is known. For all other operations by default N1QL queries are generated, and as a result proper indexes must be created for performant data access.
请注意,你可以调整你查询所需的相容性(请参阅Querying with consistency),并且可以使用不同的存储区支持不同的存储库(请参阅[couchbase.repository.multibucket])。
Note that you can tune the consistency you want for your queries (see Querying with consistency) and have different repositories backed by different buckets (see [couchbase.repository.multibucket])
Configuration
虽然始终提供对存储库的支持,但您需要在通用情况下或对特定命名空间启用它们。如果您扩展 AbstractCouchbaseConfiguration
,只需使用 @EnableCouchbaseRepositories
注释。它提供了许多可能的选项来缩小或自定义搜索路径,最常见的一个选项是 basePackages
。
While support for repositories is always present, you need to enable them in general or for a specific namespace.
If you extend AbstractCouchbaseConfiguration
, just use the @EnableCouchbaseRepositories
annotation.
It provides lots of possible options to narrow or customize the search path, one of the most common ones is basePackages
.
还要注意,如果您在 Spring 启动程序内部运行,则自动配置支持已为您设置了注释,因此您只需在需要覆盖默认值时使用它。
Also note that if you are running inside spring boot, the autoconfig support already sets up the annotation for you so you only need to use it if you want to override the defaults.
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.couchbase.example.repos"})
public class Config extends AbstractCouchbaseConfiguration {
//...
}
高级用法在 [couchbase.repository.multibucket] 中有所描述。
An advanced usage is described in [couchbase.repository.multibucket].
QueryDSL Configuration
Spring Data Couchbase 支持 QueryDSL 来构建类型安全的查询。要启用代码生成,您需要在项目中将 spring-data-couchbase
设置为注释处理器。
Spring Data Couchbase supports QueryDSL for building type-safe queries. To enable code generation you need to set spring-data-couchbase
as annotation processor on your project.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>[compiler-plugin-version]</version>
<configuration>
<annotationProcessorPaths>
<!-- path to the annotation processor -->
<path>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>[version]</version>
</path>
<path>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>[version]</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
annotationProcessor 'com.querydsl:querydsl-apt:${querydslVersion}'
annotationProcessor 'org.springframework.data:spring-data-couchbase:${springDataCouchbaseVersion}'
Usage
在最简单的情况下,您的存储库将扩展 CrudRepository<T, String>
, 其中 T 是您想要公开的实体。让我们看一下 UserInfo 的存储库:
In the simplest case, your repository will extend the CrudRepository<T, String>
, where T is the entity that you want to expose.
Let’s look at a repository for a UserInfo:
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<UserInfo, String> {
}
请注意,这只是一个接口,而不是一个实际的类。在后台,当您的上下文得到初始化时,将会创建您存储库描述的实际实现,您可以通过常规 bean 访问它们。这意味着您将节省大量的样板代码,同时仍向您的服务层和应用程序公开完整的 CRUD 语义。
Please note that this is just an interface and not an actual class. In the background, when your context gets initialized, actual implementations for your repository descriptions get created and you can access them through regular beans. This means you will save lots of boilerplate code while still exposing full CRUD semantics to your service layer and application.
现在,让我们假设我们将 UserRepository
@Autowire
到一个使用它的类。我们有哪些可用方法呢?
Now, let’s imagine we @Autowire
the UserRepository
to a class that makes use of it.
What methods do we have available?
Method | Description |
---|---|
UserInfo save(UserInfo entity) |
Save the given entity. |
Iterable<UserInfo> save(Iterable<UserInfo> entity) |
Save the list of entities. |
UserInfo findOne(String id) |
Find a entity by its unique id. |
boolean exists(String id) |
Check if a given entity exists by its unique id. |
Iterable<UserInfo> findAll() |
Find all entities by this type in the bucket. |
Iterable<UserInfo> findAll(Iterable<String> ids) |
Find all entities by this type and the given list of ids. |
long count() |
Count the number of entities in the bucket. |
void delete(String id) |
Delete the entity by its id. |
void delete(UserInfo entity) |
Delete the entity. |
void delete(Iterable<UserInfo> entities) |
Delete all given entities. |
void deleteAll() |
Delete all entities by type in the bucket. |
这太酷了!只需定义一个接口,我们就可以在托管实体之上获得完整的 CRUD 功能。
Now that’s awesome! Just by defining an interface we get full CRUD functionality on top of our managed entity.
虽然公开的方法为您提供了各种访问模式,但您常常需要定义自定义模式。您可以通过向您的接口添加方法声明来执行此操作,这些方法声明将在后台自动解析为请求,正如我们将在下一节中看到的。
While the exposed methods provide you with a great variety of access patterns, very often you need to define custom ones. You can do this by adding method declarations to your interface, which will be automatically resolved to requests in the background, as we’ll see in the next sections.
Repositories and Querying
N1QL based querying
前提条件是在实体将存储到的存储桶上创建了主键索引。
Prerequisite is to have created a PRIMARY INDEX on the bucket where the entities will be stored.
这是一个示例:
Here is an example:
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 支持的两种查询方式。
Here we see two N1QL-backed ways of querying.
第一个方法使用 Query
注释来内联提供 N1QL 语句。 通过将 SpEL 表达式块置于 #{
和 }
之间,支持 SpEL(Spring 表达式语言)。通过 SpEL 提供了一些特定于 N1QL 的值:
The first method uses the Query
annotation to provide a N1QL statement inline.
SpEL (Spring Expression Language) is supported by surrounding SpEL expression blocks between #{
and }
.
A few N1QL-specific values are provided through SpEL:
-
#n1ql.selectEntity
allows to easily make sure the statement will select all the fields necessary to build the full entity (including document ID and CAS value). -
#n1ql.filter
in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information. -
#n1ql.bucket
will be replaced by the name of the bucket the entity is stored in, escaped in backticks. -
#n1ql.scope
will be replaced by the name of the scope the entity is stored in, escaped in backticks. -
#n1ql.collection
will be replaced by the name of the collection the entity is stored in, escaped in backticks. -
#n1ql.fields
will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity. -
#n1ql.delete
will be replaced by thedelete from
statement. -
#n1ql.returning
will be replaced by returning clause needed for reconstructing entity.
我们建议你始终将 selectEntity
SpEL 与 WHERE 子句和 filter
SpEL 结合使用(否则你的查询可能会受到来自其他存储库的实体的影响)。
We recommend that you always use the selectEntity
SpEL and a WHERE clause with a filter
SpEL (since otherwise your query could be impacted by entities from other repositories).
基于字符串的查询支持参数化查询。您可以使用位置占位符(如 “$1”),在这种情况下,每个方法参数将按顺序映射到 $1
、 $2
、 $3
……或者,您可以使用使用 “$someString” 语法的命名占位符。方法参数将使用参数的名称与它们对应的占位符匹配,可以用 @Param
(例如 @Param("someString")
)注释每个参数(Pageable
或 Sort
除外)来覆盖此名称。您不能在查询中混合使用这两种方法,这样做会得到 IllegalArgumentException
。
String-based queries support parametrized queries.
You can either use positional placeholders like “$1”, in which case each of the method parameters will map, in order, to $1
, $2
, $3
… Alternatively, you can use named placeholders using the “$someString” syntax.
Method parameters will be matched with their corresponding placeholder using the parameter’s name, which can be overridden by annotating each parameter (except a Pageable
or Sort
) with @Param
(eg. @Param("someString")
).
You cannot mix the two approaches in your query and will get an IllegalArgumentException
if you do.
请注意,可以混合使用 N1QL 占位符和 SpEL。 N1QL 占位符仍然会考虑所有方法参数,因此请务必使用正确的索引,如下面的示例所示:
Note that you can mix N1QL placeholders and SpEL. N1QL placeholders will still consider all method parameters, so be sure to use the correct index like in the example below:
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
public List<User> findUsersByDynamicCriteria(String criteriaField, Object criteriaValue)
这允许您生成类似于(例如) AND name = "someName"`或 `AND age = 3
的查询,只需一个方法声明。
This allows you to generate queries that would work similarly to eg. AND name = "someName"
or AND age = 3
, with a single method declaration.
您还可以在 N1QL 查询中执行单个投影(前提是它只选择一个字段并且只返回一个结果,通常是聚合,如 COUNT
、 AVG
、 MAX
……)。此类投影将具有简单的返回类型,如 long
、 boolean
或 String
。此方法*不*适用于投影到 DTO。
You can also do single projections in your N1QL queries (provided it selects only one field and returns only one result, usually an aggregation like COUNT
, AVG
, MAX
…).
Such projection would have a simple return type like long
, boolean
or String
.
This is NOT intended for projections to DTOs.
另一个示例:#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
等同于 SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1
Another example:
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
is equivalent to
SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1
当您想要执行取决于其他 Spring 组件(如 Spring Security)注入的数据的查询时,SpEL 会很有用。以下是扩展 SpEL 上下文以获取此类外部数据所需执行的操作。
SpEL can be useful when you want to do a query depending on data injected by other Spring components, like Spring Security. Here is what you need to do to extend the SpEL context to get access to such external data.
首先,你需要实现一个 EvaluationContextExtension
(使用如下的支持类):
First, you need to implement an EvaluationContextExtension
(use the support class as below):
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
现在,所要做的就是声明一个相应的 bean 配置,以使 Spring Data Couchbase 能够访问关联的 SpEL 值:
Then all you need to do for Spring Data Couchbase to be able to access associated SpEL values is to declare a corresponding bean in your configuration:
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
这可以帮助根据已连接用户的角色制作一个查询,例如:
This could be useful to craft a query according to the role of the connected user for instance:
@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
删除查询示例:
Delete query example:
@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
之类的名称限制…
The second method uses Spring-Data’s query derivation mechanism to build a N1QL query from the method name and parameters.
This will produce a query looking like this: SELECT … FROM … WHERE firstName = "valueOfFnameAtRuntime"
.
You can combine these criteria, even do a count with a name like countByFirstname
or a limit with a name like findFirst3ByLastname
…
实际上,生成的 N1QL 查询还将包含一个附加的 N1QL 标准,以便仅选择与存储库实体类匹配的文档。 |
Actually the generated N1QL query will also contain an additional N1QL criteria in order to only select documents that match the repository’s entity class. |
大多数 Spring-Data 关键字均受支持:受支持的关键字包含在 @Query(N1QL)方法名中
Most Spring-Data keywords are supported: .Supported keywords inside @Query (N1QL) method names
Keyword | Sample | N1QL WHERE clause snippet |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
您可以使用此方法同时使用计数查询和 [repositories.limit-query-result] 功能。
You can use both counting queries and [repositories.limit-query-result] features with this approach.
借助 N1QL,存储库的另一种可能的接口是 PagingAndSortingRepository
(它扩展了 CrudRepository
)。它添加了两个方法:
With N1QL, another possible interface for the repository is the PagingAndSortingRepository
one (which extends CrudRepository
).
It adds two methods:
Method | Description |
---|---|
Iterable<T> findAll(Sort sort); |
Allows to retrieve all relevant entities while sorting on one of their attributes. |
Page<T> findAll(Pageable pageable); |
Allows to retrieve your entities in pages. The returned |
你还可以将 |
You can also use |
如果分页和排序参数与内联查询一起使用,则内联查询本身不应该包含任何 order by、limit 或 offset 子句,否则服务器会拒绝该查询,因为它格式不正确。 |
If pageable and sort parameters are used with inline queries, there should not be any order by, limit or offset clause in the inline query itself otherwise the server would reject the query as malformed. |
Automatic Index Management
默认情况下,用户预期创建和管理其查询的最优索引。尤其是在开发的早期阶段,自动创建索引以使其能够快速启动会非常方便。
By default, it is expected that the user creates and manages optimal indexes for their queries. Especially in the early stages of development, it can come in handy to automatically create indexes to get going quickly.
针对 N1QL,提供了以下注释,需要附加到实体上(在类或字段上):
For N1QL, the following annotations are provided which need to be attached to the entity (either on the class or the field):
-
@QueryIndexed
: Placed on a field to signal that this field should be part of the index -
@CompositeQueryIndex
: Placed on the class to signal that an index on more than one field (composite) should be created. -
@CompositeQueryIndexes
: If more than oneCompositeQueryIndex
should be created, this annotation will take a list of them.
例如,这是在实体上定义复合索引的方式:
For example, this is how you define a composite index on an entity:
@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;
}
}
默认情况下,索引创建被禁用。如果您想启用它,您需要在配置中覆盖它:
By default, index creation is disabled. If you want to enable it you need to override it on the configuration:
@Override
protected boolean autoIndexCreation() {
return true;
}
Querying with consistency
默认情况下,使用 N1QL 的存储库查询使用 NOT_BOUNDED
扫描一致性。这意味着结果返回很快,但索引中的数据可能还不包含先前写入操作的数据(称为最终一致性)。如果您需要为查询使用“就绪读自己的写入”语义,您需要使用 @ScanConsistency
注释。以下是一个示例:
By default repository queries that use N1QL use the NOT_BOUNDED
scan consistency. This means that results return quickly, but the data from the index may not yet contain data from previously written operations (called eventual consistency). If you need "ready your own write" semantics for a query, you need to use the @ScanConsistency
annotation. Here is an example:
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Iterable<Airport> findAll();
}
DTO Projections
使用查询方法时,Spring Data 存储库通常返回域模型。但是,有时,您可能需要出于各种原因更改该模型的视图。在本部分,您将学习如何定义投影来提供简化和简略的资源视图。
Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.
查看以下域模型:
Look at the following domain model:
@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
有多个属性:
This Person
has several attributes:
-
id
is the primary key -
firstName
andlastName
are data attributes -
address
is a link to another domain object
现在假设我们按如下方式创建一个相应的存储库:
Now assume we create a corresponding repository as follows:
interface PersonRepository extends CrudRepository<Person, Long> {
Person findPersonByFirstName(String firstName);
}
Spring Data 将返回域对象,包括其所有属性。仅仅获取 address
属性有两种选择。一种选择是为 Address
对象定义一个类似这样的存储库:
Spring Data will return the domain object including all of its attributes.
There are two options just to retrieve the address
attribute.
One option is to define a repository for Address
objects like this:
interface AddressRepository extends CrudRepository<Address, Long> {}
在这种情况中,使用 PersonRepository
仍然会返回整个 Person
对象。使用 AddressRepository
将仅仅返回 Address
。
In this situation, using PersonRepository
will still return the whole Person
object.
Using AddressRepository
will return just the Address
.
但是,如果您完全不想公开 address
详细信息呢?您可以通过定义一个或多个投影,向存储库服务使用者提供替代方案。
However, what if you do not want to expose address
details at all?
You can offer the consumer of your repository service an alternative by defining one or more projections.
interface NoAddresses { 1
String getFirstName(); 2
String getLastName(); 3
}
该投影包含以下详情:
This projection has the following details:
1 | A plain Java interface making it declarative. |
2 | Export the firstName . |
3 | Export the lastName . |
NoAddresses
投影只对 firstName
和 lastName
具有 getter,这意味着它不会提供任何地址信息。在这种情况下,查询方法定义将返回 NoAdresses
,而不是 Person
。
The NoAddresses
projection only has getters for firstName
and lastName
meaning that it will not serve up any address information.
The query method definition returns in this case NoAdresses
instead of Person
.
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
投影声明了底层类型与公开属性相关的签名类型的契约。因此,需要根据底层类型的属性名称来命名 getter 方法。如果底层属性名称为 firstName
,那么 getter 方法必须命名为 getFirstName
,否则 Spring Data 无法查找源属性。
Projections declare a contract between the underlying type and the method signatures related to the exposed properties.
Hence it is required to name getter methods according to the property name of the underlying type.
If the underlying property is named firstName
, then the getter method must be named getFirstName
otherwise Spring Data is not able to look up the source property.