1. введение
В этом дополнении к нашему введению в Couchbase мы создаем набор служб Spring , которые можно использовать вместе для создания базового уровня сохраняемости для приложения Spring без использования данных Spring.
2. Служба кластеров
Чтобы удовлетворить ограничению, согласно которому в JVM может быть активна только одна среда CouchbaseEnvironment , мы начинаем с написания службы, которая подключается к кластеру Couchbase и предоставляет доступ к ведрам данных без непосредственного доступа к экземплярам Cluster или CouchbaseEnvironment .
2.1. Интерфейс
Вот наш Кластерный сервис интерфейс:
public interface ClusterService { Bucket openBucket(String name, String password); }
2.2. Реализация
Наш класс реализации создает экземпляр DefaultCouchbaseEnvironment и подключается к кластеру во время фазы @PostConstruct во время инициализации контекста Spring.
Это гарантирует, что кластер не является нулевым и что он подключен, когда класс вводится в другие классы обслуживания, что позволяет им открывать один или несколько сегментов данных:
@Service public class ClusterServiceImpl implements ClusterService { private Cluster cluster; @PostConstruct private void init() { CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create(); cluster = CouchbaseCluster.create(env, "localhost"); } ... }
Затем мы предоставляем ConcurrentHashMap , чтобы содержать открытые ведра и реализовать метод open Bucket :
private Mapbuckets = 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 .
Чтобы избежать этого сценария, мы определяем Bucket Service interface и класс реализации для каждого bucket. Каждый класс реализации действует как мост между Службой кластеров и классами, которым необходим прямой доступ к определенному ведру .
3.1. Интерфейс
Вот наш Сервис ведра интерфейс:
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; } }
Введя службу Cluster Service в наш класс реализации TutorialBucket Service и открыв ведро в методе, аннотированном @PostConstruct, мы обеспечили, что ведро будет готово к использованию, когда TutorialBucketService затем будет введено в другие службы.
4. Уровень персистентности
Теперь, когда у нас есть служба для получения экземпляра Bucket , мы создадим похожий на репозиторий уровень сохраняемости, который предоставляет операции CRUD для классов сущностей другим службам, не предоставляя им экземпляр Bucket .
4.1. Физическое Лицо
Вот класс Person entity, который мы хотим сохранить:
public class Person { private String id; private String type; private String name; private String homeTown; // standard getters and setters }
4.2. Преобразование Классов Сущностей В JSON и из Него
Чтобы преобразовать классы сущностей в объекты Json Document , которые Couchbase использует в своих операциях сохранения, мы определяем интерфейс JsonDocumentConverter :
public interface JsonDocumentConverter{ JsonDocument toDocument(T t); T fromDocument(JsonDocument doc); }
4.3. Реализация конвертера JSON
Далее нам нужно реализовать JsonConverter для Person entities.
@Service public class PersonDocumentConverter implements JsonDocumentConverter{ ... }
Мы могли бы использовать библиотеку Jackson в сочетании с методами JSONObject класса toJSON и FromJSON для сериализации и десериализации сущностей |, однако при этом возникают дополнительные накладные расходы.
Вместо этого для метода to Document мы будем использовать методы fluent класса 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); }
А для метода from Document мы будем использовать метод 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
Теперь мы создаем универсальный Crud-сервис интерфейс, который определяет операции сохранения для классов сущностей:
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. Внедрение Сервиса CRUD
Имея классы entity и converter на месте, мы теперь реализуем службу Crud для Person entity, вводя службу bucket и конвертер документов, показанные выше, и извлекая ведро во время инициализации:
@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. Собрать Все Это Воедино
Теперь, когда у нас есть все части нашего уровня сохраняемости, вот простой пример службы регистрации, которая использует 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.
Исходный код, показанный в этом руководстве, доступен в проекте GitHub .
Вы можете узнать больше о Java SDK Couchbase на официальном сайте документации разработчика Couchbase.