1. Overview
Cloud Spanner is a highly-available, horizontally scalable, and multi-regional RDBMS.This code lab will use a smallest instance of Cloud Spanner, but don't forget to shut it down when you are done!
What you'll learn
- How to use Cloud Spanner to save and retrieve data with Spring Boot
What you'll need
How will you use this tutorial?
How would you rate your experience with using Google Cloud Platform services?
2. Setup and Requirements
Self-paced environment setup
- Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or G Suite account, you must create one.)
Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID
.
- Next, you'll need to enable billing in Cloud Console in order to use Google Cloud resources.
Running through this codelab shouldn't cost much, if anything at all. Be sure to to follow any instructions in the "Cleaning up" section which advises you how to shut down resources so you don't incur billing beyond this tutorial. New users of Google Cloud are eligible for the $300USD Free Trial program.
Activate Cloud Shell
- From the Cloud Console, click Activate Cloud Shell .
If you've never started Cloud Shell before, you'll be presented with an intermediate screen (below the fold) describing what it is. If that's the case, click Continue (and you won't ever see it again). Here's what that one-time screen looks like:
It should only take a few moments to provision and connect to Cloud Shell.
This virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with simply a browser or your Chromebook.
Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your project ID.
- Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list
Command output
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
gcloud config list project
Command output
[core] project = <PROJECT_ID>
If it is not, you can set it with this command:
gcloud config set project <PROJECT_ID>
Command output
Updated property [core/project].
3. Initialize Cloud Spanner
Enable Cloud Spanner API using the gcloud CLI:
gcloud services enable spanner.googleapis.com
Create a Cloud Spanner instance:
gcloud spanner instances create spanner-instance \ --config=regional-us-central1 \ --nodes=1 --description="A Spanner Instance"
Create a database within the instance:
gcloud spanner databases create orders \ --instance=spanner-instance
Create a schema.ddl
file to describe the data schema:
cat << EOF > schema.ddl CREATE TABLE orders ( order_id STRING(36) NOT NULL, description STRING(255), creation_timestamp TIMESTAMP, ) PRIMARY KEY (order_id); CREATE TABLE order_items ( order_id STRING(36) NOT NULL, order_item_id STRING(36) NOT NULL, description STRING(255), quantity INT64, ) PRIMARY KEY (order_id, order_item_id), INTERLEAVE IN PARENT orders ON DELETE CASCADE; EOF
Apply the schema to Cloud Spanner database:
gcloud spanner databases ddl update orders \ --instance=spanner-instance \ --ddl="$(<schema.ddl)"
4. Bootstrap a new Spring Boot Java Application
From the Cloud Shell environment, use the following command to initialize and bootstrap a new Spring Boot application:
$ curl https://start.spring.io/starter.tgz \ -d packaging=jar \ -d dependencies=cloud-gcp,web,lombok \ -d baseDir=spanner-example \ -d type=maven-project \ -d bootVersion=3.2.6 | tar -xzvf - $ cd spanner-example
This will create a new spanner-example/
directory with a new Maven project, along with Maven's pom.xml
, a Maven wrapper, as well as an application entrypoint.
In the pom.xml
file, add the Spring Data Cloud Spanner starter.
spanner-example/pom.xml
<project>
...
<dependencies>
...
<!-- Add Spring Cloud GCP Spanner Starter -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
...
</dependencies>
...
</project>
In application.properties, configure Spanner database connection information:
spanner-example/src/main/resources/application.properties
spring.cloud.gcp.spanner.instance-id=spanner-instance spring.cloud.gcp.spanner.database=orders
Make sure JAVA_HOME
is set to the right version:
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/
Rebuild the app to make sure your Maven configuration is correct:
./mvnw package
5. Create the Entities
With Spring Cloud GCP's Spring Data Spanner support, you can easily create a Java object, and idiomatic ORM mapping to a Spanner table, using Spring Data.
First, create an Order Item class.
spanner-example/src/main/java/com/example/demo/OrderItem.java
package com.example.demo;
import com.google.cloud.spring.data.spanner.core.mapping.Column;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
@Table(name="order_items")
@Data
class OrderItem {
@PrimaryKey(keyOrder = 1)
@Column(name="order_id")
private String orderId;
@PrimaryKey(keyOrder = 2)
@Column(name="order_item_id")
private String orderItemId;
private String description;
private Long quantity;
}
For Parent/Child relationships in Spanner, you should use a composite primary key. In this example, the composite key is the order_id
, and order_item_id
.
Next, create an Order class:
spanner-example/src/main/java/com/example/demo/Order.java
package com.example.demo;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import com.google.cloud.spring.data.spanner.core.mapping.Column;
import com.google.cloud.spring.data.spanner.core.mapping.Interleaved;
import com.google.cloud.spring.data.spanner.core.mapping.PrimaryKey;
import com.google.cloud.spring.data.spanner.core.mapping.Table;
@Table(name="orders")
@Data
public class Order {
@PrimaryKey
@Column(name="order_id")
private String id;
private String description;
@Column(name="creation_timestamp")
private LocalDateTime timestamp;
@Interleaved
private List<OrderItem> items;
}
This class uses the @Interleaved
annotation to create a one to many relationship with Order Items.
6. Create the OrderRepository interface
Create the OrderRepository
class with the following content:
spanner-example/src/main/java/com/example/demo/OrderRepository.java
package com.example.demo;
import com.google.cloud.spring.data.spanner.repository.SpannerRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderRepository extends SpannerRepository<Order, String> {
}
The interface extends SpannerRepository<Order, String>
where Order
is the domain class and String
is the Primary Key type. Spring Data will automatically provide CRUD access through this interface and you won't need to create any additional code.
7. Create a REST Controller for basic operations
Open the main application DemoApplication
class and modify it to look like this:
spanner-example/src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import java.time.LocalDateTime;
import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class OrderController {
private final OrderRepository orderRepository;
OrderController(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@GetMapping("/api/orders/{id}")
public Order getOrder(@PathVariable String id) {
return orderRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, id + " not found"));
}
@PostMapping("/api/orders")
public String createOrder(@RequestBody Order order) {
// Spanner currently does not auto generate IDs
// Generate UUID on new orders
order.setId(UUID.randomUUID().toString());
order.setTimestamp(LocalDateTime.now());
order.getItems().forEach(item -> {
// Assign parent ID, and also generate child ID
item.setOrderId(order.getId());
item.setOrderItemId(UUID.randomUUID().toString());
});
Order saved = orderRepository.save(order);
return saved.getId();
}
}
8. Run the Application
Rebuild and run the application!
./mvnw spring-boot:run
This should start properly and listen on port 8080.
You can post an Order record to the endpoint:
curl -H"Content-Type: application/json" -d'{"description": "My orders", "items": [{"description": "Android Phone", "quantity": "1"}]}' \ http://localhost:8080/api/orders
It should respond with the Order's UUID
.
You can then retrieve the Order with the UUID
:
curl http://localhost:8080/api/orders/REPLACE_WITH_ORDER_UUID
To see how the data is stored Cloud Spanner, go to Cloud Console and navigate to Spanner → Spanner Instance → order database → orders table → Data.
9. Clean up
To clean up, delete the Spanner instance so that it's no longer incurring charges!
gcloud spanner instances delete spanner-instance -q
10. Congratulations!
In this codelab, you've created an interactive CLI application that can store and retrieve data from Cloud Spanner!
Learn More
- Cloud Spanner: https://cloud.google.com/spanner/
- Spring on GCP project: https://googlecloudplatform.github.io/spring-cloud-gcp/reference/html/
- Spring on GCP GitHub repository: https://github.com/spring-cloud/spring-cloud-gcp
- Java on Google Cloud Platform: https://cloud.google.com/java/
License
This work is licensed under a Creative Commons Attribution 2.0 Generic License.