Modern NoSQL Persistence with Spring Data Datastore
Spring Data provides a consistent repository-based programming model for various database technologies, including NoSQL stores like Google Cloud Datastore. Spring Data Datastore allows you to interact with Datastore using the familiar, high-level repository pattern, just like Spring Data JPA, but for a schemaless, NoSQL database.
This guide provides a complete, runnable example of a Spring Boot application using Spring Data Datastore. To make it easy to run without a real Google Cloud project, we will use the local Datastore emulator.
Key Concepts
DatastoreRepository: Similar toJpaRepository, you extend this interface to get full CRUD functionality for your Datastore entities.@Entity&@Id: Annotations used to mark your POJOs as Datastore entities.@Reference: Used to model relationships between entities. Instead of a foreign key, this stores the DatastoreKeyof the related entity, and Spring Data handles the lookup for you.
Complete Runnable Example with Datastore Emulator
This example can be set up as a new Maven project in Eclipse.
1. Prerequisites: Install the Datastore Emulator
To run this project locally, you need the Google Cloud CLI installed.
Install Google Cloud CLI: Follow the official instructions at cloud.google.com/sdk/docs/install.
Install the Datastore Emulator Component:
gcloud components install cloud-datastore-emulatorStart the Emulator: In a new terminal, run the following command. Keep this terminal open while you run the application.
gcloud beta emulators datastore start --project=local-test-project --host-port="localhost:8081"
2. Project Structure
spring-data-datastore/
├── pom.xml OR build.gradle
└── src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── datastore/
│ │ ├── Application.java
│ │ ├── Author.java
│ │ ├── AuthorRepository.java
│ │ ├── Book.java
│ │ └── BookRepository.java
│ └── resources/
│ └── application.properties
└── test/
3. Build Configuration (Choose Maven or Gradle)
You only need one of these files in your project root.
Option A: pom.xml (Maven)
This file includes the Spring Cloud GCP starter for Datastore. The Spring Cloud version is managed via the spring-cloud-gcp-dependencies BOM.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.7.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-data-datastore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-datastore</name>
<description>Demo project for Spring Boot with Spring Data Datastore</description>
<properties>
<java.version>11</java.version>
<spring-cloud-gcp.version>3.4.1</spring-cloud-gcp.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>${spring-cloud-gcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Data starter for Google Cloud Datastore -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
</dependency>
<!-- Spring Web starter to easily test with a browser/curl -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Option B: build.gradle (Gradle)
This file provides the exact same dependencies and functionality as the pom.xml but uses Gradle's Groovy DSL.
plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext {
set('springCloudGcpVersion', "3.4.1")
set('springCloudVersion', "2021.0.4")
}
dependencies {
// Spring Data starter for Google Cloud Datastore
implementation 'com.google.cloud:spring-cloud-gcp-starter-data-datastore'
// Spring Web starter to easily test with a browser/curl
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "com.google.cloud:spring-cloud-gcp-dependencies:${springCloudGcpVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
How to Use the Gradle Configuration
- Create the File: Instead of a
pom.xmlfile, create a file namedbuild.gradlein the root of your project and paste the content above. - Initialize the Wrapper (Recommended): Open a terminal in your project root and run
gradle wrapper. This will create the Gradle Wrapper files (gradlewandgradlew.bat), which is the standard way to build Gradle projects without needing to install Gradle locally. - Build and Run: You can now run the application from your terminal using the wrapper:
- On Mac/Linux:
./gradlew bootRun - On Windows:
gradlew.bat bootRun
- On Mac/Linux:
Key Differences from Maven
- Syntax: Gradle uses a concise, code-like Groovy or Kotlin DSL, whereas Maven uses verbose XML.
pluginsblock: This is where you apply core plugins, similar to specifying a<parent>and build plugins in Maven.dependenciesblock: This is where you declare your project's libraries.implementationis the most common configuration, similar to<scope>compile</scope>in Maven.dependencyManagementblock: This imports the Spring Cloud "BOMs" (Bill of Materials) to ensure all dependency versions are compatible, just like the<dependencyManagement>section in thepom.xml.
4. src/main/resources/application.properties
This configuration tells the application to connect to our local emulator instead of the real Google Cloud Datastore. This file is the same for both Maven and Gradle.
# Spring Data Datastore configuration
# Use a dummy project ID for the emulator
spring.cloud.gcp.datastore.project-id=local-test-project
# Point to the local emulator address
spring.cloud.gcp.datastore.host=localhost:8081
# Disable authentication when using the emulator
spring.cloud.gcp.datastore.credentials.location=
# Optional: Expose REST endpoints for our repositories automatically
# This allows for easy testing at http://localhost:8080/authors and http://localhost:8080/books
spring.data.rest.base-path=/api
5. Entity and Repository Classes
Here we model a one-to-many relationship: an Author can have many Books. (These files are the same for both Maven and Gradle).
File: src/main/java/com/example/datastore/Book.java
package com.example.datastore;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
@Entity(name = "books")
public class Book {
@Id
private Long id;
private String title;
public Book(String title) {
this.title = title;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
@Override
public String toString() {
return "Book{" + "id=" + id + ", title='" + title + "\'" + '}';
}
}
File: src/main/java/com/example/datastore/Author.java
package com.example.datastore;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import java.util.List;
@Entity(name = "authors")
public class Author {
@Id
private Long id;
private String name;
// This annotation stores the Keys of the related Book entities.
// When the Author is loaded, Spring Data will automatically fetch the associated books.
@Reference
private List<Book> books;
public Author(String name, List<Book> books) {
this.name = name;
this.books = books;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<Book> getBooks() { return books; }
public void setBooks(List<Book> books) { this.books = books; }
@Override
public String toString() {
return "Author{" + "id=" + id + ", name='" + name + "\'" + ", books=" + books + '}';
}
}
File: src/main/java/com/example/datastore/BookRepository.java
package com.example.datastore;
import com.google.cloud.spring.data.datastore.repository.DatastoreRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookRepository extends DatastoreRepository<Book, Long> {}
File: src/main/java/com/example/datastore/AuthorRepository.java
package com.example.datastore;
import com.google.cloud.spring.data.datastore.repository.DatastoreRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorRepository extends DatastoreRepository<Author, Long> {}
6. Main Application and Demo Runner
The main class starts the application and uses a CommandLineRunner to save and retrieve data, demonstrating the relationship mapping. (This file is the same for both Maven and Gradle).
File: src/main/java/com/example/datastore/Application.java
package com.example.datastore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner demo(AuthorRepository authorRepository, BookRepository bookRepository) {
return (args) -> {
// Clear the database on startup
authorRepository.deleteAll();
bookRepository.deleteAll();
// --- Create and save entities to demonstrate relationship ---
log.info("Saving books and authors...");
// First, save the child entities (Books)
Book book1 = bookRepository.save(new Book("The Cloud Guide"));
Book book2 = bookRepository.save(new Book("Advanced Datastore"));
Book book3 = bookRepository.save(new Book("Spring for NoSQL"));
// Create a parent entity (Author) that references the books
Author author = new Author("Jane Doe", Arrays.asList(book1, book2));
authorRepository.save(author);
// Create another author
authorRepository.save(new Author("John Smith", Arrays.asList(book3)));
// --- Query and display the results ---
log.info("----------------------------------------");
log.info("Fetching all authors...");
Iterable<Author> allAuthors = authorRepository.findAll();
for (Author a : allAuthors) {
log.info("Author: " + a.getName());
// The referenced books are automatically loaded
if (a.getBooks() != null) {
a.getBooks().forEach(book -> log.info("\t- Book: " + book.getTitle()));
}
}
log.info("----------------------------------------");
};
}
}