此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Couchbase 5.4.4spring-doc.cadn.net.cn

Couchbase 存储库

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

默认情况下,如果作是单文档作并且 ID 已知,则作由 Key/Value 提供支持。 默认情况下,对于所有其他作,会生成 N1QL 查询,因此必须创建适当的索引才能进行高性能数据访问。spring-doc.cadn.net.cn

请注意,您可以调整查询所需的一致性(请参阅一致查询),并拥有由不同存储桶支持的不同存储库(请参阅 [couchbase.repository.multibucket])spring-doc.cadn.net.cn

配置

虽然始终支持存储库,但您需要在一般情况下启用它们或为特定命名空间启用它们。 如果您扩展AbstractCouchbaseConfiguration,只需使用@EnableCouchbaseRepositories注解。 它提供了许多可能的选项来缩小或自定义搜索路径,最常见的选项之一是basePackages.spring-doc.cadn.net.cn

另请注意,如果您在 Spring Boot 中运行,则 autoconfig 支持已经为您设置了 Comments,因此只有在要覆盖默认值时才需要使用它。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> {
}

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.spring-doc.cadn.net.cn

Now, let’s imagine we @Autowire the UserRepository to a class that makes use of it. What methods do we have available?spring-doc.cadn.net.cn

Table 1. Exposed methods on the UserRepository
Method Description

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

Save the given entity.spring-doc.cadn.net.cn

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

Save the list of entities.spring-doc.cadn.net.cn

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

Find a entity by its unique id.spring-doc.cadn.net.cn

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

Check if a given entity exists by its unique id.spring-doc.cadn.net.cn

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

Find all entities by this type in the bucket.spring-doc.cadn.net.cn

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

Find all entities by this type and the given list of ids.spring-doc.cadn.net.cn

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

Count the number of entities in the bucket.spring-doc.cadn.net.cn

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

Delete the entity by its id.spring-doc.cadn.net.cn

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

Delete the entity.spring-doc.cadn.net.cn

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

Delete all given entities.spring-doc.cadn.net.cn

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

Delete all entities by type in the bucket.spring-doc.cadn.net.cn

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

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.spring-doc.cadn.net.cn

Repositories and Querying

N1QL based querying

Prerequisite is to have created a PRIMARY INDEX on the bucket where the entities will be stored.spring-doc.cadn.net.cn

Here is an example:spring-doc.cadn.net.cn

Example 5. An extended UserInfo repository with N1QL queries
public interface UserRepository extends CrudRepository<UserInfo, String> {

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

    List<UserInfo> findByFirstname(String fname);
}

Here we see two N1QL-backed ways of querying.spring-doc.cadn.net.cn

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:#{}spring-doc.cadn.net.cn

  • #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).spring-doc.cadn.net.cn

  • #n1ql.filter in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information.spring-doc.cadn.net.cn

  • #n1ql.bucket will be replaced by the name of the bucket the entity is stored in, escaped in backticks.spring-doc.cadn.net.cn

  • #n1ql.scope will be replaced by the name of the scope the entity is stored in, escaped in backticks.spring-doc.cadn.net.cn

  • #n1ql.collection will be replaced by the name of the collection the entity is stored in, escaped in backticks.spring-doc.cadn.net.cn

  • #n1ql.fields will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.spring-doc.cadn.net.cn

  • #n1ql.delete will be replaced by the delete from statement.spring-doc.cadn.net.cn

  • #n1ql.returning will be replaced by returning clause needed for reconstructing entity.spring-doc.cadn.net.cn

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).

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.spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

Example 6. An inline query that mixes SpEL and N1QL placeholders
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2")
public List<User> findUsersByDynamicCriteria(String criteriaField, Object criteriaValue)

This allows you to generate queries that would work similarly to eg. AND name = "someName" or AND age = 3, with a single method declaration.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Another example:
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
is equivalent to
SELECT #{#n1ql.fields} FROM #{#n1ql.collection} WHERE #{#n1ql.filter} AND test = $1spring-doc.cadn.net.cn

A practical application of SpEL with Spring Security

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.spring-doc.cadn.net.cn

First, you need to implement an EvaluationContextExtension (use the support class as below):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) {};
  }
}

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:spring-doc.cadn.net.cn

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

This could be useful to craft a query according to the role of the connected user for instance: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

Delete query example:spring-doc.cadn.net.cn

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

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…​spring-doc.cadn.net.cn

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.

