概述
在我们的Couchbase入门教程后续内容中,我们将创建一组Spring服务,用于为不使用Spring Data的Spring应用构建基本的持久层。
2. 集群服务
为了满足JVM中只能有一个CouchbaseEnvironment
活跃的约束,我们首先编写一个服务,它连接到Couchbase集群,并提供对数据桶的访问,但不会直接暴露Cluster
或CouchbaseEnvironment
实例。
2.1. 接口
这是我们的ClusterService
接口:
public interface ClusterService {
Bucket openBucket(String name, String password);
}
2.2. 实现
我们的实现类在Spring上下文初始化的@PostConstruct
阶段创建一个DefaultCouchbaseEnvironment
并连接到集群。
这样确保了当类注入到其他服务类时,集群不会为null且已连接,从而允许它们打开一个或多个数据桶:
@Service
public class ClusterServiceImpl implements ClusterService {
private Cluster cluster;
@PostConstruct
private void init() {
CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
cluster = CouchbaseCluster.create(env, "localhost");
}
...
}
接下来,我们提供一个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. 数据桶服务
根据你的应用程序架构,你可能需要在多个Spring服务中提供对同一数据桶的访问。
如果在应用启动期间试图在两个或更多服务中打开同一个桶,第二个尝试的服努可能会遇到ConcurrentTimeoutException
。
为了避免这种情况,我们定义了一个BucketService
接口以及一个与每个桶相对应的实现类。每个实现类作为ClusterService
和需要直接访问特定Bucket
的类之间的桥梁。
3.1. 接口
这是我们的BucketService
接口:
public interface BucketService {
Bucket getBucket();
}
3.2. 实现
以下类提供了对“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;
}
}
通过在TutorialBucketService
实现类中注入ClusterService
并在带有@PostConstruct
注解的方法中打开桶,我们确保了当TutorialBucketService
被注入到其他服务时,桶已经准备好供使用。
4. 持久层
现在我们有了一个获取Bucket
实例的服务,我们将创建一个类似于仓库的持久层,为实体类提供CRUD操作,而不会将Bucket
实例暴露给它们。
4.1. 人员实体
这是我们希望持久化的Person
实体类:
public class Person {
private String id;
private String type;
private String name;
private String homeTown;
// standard getters and setters
}
4.2. 实体类与JSON之间的转换
为了将实体类转换为Couchbase在持久操作中使用的JsonDocument
对象,我们定义了JsonDocumentConverter
接口:
public interface JsonDocumentConverter<T> {
JsonDocument toDocument(T t);
T fromDocument(JsonDocument doc);
}
4.3. 实现JSON转换器
接下来,我们需要为Person
实体实现一个JsonConverter
。
@Service
public class PersonDocumentConverter
implements JsonDocumentConverter<Person> {
...
}
我们可以使用Jackson
库配合JsonObject
类的toJson
和fromJson
方法进行序列化和反序列化,但这会带来额外的开销。
对于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);
}
而对于fromDocument
方法,我们将使用JsonObject
类的getString
方法,结合Person
类的setter方法在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接口
现在我们创建一个通用的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. 实现CRUD服务
有了实体和转换类,我们现在可以为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. 整合所有部分
现在我们的持久层组件齐全,下面是一个简单的示例,展示如何使用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. 总结
我们已经展示了仅使用几个基础的Spring服务,就可以轻松地将Couchbase融入Spring应用,并在不使用Spring Data的情况下实现基本的持久层。
本教程中展示的源代码可以在GitHub项目中找到。
你可以从官方Couchbase开发者文档网站了解更多关于Couchbase Java SDK的信息。