一、简介

介绍 Spring Data Couchbase后,在第二个教程中,我们重点关注对 Couchbase 文档数据库的实体验证 (JSR-303)、乐观锁定和不同级别的查询一致性的支持。

2. 实体验证

Spring Data Couchbase 提供对 JSR-303 实体验证注释的支持。为了利用此功能,首先我们将 JSR-303 库添加到 Maven 项目的依赖项部分:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

然后我们添加 JSR-303 的实现。我们将使用 Hibernate 实现:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.4.Final</version>
</dependency>

最后,我们将验证器工厂 bean 和相应的 Couchbase 事件监听器添加到我们的 Couchbase 配置中:

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
}

@Bean
public ValidatingCouchbaseEventListener validatingCouchbaseEventListener() {
    return new ValidatingCouchbaseEventListener(localValidatorFactoryBean());
}

等效的 XML 配置如下所示:

<bean id="validator"
  class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

<bean id="validatingEventListener" 
  class="org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener"/>

现在我们将 JSR-303 注释添加到实体类中。当持久化操作期间遇到约束冲突时,操作将失败,并抛出 ConstraintViolationException

以下是我们可以对 学生 实体实施的约束示例:

@Field
@NotNull
@Size(min=1, max=20)
@Pattern(regexp="^[a-zA-Z .'-]+$")
private String firstName;

...
@Field
@Past
private DateTime dateOfBirth;

3. 乐观锁

Spring Data Couchbase 不支持类似于在其他 Spring Data 模块(例如 Spring Data JPA)中实现的多文档事务(通过 @Transactional 注释),也不提供回滚功能。

然而,它确实通过使用 @Version 注释以与其他 Spring Data 模块大致相同的方式支持乐观锁定:

@Version
private long version;

在幕后,Couchbase 使用所谓的“比较和交换”(CAS) 机制来实现数据存储级别的乐观锁定。

Couchbase 中的每个文档都有一个关联的 CAS 值,只要文档的元数据或内容发生更改,该值就会自动修改。在字段上使用 @Version 注释会导致每当从 Couchbase 检索文档时都会使用当前 CAS 值填充该字段。

当您尝试将文档保存回 Couchbase 时,系统会根据 Couchbase 中的当前 CAS 值检查此字段。如果值不匹配,持久化操作将失败并出现 OptimisticLockingException

请务必注意,切勿尝试在代码中访问或修改此字段,这一点 非常重要

4. 查询一致性

在 Couchbase 上实现持久层时,您必须考虑过时读取和写入的可能性。这是因为当插入、更新或删除文档时,可能需要一些时间才能更新支持视图和索引以反映这些更改。

如果您有一个由 Couchbase 节点集群支持的大型数据集,这可能会成为一个重大问题,尤其是对于 OLTP 系统。

Spring Data 为某些存储库和模板操作提供了强大的一致性级别,另外还有几个选项可让您确定应用程序可接受的读写一致性级别。

4.1.一致性级别

Spring Data 允许您通过 org.springframework.data.couchbase.core.query 包中的 Consistency 枚举为应用程序指定各种级别的查询一致性和过时性。

该枚举定义了以下查询一致性和陈旧性级别(从最严格到最严格):

  • EVENTUALLY_CONCISTENT
    • 允许陈旧读取
    • 索引根据Couchbase标准算法更新
  • 更新_AFTER
    • 允许陈旧读取
    • 每次请求后都会更新索引
  • DEFAULT_CONSISTENCY (与 READ_YOUR_OWN_WRITES 相同)
  • READ_YOUR_OWN_WRITES
    • 不允许陈旧的读取
    • 每次请求后都会更新索引
  • 强烈_一致
    • 不允许陈旧的读取
    • 索引在每个语句后更新

4.2.默认行为

考虑这样一种情况:您的文档已从 Couchbase 中删除,并且支持视图和索引尚未完全更新。

