Couchbase in einer Frühlingsanwendung verwenden

Verwenden von Couchbase in einer Spring-Anwendung

1. Einführung

In diesem Follow-up zu unserenintroduction to Couchbase erstellen wir eine Reihe von Spring-Diensten, die zusammen verwendet werden können, um eine grundlegende Persistenzschicht für eine Spring-Anwendung ohne Verwendung von Spring-Daten zu erstellen.

2. Clusterdienst

Um die Einschränkung zu erfüllen, dass nur ein einzigesCouchbaseEnvironment in der JVM aktiv sein darf, schreiben wir zunächst einen Dienst, der eine Verbindung zu einem Couchbase-Cluster herstellt und Zugriff auf Daten-Buckets bietet, ohne dieCluster direkt offenzulegen oderCouchbaseEnvironment Instanzen.

2.1. Schnittstelle

Hier ist unsereClusterService-Schnittstelle:

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

2.2. Implementierung

Unsere Implementierungsklasse instanziiert einDefaultCouchbaseEnvironment und stellt während der@PostConstruct-Phase während der Spring-Kontextinitialisierung eine Verbindung zu einem Cluster her.

Auf diese Weise wird sichergestellt, dass der Cluster nicht null ist und dass eine Verbindung hergestellt wird, wenn die Klasse in andere Serviceklassen eingefügt wird. Auf diese Weise können sie einen oder mehrere Datenbereiche öffnen:

@Service
public class ClusterServiceImpl implements ClusterService {
    private Cluster cluster;

    @PostConstruct
    private void init() {
        CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
        cluster = CouchbaseCluster.create(env, "localhost");
    }
...
}

Als nächstes stellen wirConcurrentHashMap bereit, um die offenen Buckets zu enthalten, und implementieren die MethodeopenBucket:

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. Eimerservice

Je nachdem, wie Sie Ihre Anwendung architektonisch gestalten, müssen Sie möglicherweise in mehreren Spring-Services Zugriff auf denselben Daten-Bucket gewähren.

Wenn wir lediglich versucht haben, denselben Bucket in zwei oder mehr Diensten während des Anwendungsstarts zu öffnen, tritt beim zweiten Dienst, der dies versucht, wahrscheinlich einConcurrentTimeoutException auf.

Um dieses Szenario zu vermeiden, definieren wir eineBucketService-Schnittstelle und eine Implementierungsklasse pro Bucket. Jede Implementierungsklasse fungiert als Brücke zwischen denClusterService und den Klassen, die direkten Zugriff auf bestimmteBucket benötigen.

3.1. Schnittstelle

Hier ist unsereBucketService-Schnittstelle:

public interface BucketService {
    Bucket getBucket();
}

3.2. Implementierung

Die folgende Klasse bietet Zugriff auf den Bucket "example-tutorial":

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

    @Autowired
    private ClusterService couchbase;

    private Bucket bucket;

    @PostConstruct
    private void init() {
        bucket = couchbase.openBucket("example-tutorial", "");
    }

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

Durch Einfügen derClusterService in unsere ImplementierungsklasseTutorialBucketService und Öffnen des Buckets in einer mit@PostConstruct, versehenen Methode haben wir sichergestellt, dass der Bucket zur Verwendung bereit ist, wennTutorialBucketService ist dann in andere Dienste injiziert.

4. Persistenzschicht

Nachdem wir einen Dienst eingerichtet haben, um eineBucket-Instanz abzurufen, erstellen wir eine repositoryähnliche Persistenzschicht, die CRUD-Operationen für Entitätsklassen für andere Dienste bereitstellt, ohne dieBucket-Instanz für sie verfügbar zu machen.

4.1. Die Personeneinheit

Hier ist die EntitätsklassePerson, die wir beibehalten möchten:

public class Person {

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

    // standard getters and setters
}

4.2. Konvertieren von Entitätsklassen in und von JSON

Um Entitätsklassen in und von denJsonDocument-Objekten zu konvertieren, die Couchbase für seine Persistenzoperationen verwendet, definieren wir dieJsonDocumentConverter-Schnittstelle:

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

4.3. Implementierung des JSON-Konverters

Als nächstes müssen wirJsonConverter fürPerson Entitäten implementieren.

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

Wir könnten die BibliothekJacksonin Verbindung mit den MethodentoJson undfromJson der KlassenJsonObject verwenden, um die __ Entitäten zu serialisieren und zu deserialisieren. Dies ist jedoch mit zusätzlichem Aufwand verbunden.

Stattdessen verwenden wir für dietoDocument-Methode die fließenden Methoden derJsonObject-Klasse, um einJsonObject zu erstellen und zu füllen, bevor es mit einemJsonDocument umbrochen wird:

@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);
}

Und für die MethodefromDocumentverwenden wir die MethodegetStringder KlasseJsonObjectzusammen mit den Setzern in der KlassePersonin unserer MethodefromDocument:

@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-Schnittstelle

Wir erstellen jetzt eine generischeCrudService-Schnittstelle, die Persistenzoperationen für Entitätsklassen definiert:

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. Implementierung des CRUD-Dienstes

Mit den vorhandenen Entitäts- und Konverterklassen implementieren wir jetztCrudService für die EntitätPerson, injizieren den oben gezeigten Bucket-Service und Dokumentkonverter und rufen den Bucket während der Initialisierung ab:

@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. Alles zusammenfügen

Nachdem wir nun alle Teile unserer Persistenzschicht eingerichtet haben, finden Sie hier ein einfaches Beispiel für einen Registrierungsdienst, derPersonCrudServiceverwendet, um Registranten zu persistieren und abzurufen:

@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. Fazit

Wir haben gezeigt, dass es mit einigen grundlegenden Spring-Diensten relativ einfach ist, Couchbase in eine Spring-Anwendung zu integrieren und eine grundlegende Persistenzschicht zu implementieren, ohne Spring-Daten zu verwenden.

Der in diesem Tutorial gezeigte Quellcode ist inGitHub project verfügbar.

Weitere Informationen zum Couchbase Java SDK finden Sie unterCouchbase developer documentation site.