Utilisation de Couchbase dans une application Spring

1. Introduction

Dans ce suivi de notre introduction à Couchbase, nous créons un ensemble de services Spring qui peuvent être utilisés ensemble pour créer une couche de persistance de base pour une application Spring sans utiliser Spring Data.

2. Service de cluster

Afin de satisfaire la contrainte selon laquelle seul un seul CouchbaseEnvironment peut être actif dans la JVM, nous commençons par écrire un service qui se connecte à un cluster Couchbase et fournit un accès aux compartiments de données sans exposer directement les instances de Cluster ou CouchbaseEnvironment .

2.1. Interface

Voici notre interface ClusterService :

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

2.2. la mise en oeuvre

Notre classe d'implémentation instancie un DefaultCouchbaseEnvironment et se connecte à un cluster pendant la phase @PostConstruct lors de l'initialisation du contexte Spring.

Cela garantit que le cluster n'est pas nul et qu'il est connecté lorsque la classe est injectée dans d'autres classes de service, leur permettant ainsi d'ouvrir un ou plusieurs buckets de données:

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

Ensuite, nous fournissons un ConcurrentHashMap pour contenir les buckets ouverts et implémenter la méthode openBucket :

private Map 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. Service de godet

Selon la manière dont vous concevez votre application, vous devrez peut-être fournir un accès au même compartiment de données dans plusieurs services Spring.

Si nous essayons simplement d'ouvrir le même compartiment dans deux ou plusieurs services lors du démarrage de l'application, le deuxième service qui tente cela est susceptible de rencontrer une exception ConcurrentTimeoutException .

Pour éviter ce scénario, nous définissons une interface BucketService et une classe d'implémentation par compartiment. Chaque classe d'implémentation agit comme un pont entre le ClusterService et les classes qui ont besoin d'un accès direct à un Bucket particulier .

3.1. Interface

Voici notre interface BucketService :

public interface BucketService { Bucket getBucket(); }

3.2. la mise en oeuvre

La classe suivante permet d'accéder au 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; } }

En injectant le ClusterService dans notre classe d'implémentation TutorialBucketService et en ouvrant le bucket dans une méthode annotée avec @PostConstruct, nous nous sommes assurés que le bucket sera prêt à être utilisé lorsque le TutorialBucketService est ensuite injecté dans d'autres services.

4. Couche de persistance

Maintenant que nous avons un service en place pour obtenir une instance Bucket , nous allons créer une couche de persistance semblable à un référentiel qui fournit des opérations CRUD pour les classes d'entités à d'autres services sans leur exposer l' instance Bucket .

4.1. L'entité personne

Voici la classe d'entité Person que nous souhaitons conserver:

public class Person { private String id; private String type; private String name; private String homeTown; // standard getters and setters }

4.2. Conversion de classes d'entités vers et depuis JSON

Pour convertir des classes d'entités vers et depuis les objets JsonDocument que Couchbase utilise dans ses opérations de persistance, nous définissons l' interface JsonDocumentConverter :

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

4.3. Implémentation du convertisseur JSON

Ensuite, nous devons implémenter un JsonConverter pour les entités Person .

@Service public class PersonDocumentConverter implements JsonDocumentConverter { ... }

Nous pourrions utiliser la bibliothèque Jackson en conjonction avec les méthodes toJson et fromJson de la classe JsonObject pour sérialiser et désérialiser leentités, mais cela entraîne des frais généraux supplémentaires.

Au lieu de cela, pour la méthode toDocument , nous utiliserons les méthodes fluent de la classe JsonObject pour créer et remplir un JsonObject avant de l' encapsuler dans un 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); }

Et pour la méthode fromDocument , nous utiliserons la méthode getString de la classe JsonObject avec les setters de la classe Person dans notre méthode 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. Interface CRUD

Nous créons maintenant une interface CrudService générique qui définit les opérations de persistance pour les classes d'entités:

public interface CrudService { 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. Implémentation du service 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:

@Service public class PersonCrudService implements CrudService { @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 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

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:

@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

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.

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

Vous pouvez en savoir plus sur le SDK Java Couchbase sur le site officiel de documentation des développeurs Couchbase.