CouchbaseRepository 内置方法 deleteAll() 安全地忽略后备视图找到但视图尚未反映其删除的文档。

同样, CouchbaseTemplate 内置方法 findByViewfindBySpatialView 通过不返回最初由支持视图找到但后来被删除的文档来提供类似级别的一致性。

对于所有其他模板方法、内置存储库方法和派生存储库查询方法,根据撰写本文时的官方 Spring Data Couchbase 2.1.x 文档,Spring Data 使用默认的一致性级别 Consistency.READ_YOUR_OWN_WRITES。

值得注意的是,该库的早期版本使用默认值 Consistency.UPDATE_AFTER

无论您使用哪个版本,如果您对盲目接受所提供的默认一致性级别有任何保留,Spring 提供了两种方法,您可以通过它们以声明方式控制所使用的一致性级别,如以下小节将描述的。

4.3.全局一致性设置

如果您使用 Couchbase 存储库,并且您的应用程序需要更强的一致性级别,或者可以容忍较弱的一致性级别,那么您可以通过覆盖 Couchbase 配置中的 getDefaultConsistency() 方法来覆盖所有存储库的默认一致性设置。

以下是如何覆盖 Couchbase 配置类中的全局一致性级别:

@Override
public Consistency getDefaultConsistency() {
    return Consistency.STRONGLY_CONSISTENT;
}

以下是等效的 XML 配置:

<couchbase:template consistency="STRONGLY_CONSISTENT"/>

请注意,更严格的一致性级别的代价是查询时的延迟增加,因此请务必根据应用程序的需求定制此设置。

例如,经常仅批量附加或更新数据的数据仓库或报告应用程序是 EVENTUALLY_CONSISTENT 的良好候选者,而 OLTP 应用程序可能应该倾向于更严格的级别,例如 READ_YOUR_OWN_WRITESSTRONGLY_CONSISTENT

4.4.自定义一致性实施

如果您需要更精细调整的一致性设置,您可以通过为您想要独立控制其一致性级别的任何查询提供自己的存储库实现并使用 queryView 和/或 CouchbaseTemplate 提供的 queryN1QL 方法。

让我们为 Student 实体实现一个名为 findByFirstNameStartsWith 的 自定义存储库方法,我们不想允许过时的读取。

首先,创建一个包含自定义方法声明的接口:

public interface CustomStudentRepository {
    List<Student> findByFirstNameStartsWith(String s);
}

接下来,实现该接口,将底层 Couchbase Java SDK 中的 Stale 设置设置为所需的级别:

public class CustomStudentRepositoryImpl implements CustomStudentRepository {

    @Autowired
    private CouchbaseTemplate template;

    public List<Student> findByFirstNameStartsWith(String s) {
        return template.findByView(ViewQuery.from("student", "byFirstName")
          .startKey(s)
          .stale(Stale.FALSE),
          Student.class);
    }
}

最后,通过让标准存储库接口扩展通用 CrudRepository 接口和自定义存储库接口,客户端将可以访问标准存储库接口的所有内置和派生方法,以及您在自定义存储库类中实现的任何自定义方法:

public interface StudentRepository extends CrudRepository<Student, String>,
  CustomStudentRepository {
    ...
}

5. 结论

在本教程中,我们展示了如何在使用 Spring Data Couchbase 社区项目时实现 JSR-303 实体验证并实现乐观锁定功能。

我们还讨论了理解 Couchbase 中查询一致性的必要性,并介绍了 Spring Data Couchbase 提供的不同级别的一致性。

最后,我们解释了 Spring Data Couchbase 全局使用的默认一致性级别以及一些特定方法,并且演示了覆盖全局默认一致性设置的方法以及如何通过提供在逐个查询的基础上覆盖一致性设置您自己的自定义存储库实现。

您可以在GitHub 项目中查看本教程的完整源代码。

要了解有关 Spring Data Couchbase 的更多信息,请访问官方Spring Data Couchbase项目网站。