By default, Spring Data Couchbase implements single-bucket configuration. In this default implementation, all POJO (Plain Old Java Object) models/documents will be stored in a single bucket. This is possible, because Couchbase is a NoSQL database which can store schemaless data. To differentiate between type of documents, Spring Data adds _class
field that contains the fully-qualified class name of the POJO.
{ "name": "Jane Doe", "createdAt": 1624102211338, "_class": "id.gultom.model.Customer", "updatedAt": 1624102211338 }
Spring Data Couchbase has an extensive documentation available here, but unfortunately the section about multibucket(s) implementation is missing. There are also some tutorials like this one, but most of them are only for single-bucket implementation. There are some references regarding multi-buckets implementation (like this Github Ticket) scattered in the internet, but I couldn’t find a single comprehensive example. So I decided to post the result of my research here.
⚠️ Please be aware that this implementation is for the Spring Data Couchbase 4.2.1. The older or newer version may require different approach — although I believe this is compatible for the same major versions (4.x). Also, in this example I only use the non-reactive repository. Implementing reactive repository may require some adjustments as well.
In order to implement multi-buckets in Spring Boot application, we need to setup a configuration class with custom Couchbase template beans for each buckets. Only model/document that is mapped to custom Couchbase template that will have separate bucket. Otherwise they will be stored in the default bucket. In this example, we define supplierTemplate
and customerTemplate
for Supplier
and Customer
models respectively.
package id.gultom.config; import id.gultom.model.Customer; import id.gultom.model.Supplier; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; @Slf4j @Configuration @EnableCouchbaseRepositories(basePackages = "id.gultom.repository.couchbase") public class CouchbaseConfig extends AbstractCouchbaseConfiguration { @Value("${spring.couchbase.connection-string}") private String connectionString; @Value("${spring.couchbase.username}") private String username; @Value("${spring.couchbase.password}") private String password; // this bucket will be used by models/repositories // that is not mapped to custom couchbase templates @Value("${spring.data.couchbase.bucket-name:Default}") private String defaultBucketName; @Autowired private MappingCouchbaseConverter mappingCouchbaseConverter; @Override public String getConnectionString() { return this.connectionString; } @Override public String getUserName() { return this.username; } @Override public String getPassword() { return this.password; } @Override public String getBucketName() { return this.defaultBucketName; } @Override protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) { // only model that is mapped to custom template will be mapped to non-default bucket mapping.mapEntity(Supplier.class, supplierTemplate()); mapping.mapEntity(Customer.class, customerTemplate()); } @Bean public CouchbaseTemplate supplierTemplate() { // define the custom template bucket name return customCouchbaseTemplate(customCouchbaseClientFactory("Supplier")); } @Bean public CouchbaseTemplate customerTemplate() { // define the custom template bucket name return customCouchbaseTemplate(customCouchbaseClientFactory("Customer")); } // do not use couchbaseTemplate for the name of this method, otherwise the value of that been // will be used instead of the result from this call (the client factory arg is different) public CouchbaseTemplate customCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory) { return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter); } // do not use couchbaseClientFactory for the name of this method, otherwise the value of that bean will // will be used instead of this call being made ( bucketname is an arg here, instead of using bucketName() ) public CouchbaseClientFactory customCouchbaseClientFactory(String bucketName) { return new SimpleCouchbaseClientFactory(getConnectionString(), authenticator(), bucketName); } }
That’s all! ?
The remaining POJO and repository classes can be defined just like the default/single-bucket implementation. Because as we can see in the configuration class above, we already explicitly map the POJOs with the custom Couchbase template and the repositories should be mapped to the POJOs like example below.
package id.gultom.repository.couchbase; import id.gultom.model.Supplier; import org.springframework.data.couchbase.repository.CouchbaseRepository; public interface SupplierRepository extends CouchbaseRepository<Supplier, String> { }
Besides that, we just need to make sure the buckets and indexes are created properly in the Couchbase database server our application is connected to. Because Spring Data Couchbase doesn’t automatically create them for us (like Hibernate).
As a reference, this is the relevant part of the pom.xml for this project:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>id.gultom</groupId> <artifactId>rest-api</artifactId> ... <dependencies> ... <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-couchbase</artifactId> </dependency> ... </dependencies> ... </project>
Obviously, my configuration is not the only way to implement multi-buckets. But this is the configuration that I’ve personally tested and apparently working well. I’d appreciate if you can give a comment if you thing any improvement can be made.
The full code (including minimal Couchbase setup) can be found in this Github repository https://github.com/yohanesgultom/spring-boot-microservice.
Enjoy! ?
Getting verified SSL information with Python (3.x) is very easy. Code examples for it are…
Last year, Google released Firebase Auth Emulator as a new component in Firebase Emulator. In…
One of the authentication protocol that is supported by most of Google Cloud services is…
If you need to to add a spatial information querying in your application, PostGIS is…
Amazon Web Service Transcribe provides API to automatically convert an audio speech file (mp3/wav) into…
I had a project to build a simple website that split uploaded video into parts…
View Comments
Nice and easy. Have you tried to map the type class to a shorter attribute?
Do you mean using shorter value than fully qualified class name for _class? Nope. But there is some suggestions here https://stackoverflow.com/a/39037452/1862500
what if there is 2 different cluster with 2 diff username and password?
how can we configure that?
Essentially you will need to create additional configuration bean extending
AbstractCouchbaseConfiguration
. Then you can map differentconnectionString
,username
,password
.etc from different properties values. The idea is the same as this tutorial of having multiple datasources in Spring Data