Using Couchbase in a Spring Application – 在Spring应用程序中使用Couchbase

最后修改: 2016年 7月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this follow-up to our introduction to Couchbase, we create a set of Spring services that can be used together to create a basic persistence layer for a Spring application without the use of Spring Data.

在我们的Couchbase介绍的后续内容中,我们创建了一组Spring服务,这些服务可以一起用于为Spring应用程序创建一个基本的持久层,而无需使用Spring Data。

2. Cluster Service

2.群集服务

In order to satisfy the constraint that only a single CouchbaseEnvironment may be active in the JVM, we begin by writing a service that connects to a Couchbase cluster and provides access to data buckets without directly exposing either the Cluster or CouchbaseEnvironment instances.

为了满足JVM中只能有一个CouchbaseEnvironment处于活动状态的约束,我们首先编写了一个连接到Couchbase集群的服务,并提供对数据桶的访问,而不直接暴露ClusterCouchbaseEnvironment实例。

2.1. Interface

2.1.接口

Here is our ClusterService interface:

这里是我们的ClusterService接口。

public interface ClusterService {
    Bucket openBucket(String name, String password);
}

2.2. Implementation

2.2.实施

Our implementation class instantiates a DefaultCouchbaseEnvironment and connects to a cluster during the @PostConstruct phase during Spring context initialization.

我们的实现类实例化了一个DefaultCouchbaseEnvironment,并在Spring上下文初始化的@PostConstruct阶段连接到一个集群。

This ensures that the cluster is not null and that it is connected when the class is injected into other service classes, thus enabling them to open one or more data buckets:

这确保了集群不是空的,并且当该类被注入到其他服务类时,它是被连接的,从而使它们能够打开一个或多个数据桶。

@Service
public class ClusterServiceImpl implements ClusterService {
    private Cluster cluster;
    
    @PostConstruct
    private void init() {
        CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
        cluster = CouchbaseCluster.create(env, "localhost");
    }
...
}

Next, we provide a ConcurrentHashMap to contain the open buckets and implement the openBucket method:

接下来,我们提供一个ConcurrentHashMap来包含开放的桶,并实现openBucket方法。

private Map<String, Bucket> buckets = new ConcurrentHashMap<>();

@Override
synchronized public Bucket openBucket(String name, String password) {
    if(!buckets.containsKey(name)) {
        Bucket bucket = cluster.openBucket(name, password);
        buckets.put(name, bucket);
    }
    return buckets.get(name);
}

3. Bucket Service

3.水桶服务

Depending on how you architect your application, you may need to provide access to the same data bucket in multiple Spring services.

取决于你如何架构你的应用程序,你可能需要在多个Spring服务中提供对同一数据桶的访问。

If we merely attempted to open the same bucket in two or more services during application startup, the second service to attempt this is likely to encounter a ConcurrentTimeoutException.

如果我们只是在应用程序启动期间尝试在两个或更多的服务中打开同一个桶,那么第二个尝试的服务很可能会遇到ConcurrentTimeoutException

To avoid this scenario, we define a BucketService interface and an implementation class per bucket. Each implementation class acts as a bridge between the ClusterService and the classes that need direct access to a particular Bucket.

为了避免这种情况,我们为每个桶定义一个BucketService接口和一个实现类。每个实现类在ClusterService和需要直接访问特定Bucket的类之间充当桥梁。

3.1. Interface

3.1.接口

Here is our BucketService interface:

这里是我们的BucketService接口。

public interface BucketService {
    Bucket getBucket();
}

3.2. Implementation

3.2.实施

The following class provides access to the “baeldung-tutorial” bucket:

下面的类提供了对”baeldung-tutorial“桶的访问。

@Service
@Qualifier("TutorialBucketService")
public class TutorialBucketService implements BucketService {

    @Autowired
    private ClusterService couchbase;
    
    private Bucket bucket;
    
    @PostConstruct
    private void init() {
        bucket = couchbase.openBucket("baeldung-tutorial", "");
    }

    @Override
    public Bucket getBucket() {
        return bucket;
    }
}

By injecting the ClusterService in our TutorialBucketService implementation class and opening the bucket in a method annotated with @PostConstruct, we have ensured that the bucket will be ready for use when the TutorialBucketService is then injected into other services.

通过在我们的TutorialBucketService实现类中注入ClusterService,并在注有@PostConstruct的方法中打开桶,我们确保了当TutorialBucketService被注入其他服务时,桶将随时可以使用。

4. Persistence Layer

4.持久层

Now that we have a service in place to obtain a Bucket instance, we will create a repository-like persistence layer that provides CRUD operations for entity classes to other services without exposing the Bucket instance to them.

现在我们已经有了一个获取Bucket实例的服务,我们将创建一个类似于存储库的持久层,为其他服务提供实体类的CRUD操作,而不向它们暴露Bucket实例。

4.1. The Person Entity

4.1.人的实体

Here is the Person entity class that we wish to persist:

这里是我们希望坚持的Person实体类。

public class Person {

    private String id;
    private String type;
    private String name;
    private String homeTown;

    // standard getters and setters
}

4.2. Converting Entity Classes To and From JSON

4.2.将实体类转换为JSON,或从JSON转换为实体类

To convert entity classes to and from the JsonDocument objects that Couchbase uses in its persistence operations, we define the JsonDocumentConverter interface:

为了将实体类转换成Couchbase在持久化操作中使用的JsonDocument对象,我们定义了JsonDocumentConverter接口。

public interface JsonDocumentConverter<T> {
    JsonDocument toDocument(T t);
    T fromDocument(JsonDocument doc);
}

4.3. Implementing the JSON Converter

4.3.实现JSON转换器

Next, we need to implement a JsonConverter for Person entities.

接下来,我们需要为Person实体实现一个JsonConverter

@Service
public class PersonDocumentConverter
  implements JsonDocumentConverter<Person> {
    ...
}

We could use the Jackson library in conjunction with the JsonObject class’s toJson and fromJson methods to serialize and deserialize the entities, however there is additional overhead in doing so.

我们可以使用Jackson库与JsonObject类的toJsonfromJson方法来序列化和反序列化实体,但是这样做会产生额外的开销。

Instead, for the toDocument method, we’ll use the fluent methods of the JsonObject class to create and populate a JsonObject before wrapping it a JsonDocument:

相反,对于toDocument方法,我们将使用JsonObject类的流畅方法来创建和填充JsonObject,然后将其包装成JsonDocument

@Override
public JsonDocument toDocument(Person p) {
    JsonObject content = JsonObject.empty()
            .put("type", "Person")
            .put("name", p.getName())
            .put("homeTown", p.getHomeTown());
    return JsonDocument.create(p.getId(), content);
}

And for the fromDocument method, we’ll use theJsonObject class’s getString method along with the setters in the Person class in our fromDocument method:

对于fromDocument方法,我们将使用JsonObject类的getString方法以及Person类中的设置器来实现我们的fromDocument方法。

@Override
public Person fromDocument(JsonDocument doc) {
    JsonObject content = doc.content();
    Person p = new Person();
    p.setId(doc.id());
    p.setType("Person");
    p.setName(content.getString("name"));
    p.setHomeTown(content.getString("homeTown"));
    return p;
}

4.4. CRUD Interface

4.4.CRUD接口

We now create a generic CrudService interface that defines persistence operations for entity classes:

我们现在创建一个通用的CrudService接口,定义了实体类的持久化操作。

public interface CrudService<T> {
    void create(T t);
    T read(String id);
    T readFromReplica(String id);
    void update(T t);
    void delete(String id);
    boolean exists(String id);
}

4.5. Implementing the CRUD Service

4.5.实现CRUD服务

With the entity and converter classes in place, we now implement the CrudService for the Person entity, injecting the bucket service and document converter shown above and retrieving the bucket during initialization:

有了实体和转换器类,我们现在为Person实体实现CrudService,注入上文所示的桶服务和文档转换器,并在初始化期间检索桶。

@Service
public class PersonCrudService implements CrudService<Person> {
    
    @Autowired
    private TutorialBucketService bucketService;
    
    @Autowired
    private PersonDocumentConverter converter;
    
    private Bucket bucket;
    
    @PostConstruct
    private void init() {
        bucket = bucketService.getBucket();
    }

    @Override
    public void create(Person person) {
        if(person.getId() == null) {
            person.setId(UUID.randomUUID().toString());
        }
        JsonDocument document = converter.toDocument(person);
        bucket.insert(document);
    }

    @Override
    public Person read(String id) {
        JsonDocument doc = bucket.get(id);
        return (doc != null ? converter.fromDocument(doc) : null);
    }

    @Override
    public Person readFromReplica(String id) {
        List<JsonDocument> docs = bucket.getFromReplica(id, ReplicaMode.FIRST);
        return (docs.isEmpty() ? null : converter.fromDocument(docs.get(0)));
    }

    @Override
    public void update(Person person) {
        JsonDocument document = converter.toDocument(person);
        bucket.upsert(document);
    }

    @Override
    public void delete(String id) {
        bucket.remove(id);
    }

    @Override
    public boolean exists(String id) {
        return bucket.exists(id);
    }
}

5. Putting It All Together

5.把一切都放在一起

Now that we have all of the pieces of our persistence layer in place, here’s a simple example of a registration service that uses the PersonCrudService to persist and retrieve registrants:

现在我们已经有了持久层的所有部分,下面是一个简单的注册服务的例子,它使用PersonCrudService来持久化和检索注册者。

@Service
public class RegistrationService {

    @Autowired
    private PersonCrudService crud;
    
    public void registerNewPerson(String name, String homeTown) {
        Person person = new Person();
        person.setName(name);
        person.setHomeTown(homeTown);
        crud.create(person);
    }
    
    public Person findRegistrant(String id) {
        try{
            return crud.read(id);
        }
        catch(CouchbaseException e) {
            return crud.readFromReplica(id);
        }
    }
}

6. Conclusion

6.结论

We have shown that with a few basic Spring services, it is fairly trivial to incorporate Couchbase into a Spring application and implement a basic persistence layer without using Spring Data.

我们已经表明,通过一些基本的Spring服务,将Couchbase纳入Spring应用程序并在不使用Spring Data的情况下实现一个基本的持久化层是相当简单的。

The source code shown in this tutorial is available in the GitHub project.

本教程中显示的源代码可在GitHub项目中找到。

You can learn more about the Couchbase Java SDK at the official Couchbase developer documentation site.

你可以在官方的Couchbase开发者文档网站了解更多关于Couchbase Java SDK的信息。