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.