This quickstart tutorial will review the basics of using Couchbase by building a simple Spring Data REST API that stores user profiles is used as an example.
To run this prebuild project, you will need:
The sample source code used in this tutorial is published on GitHub. To obtain it, clone the git repository with your IDE or execute the following command:
git clone https://github.com/couchbase-examples/java-springdata-quickstart
Gradle dependencies:
implementation 'org.springframework.boot:spring-boot-starter-web'
// spring data couchbase connector
implementation 'org.springframework.boot:spring-boot-starter-data-couchbase'
// swagger ui
implementation 'org.springdoc:springdoc-openapi-ui:1.6.6'
Maven dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-couchbase</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.6</version>
</dependency>
Spring Data couchbase connector can be configured by providing a @Configuration
bean that extends AbstractCouchbaseConfiguration
.
The sample application provides a configuration bean that uses default couchbase login and password:
@Configuration
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
// capella
// return "couchbases://cb.jnym5s9gv4ealbe.cloud.couchbase.com";
// localhost
return "127.0.0.1"
}
@Override
public String getUserName() {
return "Administrator";
}
@Override
public String getPassword() {
return "password";
}
@Override
public String getBucketName() {
return "springdata_quickstart";
}
...
from config/CouchbaseConfiguration.java
This default configuration assumes that you have a locally running Couchbae server and uses standard administrative login and password for demonstration purpose. Applications deployed to production or staging environments should use less privileged credentials created using Role-Based Access Control. Please refer to Managing Connections using the Java SDK with Couchbase Server for more information on Capella and local cluster connections.
To install dependencies and run the application on Linux, Unix or OS X, execute ./gradlew bootRun
(./gradew.bat bootRun
on Windows).
Once the site is up and running, you can launch your browser and go to the Swagger Start Page to test the APIs.
We will be setting up a REST API to manage demo user profiles and store them as documents on a Couchbase Cluster. Every document needs a unique identifier with which it can be addressed in the API. We will use auto-generated UUIDs for this purpose and store in profile documents the first and the last name of the user, their age, and address:
{
"id": "b181551f-071a-4539-96a5-8a3fe8717faf",
"firstName": "John",
"lastName": "Doe",
"age": "35",
"address": "123 Main St"
}
To work with submitted profiles, we first need to model their structure in a Java class, which would define the set of profile fields and their types.
In our sample application, this is done in model/Profile.java
class:
@Scope("_default")
@Collection("profile")
public class Profile {
@id
@GeneratedValue
private UUID id;
private String firstName, lastName;
private byte age;
private String address;
// ...
}
from
model/Profile.java
The whole model is annotated with @Scope
and @Collection
annotations, which configure Spring Data to store model instances into profile
collection in the default scope.
It is also worth noting the use of @Id
and @GeneratedValue
annotations on Profile::id
field.
In couchbase, data is stored as JSON documents; each document has a unique identifier that can be used to address that document.
Every profile instance in our example corresponds to a single document and this annotation is used here to link the document's id to a java field.
Additionally, the @GeneratedValue
annotation on the field instructs Spring Data to generate a random UUID if we try to store a profile without one, which will come in handy later.
You can find more information on key generation in the Connector Documentation.
Couchbase Spring Data connector will automatically serialize model instances into JSON when storing them on the cluster.
Automated database initialization and migration is a common solution that simplifies database management operations.
To keep it simple, our demo uses DbSetupRunner component class that implements CommandLineRunner
interface and is invoked every time the application starts.
The runner tries to create required structure every startup but ignores any errors if such structure already exists.
For example, this code creates a primary index for configured bucket:
try {
// We must create primary index on our bucket in order to query it
cluster.queryIndexes().createPrimaryIndex(config.getBucketName());
LOGGER.info("Created primary index {}", config.getBucketName());
} catch (IndexExistsException iee) {
LOGGER.info("Primary index {} already exists", config.getBucketName());
}
From
config/DbSetupRunner.java
Primary indexes in Couchbase contain all document keys and are used to fetch documents by their unique identifiers.
Secondary indexes can be used to efficiently query documents by their properties.
For example, DbSetupRunner
creates additional indexes on the collection that allow querying profiles by first or last names or addresses:
try {
final String query = "CREATE INDEX secondary_profile_index ON " + config.getBucketName() + "._default." + CouchbaseConfiguration.PROFILE_COLLECTION + "(firstName, lastName, address)";
cluster.query(query);
} catch (IndexExistsException e) {
LOGGER.info("Secondary index exists on collection {}", CouchbaseConfiguration.PROFILE_COLLECTION);
}
From
config/DbSetupRunner.java
More information on working with Couchbase indexes can be found in our documentation.
For CRUD operations, we will extend PagingAndSortingRepository
provided by the framework:
@Repository
public interface ProfileRepository extends PagingAndSortingRepository<Profile, UUID> {
@Query("#{#n1ql.selectEntity} WHERE firstName LIKE '%' || $1 || '%' OR lastName LIKE '%' || $1 || '%' OR address LIKE '%' || $1 || '%' OFFSET $2 * $3 LIMIT $3")
List<Profile> findByText(String query, int pageNum, int pageSize);
Page<Profile> findByAge(byte age, Pageable pageable);
}
From
repository/ProfileRepository.java
Open the ProfileController
class located in controller
package and navigate to the saveProfile
method.
This method accepts Profile
objects deserialized by Spring Web from the body of an HTTP request.
@PostMapping("/profile")
public ResponseEntity<Profile> saveProfile(@RequestBody Profile profile) {
// the same endpoint can be used to create and save the object
profile = profileRepository.save(profile);
return ResponseEntity.status(HttpStatus.CREATED).body(profile);
}
from
saveProfile
method ofcontroller/ProfileController.java
This object can be modified according to business requirements and then saved directly into the database using ProfileRepository::save
method.
Because we used @GeneratedValue
annotation on id
field of our java model, Spring Data will automatically generate a document id when it is missing from the request. This allows clients to use /profile
endpoint both to update existing profiles and create new records.
To achieve this, a client needs to submit a Profile object without the id field.
Navigate to the getProfileById
method in ProfileController
class.
This method handles client requests to retrieve a single profile by its unique id.
Sent by the client UUID is passed to the standard findById
method of ProfileRepository
, which returns an Optional
with requested profile:
Profile result = profileRepository.findById(id).orElse(null);
from getProfileById method of controller/ProfileController.java
Although Couchbase provides powerful full-text search capabilities out of the box, in this demo we use classic LIKE
query for our profile search endpoint.
Navigate to listProfiles
method of Profile Controller.
The endpoint uses customized findByText
method of Profile Repository:
result = profileRepository.findByText(query, pageRequest).toList();
from
listProfiles
method incontroller/ProfileController.java
The ProfileRepository::findByQueryMethod
is generated automatically using provided in @Query
annotation SpEL template in SQL++:
@Query("#{#n1ql.selectEntity} WHERE firstName LIKE '%' || $1 || '%' OR lastName LIKE '%' || $1 || '%' OR address LIKE '%' || $1 || '%'")
Page<Profile> findByQuery(String query, Pageable pageable);
definition of
findByQuery
method inrepository/ProfileRepository.java
You can find out more about SQL++ in Spring Data in the connector documentation.
Navigate to the deleteProfile
method in the Profile Controller.
We only need the Key
or id from the user to remove a document using a basic key-value operation.
profileRepository.deleteById(id);
from
deleteProfile
method of controller/ProfileController.java
Setting up a basic REST API in Spring Data with Couchbase is fairly simple. This project, when run with Couchbase Server 7 installed creates a collection in Couchbase, an index for our parameterized N1QL query, and showcases basic CRUD operations needed in most applications.