Most Spring-Data keywords are supported: .Supported keywords inside @Query (N1QL) method namesspring-doc.cadn.net.cn

Keyword Sample N1QL WHERE clause snippet

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 should be a String containing % and _ (matching n and 1 characters)spring-doc.cadn.net.cn

IsNotLike,NotLikespring-doc.cadn.net.cn

findByFieldNotLikespring-doc.cadn.net.cn

field NOT LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)spring-doc.cadn.net.cn

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

findByFieldStartingWithspring-doc.cadn.net.cn

field LIKE "a%" - a should be a String prefixspring-doc.cadn.net.cn

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

findByFieldEndingWithspring-doc.cadn.net.cn

field LIKE "%a" - a should be a String suffixspring-doc.cadn.net.cn

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

findByFieldContainsspring-doc.cadn.net.cn

field LIKE "%a%" - a should be a Stringspring-doc.cadn.net.cn

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

findByFieldNotContainingspring-doc.cadn.net.cn

field NOT LIKE "%a%" - a should be a Stringspring-doc.cadn.net.cn

IsIn,Inspring-doc.cadn.net.cn

findByFieldInspring-doc.cadn.net.cn

field IN array - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a JsonArray)spring-doc.cadn.net.cn

IsNotIn,NotInspring-doc.cadn.net.cn

findByFieldNotInspring-doc.cadn.net.cn

field NOT IN array - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a 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") - note that the ignoreCase is ignored here, a is a regular expression in String formspring-doc.cadn.net.cn

Existsspring-doc.cadn.net.cn

findByFieldExistsspring-doc.cadn.net.cn

field IS NOT MISSING - used to verify that the JSON contains this attributespring-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 must be a Stringspring-doc.cadn.net.cn

You can use both counting queries and [repositories.limit-query-result] features with this approach.spring-doc.cadn.net.cn

With N1QL, another possible interface for the repository is the PagingAndSortingRepository one (which extends CrudRepository). It adds two methods:spring-doc.cadn.net.cn

Table 2. Exposed methods on the PagingAndSortingRepository
Method Description

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

Allows to retrieve all relevant entities while sorting on one of their attributes.spring-doc.cadn.net.cn

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

Allows to retrieve your entities in pages. The returned Page allows to easily get the next page’s Pageable as well as the list of items. For the first call, use new PageRequest(0, pageSize) as Pageable.spring-doc.cadn.net.cn

You can also use Page and Slice as method return types as well with a N1QL backed repository.
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.spring-doc.cadn.net.cn

For N1QL, the following annotations are provided which need to be attached to the entity (either on the class or the field):spring-doc.cadn.net.cn

  • @QueryIndexed: Placed on a field to signal that this field should be part of the indexspring-doc.cadn.net.cn

  • @CompositeQueryIndex: Placed on the class to signal that an index on more than one field (composite) should be created.spring-doc.cadn.net.cn

  • @CompositeQueryIndexes: If more than one CompositeQueryIndex should be created, this annotation will take a list of them.spring-doc.cadn.net.cn

For example, this is how you define a composite index on an entity:spring-doc.cadn.net.cn

Example 7. Composite index on two fields with ordering
@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:spring-doc.cadn.net.cn

Example 8. Enable auto index creation
@Override
protected boolean autoIndexCreation() {
 return true;
}

Querying with consistency

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:spring-doc.cadn.net.cn

Example 9. Using a different scan consistency
@Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {

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

}

DTO Projections

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.spring-doc.cadn.net.cn

Look at the following domain model: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;

  …
}

This Person has several attributes:spring-doc.cadn.net.cn

Now assume we create a corresponding repository as follows:spring-doc.cadn.net.cn

interface PersonRepository extends CrudRepository<Person, Long> {

  Person findPersonByFirstName(String firstName);
}

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:spring-doc.cadn.net.cn

interface AddressRepository extends CrudRepository<Address, Long> {}

In this situation, using PersonRepository will still return the whole Person object. Using AddressRepository will return just the Address.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Example 10. Simple Projection
interface NoAddresses {  (1)

  String getFirstName(); (2)

  String getLastName();  (3)
}

This projection has the following details:spring-doc.cadn.net.cn

1 A plain Java interface making it declarative.
2 Export the firstName.
3 Export the lastName.

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.spring-doc.cadn.net.cn

interface PersonRepository extends CrudRepository<Person, Long> {

  NoAddresses findByFirstName(String firstName);
}

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.spring-doc.cadn.net.cn