Cloud Spanner
Cloud Spanner is a scalable, enterprise-grade, globally-distributed, and strongly consistent database service built for the cloud specifically to combine the benefits of relational database structure with non-relational horizontal scale. It delivers high-performance transactions and strong consistency across rows, regions, and continents with an industry-leading 99.999% availability SLA, no planned downtime, and enterprise-grade security. Cloud Spanner revolutionizes database administration and management and makes application development more efficient.
Cloud Spanner Instance
Enable API
gcloud services enable spanner.googleapis.com
Create an Instance
gcloud spanner instances create spanner-instance \
--config=regional-us-central1 \
--nodes=1 --description="A Spanner Instance"
Create a Database
A Cloud Spanner instance can host multiple databases, that each contains its own tables.
gcloud spanner databases create orders \
--instance=spanner-instance
Connect to Instance
There is no interactive CLI to Cloud Spanner. You can create table and execute SQL statements from the Cloud Console, or from gcloud
command line. Following are some common operations:
List Databases
gcloud spanner databases list --instance=spanner-instance
Execute SQL Statements
gcloud spanner databases execute-sql orders \
--instance=spanner-instance \
--sql="SELECT 1"
Show Query Plan
gcloud spanner databases execute-sql orders \
--instance=spanner-instance \
--sql="SELECT 1" \
--query-mode=PLAN
Create a Table
Create a DDL file, 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;
Cloud Spanner differs from traditional RDBMS in a several ways:
No server-side automatic ID generation.
Avoid monodically increasing IDs - I.e., no auto incremented ID, because it may create hot spots in partitions.
No foreign key constraints.
Cloud Spanner, being horizontally scalable and shards data with your keys, prefers the following:
UUIDv4 as IDs - It's random and can be partitioned easily.
Parent-Children relationship encoded using a compose primary key.
Colocate Parent-Children data using Interleave tables - If accessing parent means children data is also likely to be read, interleaving allows children data to be colocated with parent row in the same parition.
Use gcloud to execute the DDL:
gcloud spanner databases ddl update orders \
--instance=spanner-instance \
--ddl="$(<schema.ddl)"
Spring Data Spanner Starter
The easiest way to access Cloud Spanner is using Spring Cloud GCP's Spring Data Spanner starter. This starter provides full Spring Data support for Cloud Spanner while implementing idiomatic access patterns.
Spring Data Feature
Supported
ORM
✅
Declarative Transaction
✅
Repository
✅
REST Repository
✅
Query methods
✅
Query annotation
✅
Pagination
✅
Events
✅
Auditing
✅
Dependency
Add the Spring Data Spanner starter:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-spanner</artifactId>
</dependency>
Configuration
Configure Cloud Spanner instance and database to connect to.
spring.cloud.gcp.spanner.instance-id=spanner-instance
spring.cloud.gcp.spanner.database=orders
ORM
Spring Data Cloud Spanner allows you to map domain POJOs to Cloud Spanner tables via annotations. Read the Spring Data Spanner reference documentation for details
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import org.springframework.cloud.gcp.data.spanner.core.mapping.*;
@Table(name="orders")
@Data // Lombok to generate getter/setters
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;
}
import lombok.Data;
import org.springframework.cloud.gcp.data.spanner.core.mapping.*;
@Table(name="order_items")
@Data // Lombok to generate getter/setters
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;
}
Repository
Use Spring Data repository to quickly get CRUD access to the Cloud Spanner tables.
package com.example.demo;
import org.springframework.cloud.gcp.data.spanner.repository.SpannerRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderRepository extends SpannerRepository<Order, String> {
}
package com.example.demo;
import lombok.Data;
import org.springframework.cloud.gcp.data.spanner.core.mapping.*;
@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;
}
In a business logic service, you can utilize the repositories:
@Service
class OrderService {
private final OrderRepository orderRepository;
OrderService(OrderRepository orderRepository,
OrderItemRepository orderItemRepository) {
this.orderRepository = orderRepository;
}
@Transactional
Order createOrder(Order order) {
// Use UUID String representation for the ID
order.setId(UUID.randomUUID().toString());
// Set the creation time
order.setCreationTimestamp(LocalDateTime.now());
// Set the parent Order ID and children ID for each item.
if (order.getItems() != null) {
order.getItems().stream().forEach(orderItem -> {
orderItem.setOrderId(order.getId());
orderItem.setOrderItemId(UUID.randomUUID().toString());
});
}
// Children are saved in cascade.
return orderRepository.save(order);
}
}
Rest Repository
Spring Data Rest can expose a Spring Data repository directly on a RESTful endpoint, and rendering the payload as JSON with HATEOS format. It supports common access patterns like pagination.
Add Spring Data Rest starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
@RepositoryRestResource
interface OrderRepository extends SpannerRepository<Order, String> {
}
@RepositoryRestResource
interface OrderItemRepository extends SpannerRepository<OrderItem, Key> {
List<OrderItem> findAllByOrderId(String orderId);
}
To access the endpoint for Order:
curl http://localhost:8080/orders
Samples
JDBC
Cloud Spanner JDBC Driver can be used if you need raw JDBC access.
Dependency
Add Cloud Spanner JDBC Driver:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-jdbc</artifactId>
</dependency>
Use Spring Boot JDBC Starter to use JDBC Template:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
Configuration
Cloud Spanner JDBC Driver Class:
com.google.cloud.spanner.jdbc.JdbcDriver
Cloud Spanner JDBC URL format:
jdbc:cloudspanner:/projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_NAME
With Spring Data JDBC, you can configure the datasource:
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.datasource.url=jdbc:cloudspanner:/projects/PROJECT_ID/instances/spanner-instance/databases/orders
Hibernate
Cloud Spanner Hibernate Dialect lets you use Cloud Spanner with Hibernate ORM. You can use Cloud Spanner with Hibernate from any Java application. When using Spring Boot, you can use the Hibernate Dialect with Spring Data JPA.
Dependency
Add both the Cloud Spanner JDBC driver and Cloud Spanner Hibernate Dialect:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
</dependency>
Add Spring Data JPA starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Configuration
Cloud Spanner Hibernate Dialect class:
com.google.cloud.spanner.hibernate.SpannerDialect
Configure Spring Data JPA:
spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver
spring.datasource.url=jdbc:cloudspanner:/projects/PROJECT_ID/instances/spanner-instance/databases/orders
spring.jpa.database-platform=com.google.cloud.spanner.hibernate.SpannerDialect
ORM
Use JPA annotations to map POJO to Cloud Spanner database tables.
@Entity
@Table(name="orders")
class Order {
@Id
@Column(name="order_id")
private String id;
private String description;
@Column(name="creation_timestamp")
private LocalDateTime creationTimestamp;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> items;
// Getter and Setter ...
}
@Embeddable
class OrderItemId implements Serializable {
@Column(name="order_id")
private String orderId;
@Column(name="order_item_id")
private String orderItemId;
// Getter and Setter ...
// Hashcode and Equals ...
}
@Entity
@Table(name="order_items")
class OrderItem {
@EmbeddedId
private OrderItemId orderItemId;
private String description;
private Long quantity;
@ManyToOne
@JoinColumn(name="order_id", insertable = false, updatable = false)
private Order order;
// Getter and Setter ...
}
Repository
Use Spring Data repository for CRUD access to the tables.
@Repository
interface OrderRepository extends JpaRepository<Order, String> {
}
@Repository
interface OrderItemRepository extends JpaRepository<OrderItem, OrderItemId> {
}
Rest Repository
Use Spring Data Rest to expose the repositories as RESTful services with HATEOS format.
@RepositoryRestResource
interface OrderRepository extends JpaRepository<Order, String> {
}
@RepositoryRestResource
interface OrderItemRepository extends JpaRepository<OrderItem, OrderItemId> {
}
Sample
R2DBC
Cloud Spanner R2DBC driver let's you access Cloud Spanner data with reactive API.
Cloud Spanner R2DBC driver is under active development.
It can be used with Spring Data R2DBC using the Cloud Spanner R2DBC Dialect.
Samples
Last updated
Was this helpful?