465 lines
101 KiB
HTML
465 lines
101 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>13. Spring Data Cloud Spanner</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-gcp.html" title="Spring Cloud GCP"><link rel="up" href="multi_spring-cloud-gcp.html" title="Spring Cloud GCP"><link rel="prev" href="multi__spring_cloud_config.html" title="12. Spring Cloud Config"><link rel="next" href="multi__spring_data_cloud_datastore.html" title="14. Spring Data Cloud Datastore"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">13. Spring Data Cloud Spanner</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_config.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi__spring_data_cloud_datastore.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_data_cloud_spanner" href="#_spring_data_cloud_spanner"></a>13. Spring Data Cloud Spanner</h1></div></div></div><p><a class="link" href="https://projects.spring.io/spring-data/" target="_top">Spring Data</a> is an abstraction for storing and retrieving POJOs in numerous storage technologies.
|
|
Spring Cloud GCP adds Spring Data support for <a class="link" href="https://cloud.google.com/spanner/" target="_top">Google Cloud Spanner</a>.</p><p>Maven coordinates for this module only, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-gcp-data-spanner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
|
|
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-data-spanner'
|
|
}</pre><p>We provide a <a class="link" href="../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner" target="_top">Spring Boot Starter for Spring Data Spanner</a>, with which you can leverage our recommended auto-configuration setup.
|
|
To use the starter, see the coordinates see below.</p><p>Maven:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-gcp-starter-data-spanner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>Gradle:</p><pre class="screen">dependencies {
|
|
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-data-spanner'
|
|
}</pre><p>This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Spanner libraries as well.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_4" href="#_configuration_4"></a>13.1 Configuration</h2></div></div></div><p>To setup Spring Data Cloud Spanner, you have to configure the following:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Setup the connection details to Google Cloud Spanner.</li><li class="listitem">Enable Spring Data Repositories (optional).</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_cloud_spanner_settings" href="#_cloud_spanner_settings"></a>13.1.1 Cloud Spanner settings</h3></div></div></div><p>You can the use <a class="link" href="../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-spanner" target="_top">Spring Boot Starter for Spring Data Spanner</a> to autoconfigure Google Cloud Spanner in your Spring application.
|
|
It contains all the necessary setup that makes it easy to authenticate with your Google Cloud project.
|
|
The following configuration options are available:</p><div class="informaltable"><table class="informaltable" style="border-collapse: collapse;border-top: 1px solid ; border-bottom: 1px solid ; border-left: 1px solid ; border-right: 1px solid ; "><colgroup><col class="col_1"><col class="col_2"><col class="col_3"><col class="col_4"></colgroup><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Name</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Description</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Required</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Default value</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.instance-id</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Cloud Spanner instance to use</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"> </td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.database</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Cloud Spanner database to use</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"> </td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.project-id</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>GCP project ID where the Google Cloud Spanner API is hosted, if different from the one in the <a class="link" href="multi_spring-cloud-gcp-core.html" title="4. Spring Cloud GCP Core">Spring Cloud GCP Core Module</a></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"> </td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.credentials.location</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>OAuth2 credentials for authenticating with the
|
|
Google Cloud Spanner API, if different from the ones in the
|
|
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4. Spring Cloud GCP Core">Spring Cloud GCP Core Module</a></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"> </td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.credentials.encoded-key</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Base64-encoded OAuth2 credentials for authenticating with the
|
|
Google Cloud Spanner API, if different from the ones in the
|
|
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4. Spring Cloud GCP Core">Spring Cloud GCP Core Module</a></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"> </td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.credentials.scopes</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://developers.google.com/identity/protocols/googlescopes" target="_top">OAuth2 scope</a> for Spring Cloud GCP
|
|
Cloud Spanner credentials</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/spanner.data" target="_top">https://www.googleapis.com/auth/spanner.data</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>If <code class="literal">true</code>, then schema statements generated by <code class="literal">SpannerSchemaUtils</code> for tables with interleaved parent-child relationships will be "ON DELETE CASCADE".
|
|
The schema for the tables will be "ON DELETE NO ACTION" if <code class="literal">false</code>.</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.numRpcChannels</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Number of gRPC channels used to connect to Cloud Spanner</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>4 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.prefetchChunks</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Number of chunks prefetched by Cloud Spanner for read and query</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>4 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.minSessions</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Minimum number of sessions maintained in the session pool</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>0 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.maxSessions</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Maximum number of sessions session pool can have</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>400 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.maxIdleSessions</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Maximum number of idle sessions session pool will maintain</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>0 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.writeSessionsFraction</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Fraction of sessions to be kept prepared for write transactions</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>0.2 - Determined by Cloud Spanner client library</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.spanner.keepAliveIntervalMinutes</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>How long to keep idle sessions alive</p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="" align="left" valign="top"><p>30 - Determined by Cloud Spanner client library</p></td></tr></tbody></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_repository_settings" href="#_repository_settings"></a>13.1.2 Repository settings</h3></div></div></div><p>Spring Data Repositories can be configured via the <code class="literal">@EnableSpannerRepositories</code> annotation on your main <code class="literal">@Configuration</code> class.
|
|
With our Spring Boot Starter for Spring Data Cloud Spanner, <code class="literal">@EnableSpannerRepositories</code> is automatically added.
|
|
It is not required to add it to any other class, unless there is a need to override finer grain configuration parameters provided by <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/blob/master/spring-cloud-gcp-data-spanner/src/main/java/org/springframework/cloud/gcp/data/spanner/repository/config/EnableSpannerRepositories.java" target="_top"><code class="literal">@EnableSpannerRepositories</code></a>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_autoconfiguration" href="#_autoconfiguration"></a>13.1.3 Autoconfiguration</h3></div></div></div><p>Our Spring Boot autoconfiguration creates the following beans available in the Spring application context:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">an instance of <code class="literal">SpannerTemplate</code></li><li class="listitem">an instance of <code class="literal">SpannerDatabaseAdminTemplate</code> for generating table schemas from object hierarchies and creating and deleting tables and databases</li><li class="listitem">an instance of all user-defined repositories extending <code class="literal">SpannerRepository</code>, <code class="literal">CrudRepository</code>, <code class="literal">PagingAndSortingRepository</code>, when repositories are enabled</li><li class="listitem">an instance of <code class="literal">DatabaseClient</code> from the Google Cloud Java Client for Spanner, for convenience and lower level API access</li></ul></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_object_mapping" href="#_object_mapping"></a>13.2 Object Mapping</h2></div></div></div><p>Spring Data Cloud Spanner allows you to map domain POJOs to Cloud Spanner tables via annotations:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "traders")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Trader {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Column(name = "trader_id")</xslthl:annotation>
|
|
String traderId;
|
|
|
|
String firstName;
|
|
|
|
String lastName;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@NotMapped</xslthl:annotation>
|
|
Double temporaryNumber;
|
|
}</pre><p>Spring Data Cloud Spanner will ignore any property annotated with <code class="literal">@NotMapped</code>.
|
|
These properties will not be written to or read from Spanner.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_constructors" href="#_constructors"></a>13.2.1 Constructors</h3></div></div></div><p>Simple constructors are supported on POJOs.
|
|
The constructor arguments can be a subset of the persistent properties.
|
|
Every constructor argument needs to have the same name and type as a persistent property on the entity and the constructor should set the property from the given argument.
|
|
Arguments that are not directly set to properties are not supported.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "traders")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Trader {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Column(name = "trader_id")</xslthl:annotation>
|
|
String traderId;
|
|
|
|
String firstName;
|
|
|
|
String lastName;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@NotMapped</xslthl:annotation>
|
|
Double temporaryNumber;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Trader(String traderId, String firstName) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.traderId = traderId;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.firstName = firstName;
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_table" href="#_table"></a>13.2.2 Table</h3></div></div></div><p>The <code class="literal">@Table</code> annotation can provide the name of the Cloud Spanner table that stores instances of the annotated class, one per row.
|
|
This annotation is optional, and if not given, the name of the table is inferred from the class name with the first character uncapitalized.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_spel_expressions_for_table_names" href="#_spel_expressions_for_table_names"></a>SpEL expressions for table names</h4></div></div></div><p>In some cases, you might want the <code class="literal">@Table</code> table name to be determined dynamically.
|
|
To do that, you can use <a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions" target="_top">Spring Expression Language</a>.</p><p>For example:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "trades_#{tableNameSuffix}")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Trade {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// ...</span>
|
|
}</pre><p>The table name will be resolved only if the <code class="literal">tableNameSuffix</code> value/bean in the Spring application context is defined.
|
|
For example, if <code class="literal">tableNameSuffix</code> has the value "123", the table name will resolve to <code class="literal">trades_123</code>.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_primary_keys" href="#_primary_keys"></a>13.2.3 Primary Keys</h3></div></div></div><p>For a simple table, you may only have a primary key consisting of a single column.
|
|
Even in that case, the <code class="literal">@PrimaryKey</code> annotation is required.
|
|
<code class="literal">@PrimaryKey</code> identifies the one or more ID properties corresponding to the primary key.</p><p>Spanner has first class support for composite primary keys of multiple columns.
|
|
You have to annotate all of your POJO’s fields that the primary key consists of with <code class="literal">@PrimaryKey</code> as below:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "trades")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Trade {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey(keyOrder = 2)</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Column(name = "trade_id")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> String tradeId;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey(keyOrder = 1)</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Column(name = "trader_id")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> String traderId;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> String action;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> Double price;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> Double shares;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> String symbol;
|
|
}</pre><p>The <code class="literal">keyOrder</code> parameter of <code class="literal">@PrimaryKey</code> identifies the properties corresponding to the primary key columns in order, starting with 1 and increasing consecutively.
|
|
Order is important and must reflect the order defined in the Cloud Spanner schema.
|
|
In our example the DDL to create the table and its primary key is as follows:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">CREATE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">TABLE</span> trades (
|
|
trader_id STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
trade_id STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">action</span> STRING(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">15</xslthl:number>),
|
|
symbol STRING(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">10</xslthl:number>),
|
|
price FLOAT64,
|
|
shares FLOAT64
|
|
) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">PRIMARY</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">KEY</span> (trader_id, trade_id)</pre><p>Spanner does not have automatic ID generation.
|
|
For most use-cases, sequential IDs should be used with caution to avoid creating data hotspots in the system.
|
|
Read <a class="link" href="https://cloud.google.com/spanner/docs/schema-and-data-model#primary_keys" target="_top">Spanner Primary Keys documentation</a> for a better understanding of primary keys and recommended practices.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_columns" href="#_columns"></a>13.2.4 Columns</h3></div></div></div><p>All accessible properties on POJOs are automatically recognized as a Cloud Spanner column.
|
|
Column naming is generated by the <code class="literal">PropertyNameFieldNamingStrategy</code> by default defined on the <code class="literal">SpannerMappingContext</code> bean.
|
|
The <code class="literal">@Column</code> annotation optionally provides a different column name than that of the property and some other settings:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">name</code> is the optional name of the column</li><li class="listitem"><code class="literal">spannerTypeMaxLength</code> specifies for <code class="literal">STRING</code> and <code class="literal">BYTES</code> columns the maximum length.
|
|
This setting is only used when generating DDL schema statements based on domain types.</li><li class="listitem"><code class="literal">nullable</code> specifies if the column is created as <code class="literal">NOT NULL</code>.
|
|
This setting is only used when generating DDL schema statements based on domain types.</li><li class="listitem"><code class="literal">spannerType</code> is the Cloud Spanner column type you can optionally specify.
|
|
If this is not specified then a compatible column type is inferred from the Java property type.</li><li class="listitem"><code class="literal">spannerCommitTimestamp</code> is a boolean specifying if this property corresponds to an auto-populated commit timestamp column.
|
|
Any value set in this property will be ignored when writing to Cloud Spanner.</li></ul></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_embedded_objects" href="#_embedded_objects"></a>13.2.5 Embedded Objects</h3></div></div></div><p>If an object of type <code class="literal">B</code> is embedded as a property of <code class="literal">A</code>, then the columns of <code class="literal">B</code> will be saved in the same Cloud Spanner table as those of <code class="literal">A</code>.</p><p>If <code class="literal">B</code> has primary key columns, those columns will be included in the primary key of <code class="literal">A</code>. <code class="literal">B</code> can also have embedded properties.
|
|
Embedding allows reuse of columns between multiple entities, and can be useful for implementing parent-child situations, because Cloud Spanner requires child tables to include the key columns of their parents.</p><p>For example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> X {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
String grandParentId;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> age;
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> A {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Embedded</xslthl:annotation>
|
|
X grandParent;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey(keyOrder = 2)</xslthl:annotation>
|
|
String parentId;
|
|
|
|
String value;
|
|
}
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "items")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> B {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Embedded</xslthl:annotation>
|
|
A parent;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey(keyOrder = 2)</xslthl:annotation>
|
|
String id;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Column(name = "child_value")</xslthl:annotation>
|
|
String value;
|
|
}</pre><p>Entities of <code class="literal">B</code> can be stored in a table defined as:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">CREATE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">TABLE</span> items (
|
|
grandParentId STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
parentId STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
id STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">value</span> STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
child_value STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
age INT64
|
|
) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">PRIMARY</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">KEY</span> (grandParentId, parentId, id)</pre><p>Note that embedded properties' column names must all be unique.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_relationships" href="#_relationships"></a>13.2.6 Relationships</h3></div></div></div><p>Spring Data Cloud Spanner supports parent-child relationships using the Cloud Spanner <a class="link" href="https://cloud.google.com/spanner/docs/schema-and-data-model#creating-interleaved-tables" target="_top">parent-child interleaved table mechanism</a>.
|
|
Cloud Spanner interleaved tables enforce the one-to-many relationship and provide efficient queries and operations on entities of a single domain parent entity.
|
|
These relationships can be up to 7 levels deep.
|
|
Cloud Spanner also provides automatic cascading delete or enforces the deletion of child entities before parents.</p><p>While one-to-one and many-to-many relationships can be implemented in Cloud Spanner and Spring Data Cloud Spanner using constructs of interleaved parent-child tables, only the parent-child relationship is natively supported.
|
|
Cloud Spanner does not support the foreign key constraint, though the parent-child key constraint enforces a similar requirement when used with interleaved tables.</p><p>For example, the following Java entities:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "Singers")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Singer {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> SingerId;
|
|
|
|
String FirstName;
|
|
|
|
String LastName;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">byte</span>[] SingerInfo;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Interleaved</xslthl:annotation>
|
|
List<Album> albums;
|
|
}
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "Albums")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Album {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> SingerId;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PrimaryKey(keyOrder = 2)</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> AlbumId;
|
|
|
|
String AlbumTitle;
|
|
}</pre><p>These classes can correspond to an existing pair of interleaved tables.
|
|
The <code class="literal">@Interleaved</code> annotation may be applied to <code class="literal">Collection</code> properties and the inner type is resolved as the child entity type.
|
|
The schema needed to create them can also be generated using the <code class="literal">SpannerSchemaUtils</code> and executed using the <code class="literal">SpannerDatabaseAdminTemplate</code>:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerSchemaUtils schemaUtils;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerDatabaseAdminTemplate databaseAdmin;
|
|
...
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Get the create statmenets for all tables in the table structure rooted at Singer</span>
|
|
List<String> createStrings = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.schemaUtils.getCreateTableDdlStringsForInterleavedHierarchy(Singer.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Create the tables and also create the database if necessary</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.databaseAdmin.executeDdlStrings(createStrings, true);</pre><p>The <code class="literal">createStrings</code> list contains table schema statements using column names and types compatible with the provided Java type and any resolved child relationship types contained within based on the configured custom converters.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">CREATE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">TABLE</span> Singers (
|
|
SingerId INT64 <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NOT</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NULL</span>,
|
|
FirstName STRING(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">1024</xslthl:number>),
|
|
LastName STRING(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">1024</xslthl:number>),
|
|
SingerInfo BYTES(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">PRIMARY</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">KEY</span> (SingerId);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">CREATE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">TABLE</span> Albums (
|
|
SingerId INT64 <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NOT</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NULL</span>,
|
|
AlbumId INT64 <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NOT</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">NULL</span>,
|
|
AlbumTitle STRING(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">MAX</span>),
|
|
) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">PRIMARY</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">KEY</span> (SingerId, AlbumId),
|
|
INTERLEAVE <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">IN</span> PARENT Singers <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">ON</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">DELETE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">CASCADE</span>;</pre><p>The <code class="literal">ON DELETE CASCADE</code> clause indicates that Cloud Spanner will delete all Albums of a singer if the Singer is deleted.
|
|
The alternative is <code class="literal">ON DELETE NO ACTION</code>, where a Singer cannot be deleted until all of its Albums have already been deleted.
|
|
When using <code class="literal">SpannerSchemaUtils</code> to generate the schema strings, the <code class="literal">spring.cloud.gcp.spanner.createInterleavedTableDdlOnDeleteCascade</code> boolean setting determines if these schema are generated as <code class="literal">ON DELETE CASCADE</code> for <code class="literal">true</code> and <code class="literal">ON DELETE NO ACTION</code> for <code class="literal">false</code>.</p><p>Cloud Spanner restricts these relationships to 7 child layers.
|
|
A table may have multiple child tables.</p><p>On updating or inserting an object to Cloud Spanner, all of its referenced children objects are also updated or inserted in the same request, respectively.
|
|
On read, all of the interleaved child rows are also all read.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_supported_types" href="#_supported_types"></a>13.2.7 Supported Types</h3></div></div></div><p>Spring Data Cloud Spanner natively supports the following types for regular fields but also utilizes custom converters (detailed in following sections) and dozens of pre-defined Spring Data custom converters to handle other common Java types.</p><p>Natively supported types:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">com.google.cloud.ByteArray</code></li><li class="listitem"><code class="literal">com.google.cloud.Date</code></li><li class="listitem"><code class="literal">com.google.cloud.Timestamp</code></li><li class="listitem"><code class="literal">java.lang.Boolean</code>, <code class="literal">boolean</code></li><li class="listitem"><code class="literal">java.lang.Double</code>, <code class="literal">double</code></li><li class="listitem"><code class="literal">java.lang.Long</code>, <code class="literal">long</code></li><li class="listitem"><code class="literal">java.lang.Integer</code>, <code class="literal">int</code></li><li class="listitem"><code class="literal">java.lang.String</code></li><li class="listitem"><code class="literal">double[]</code></li><li class="listitem"><code class="literal">long[]</code></li><li class="listitem"><code class="literal">boolean[]</code></li><li class="listitem"><code class="literal">java.util.Date</code></li><li class="listitem"><code class="literal">java.util.Instant</code></li><li class="listitem"><code class="literal">java.sql.Date</code></li></ul></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_lists" href="#_lists"></a>13.2.8 Lists</h3></div></div></div><p>Spanner supports <code class="literal">ARRAY</code> types for columns.
|
|
<code class="literal">ARRAY</code> columns are mapped to <code class="literal">List</code> fields in POJOS.</p><p>Example:</p><pre class="programlisting">List<Double> curve;</pre><p>The types inside the lists can be any singular property type.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_lists_of_structs" href="#_lists_of_structs"></a>13.2.9 Lists of Structs</h3></div></div></div><p>Cloud Spanner queries can <a class="link" href="https://cloud.google.com/spanner/docs/query-syntax#using-structs-with-select" target="_top">construct STRUCT values</a> that appear as columns in the result.
|
|
Cloud Spanner requires STRUCT values appear in ARRAYs at the root level: <code class="literal">SELECT ARRAY(SELECT STRUCT(1 as val1, 2 as val2)) as pair FROM Users</code>.</p><p>Spring Data Cloud Spanner will attempt to read the column STRUCT values into a property that is an <code class="literal">Iterable</code> of an entity type compatible with the schema of the column STRUCT value.</p><p>For the previous array-select example, the following property can be mapped with the constructed <code class="literal">ARRAY<STRUCT></code> column: <code class="literal">List<TwoInts> pair;</code> where the <code class="literal">TwoInts</code> type is defined:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> TwoInts {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> val1;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> val2;
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_types" href="#_custom_types"></a>13.2.10 Custom types</h3></div></div></div><p>Custom converters can be used to extend the type support for user defined types.</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Converters need to implement the <code class="literal">org.springframework.core.convert.converter.Converter</code> interface in both directions.</li><li class="listitem"><p class="simpara">The user defined type needs to be mapped to one of the basic types supported by Spanner:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">com.google.cloud.ByteArray</code></li><li class="listitem"><code class="literal">com.google.cloud.Date</code></li><li class="listitem"><code class="literal">com.google.cloud.Timestamp</code></li><li class="listitem"><code class="literal">java.lang.Boolean</code>, <code class="literal">boolean</code></li><li class="listitem"><code class="literal">java.lang.Double</code>, <code class="literal">double</code></li><li class="listitem"><code class="literal">java.lang.Long</code>, <code class="literal">long</code></li><li class="listitem"><code class="literal">java.lang.String</code></li><li class="listitem"><code class="literal">double[]</code></li><li class="listitem"><code class="literal">long[]</code></li><li class="listitem"><code class="literal">boolean[]</code></li><li class="listitem"><code class="literal">enum</code> types</li></ul></div></li><li class="listitem">An instance of both Converters needs to be passed to a <code class="literal">ConverterAwareMappingSpannerEntityProcessor</code>, which then has to be made available as a <code class="literal">@Bean</code> for <code class="literal">SpannerEntityProcessor</code>.</li></ol></div><p>For example:</p><p>We would like to have a field of type <code class="literal">Person</code> on our <code class="literal">Trade</code> POJO:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Table(name = "trades")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Trade {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
Person person;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}</pre><p>Where Person is a simple class:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Person {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String firstName;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String lastName;
|
|
|
|
}</pre><p>We have to define the two converters:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> PersonWriteConverter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">implements</span> Converter<Person, String> {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Override</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String convert(Person person) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> person.firstName + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" "</span> + person.lastName;
|
|
}
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> PersonReadConverter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">implements</span> Converter<String, Person> {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Override</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Person convert(String s) {
|
|
Person person = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Person();
|
|
person.firstName = s.split(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" "</span>)[<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">0</xslthl:number>];
|
|
person.lastName = s.split(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" "</span>)[<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">1</xslthl:number>];
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> person;
|
|
}
|
|
}</pre><p>That will be configured in our <code class="literal">@Configuration</code> file:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Configuration</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> ConverterConfiguration {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> SpannerEntityProcessor spannerEntityProcessor(SpannerMappingContext spannerMappingContext) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> ConverterAwareMappingSpannerEntityProcessor(spannerMappingContext,
|
|
Arrays.asList(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> PersonWriteConverter()),
|
|
Arrays.asList(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> PersonReadConverter()));
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_converter_for_struct_array_columns" href="#_custom_converter_for_struct_array_columns"></a>13.2.11 Custom Converter for Struct Array Columns</h3></div></div></div><p>If a <code class="literal">Converter<Struct, A></code> is provided, then properties of type <code class="literal">List<A></code> can be used in your entity types.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_spanner_operations_template" href="#_spanner_operations_template"></a>13.3 Spanner Operations & Template</h2></div></div></div><p><code class="literal">SpannerOperations</code> and its implementation, <code class="literal">SpannerTemplate</code>, provides the Template pattern familiar to Spring developers.
|
|
It provides:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Resource management</li><li class="listitem">One-stop-shop to Spanner operations with the Spring Data POJO mapping and conversion features</li><li class="listitem">Exception conversion</li></ul></div><p>Using the <code class="literal">autoconfigure</code> provided by our Spring Boot Starter for Spanner, your Spring application context will contain a fully configured <code class="literal">SpannerTemplate</code> object that you can easily autowire in your application:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@SpringBootApplication</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> SpannerTemplateExample {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerTemplate spannerTemplate;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> doSomething() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.delete(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, KeySet.all());
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
Trade t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trade();
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.insert(t);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
List<Trade> tradesByAction = spannerTemplate.findAll(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
}</pre><p>The Template API provides convenience methods for:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara"><a class="link" href="https://cloud.google.com/spanner/docs/reads" target="_top">Reads</a>, and by providing SpannerReadOptions and
|
|
SpannerQueryOptions</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">Stale read</li><li class="listitem">Read with secondary indices</li><li class="listitem">Read with limits and offsets</li><li class="listitem">Read with sorting</li></ul></div></li><li class="listitem"><a class="link" href="https://cloud.google.com/spanner/docs/reads#execute_a_query" target="_top">Queries</a></li><li class="listitem">DML operations (delete, insert, update, upsert)</li><li class="listitem"><p class="simpara">Partial reads</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">You can define a set of columns to be read into your entity</li></ul></div></li><li class="listitem"><p class="simpara">Partial writes</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">Persist only a few properties from your entity</li></ul></div></li><li class="listitem">Read-only transactions</li><li class="listitem">Locking read-write transactions</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_sql_query" href="#_sql_query"></a>13.3.1 SQL Query</h3></div></div></div><p>Cloud Spanner has SQL support for running read-only queries.
|
|
All the query related methods start with <code class="literal">query</code> on <code class="literal">SpannerTemplate</code>.
|
|
Using <code class="literal">SpannerTemplate</code> you can execute SQL queries that map to POJOs:</p><pre class="programlisting">List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.query(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, Statement.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"SELECT * FROM trades"</span>));</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_read" href="#_read"></a>13.3.2 Read</h3></div></div></div><p>Spanner exposes a <a class="link" href="https://cloud.google.com/spanner/docs/reads" target="_top">Read API</a> for reading single row or multiple rows in a table or in a secondary index.</p><p>Using <code class="literal">SpannerTemplate</code> you can execute reads, for example:</p><pre class="programlisting">List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.readAll(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);</pre><p>Main benefit of reads over queries is reading multiple rows of a certain pattern of keys is much easier using the features of the <a class="link" href="https://github.com/GoogleCloudPlatform/google-cloud-java/blob/master/google-cloud-spanner/src/main/java/com/google/cloud/spanner/KeySet.java" target="_top"><code class="literal">KeySet</code></a> class.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_advanced_reads" href="#_advanced_reads"></a>13.3.3 Advanced reads</h3></div></div></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_stale_read" href="#_stale_read"></a>Stale read</h4></div></div></div><p>All reads and queries are <span class="strong"><strong>strong reads</strong></span> by default.
|
|
A <span class="strong"><strong>strong read</strong></span> is a read at a current timestamp and is guaranteed to see all data that has been committed up until the start of this read.
|
|
A <span class="strong"><strong>stale read</strong></span> on the other hand is read at a timestamp in the past.
|
|
Cloud Spanner allows you to determine how current the data should be when you read data.
|
|
With <code class="literal">SpannerTemplate</code> you can specify the <code class="literal">Timestamp</code> by setting it on <code class="literal">SpannerQueryOptions</code> or <code class="literal">SpannerReadOptions</code> to the appropriate read or query methods:</p><p>Reads:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// a read with options:</span>
|
|
SpannerReadOptions spannerReadOptions = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpannerReadOptions().setTimestamp(Timestamp.now());
|
|
List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.readAll(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, spannerReadOptions);</pre><p>Queries:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// a query with options:</span>
|
|
SpannerQueryOptions spannerQueryOptions = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpannerQueryOptions().setTimestamp(Timestamp.now());
|
|
List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.query(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, Statement.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"SELECT * FROM trades"</span>), spannerQueryOptions);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_read_from_a_secondary_index" href="#_read_from_a_secondary_index"></a>Read from a secondary index</h4></div></div></div><p>Using a <a class="link" href="https://cloud.google.com/spanner/docs/secondary-indexes" target="_top">secondary index</a> is available for Reads via the Template API and it is also implicitly available via SQL for Queries.</p><p>The following shows how to read rows from a table using a <a class="link" href="https://cloud.google.com/spanner/docs/secondary-indexes" target="_top">secondary index</a> simply by setting <code class="literal">index</code> on <code class="literal">SpannerReadOptions</code>:</p><pre class="programlisting">SpannerReadOptions spannerReadOptions = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpannerReadOptions().setIndex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"TradesByTrader"</span>);
|
|
List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.readAll(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, spannerReadOptions);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_read_with_offsets_and_limits" href="#_read_with_offsets_and_limits"></a>Read with offsets and limits</h4></div></div></div><p>Limits and offsets are only supported by Queries.
|
|
The following will get only the first two rows of the query:</p><pre class="programlisting">SpannerQueryOptions spannerQueryOptions = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpannerQueryOptions().setLimit(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">2</xslthl:number>).setOffset(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">3</xslthl:number>);
|
|
List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.query(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, Statement.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"SELECT * FROM trades"</span>), spannerQueryOptions);</pre><p>Note that the above is equivalent of executing <code class="literal">SELECT * FROM trades LIMIT 2 OFFSET 3</code>.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_sorting" href="#_sorting"></a>Sorting</h4></div></div></div><p>Reads by keys do not support sorting.
|
|
However, queries on the Template API support sorting through standard SQL and also via Spring Data Sort API:</p><pre class="programlisting">List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.queryAll(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, Sort.by(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"action"</span>));</pre><p>If the provided sorted field name is that of a property of the domain type, then the column name corresponding to that property will be used in the query.
|
|
Otherwise, the given field name is assumed to be the name of the column in the Cloud Spanner table.
|
|
Sorting on columns of Cloud Spanner types STRING and BYTES can be done while ignoring case:</p><pre class="programlisting">Sort.by(Order.desc(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"action"</span>).ignoreCase())</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_partial_read" href="#_partial_read"></a>Partial read</h4></div></div></div><p>Partial read is only possible when using Queries.
|
|
In case the rows returned by the query have fewer columns than the entity that it will be mapped to, Spring Data will map the returned columns only.
|
|
This setting also applies to nested structs and their corresponding nested POJO properties.</p><pre class="programlisting">List<Trade> trades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.query(Trade.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, Statement.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"SELECT action, symbol FROM trades"</span>),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpannerQueryOptions().setAllowMissingResultSetColumns(true));</pre><p>If the setting is set to <code class="literal">false</code>, then an exception will be thrown if there are missing columns in the query result.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_summary_of_options_for_query_vs_read" href="#_summary_of_options_for_query_vs_read"></a>Summary of options for Query vs Read</h4></div></div></div><div class="informaltable"><table class="informaltable" style="border-collapse: collapse;border-top: 1px solid ; border-bottom: 1px solid ; border-left: 1px solid ; border-right: 1px solid ; "><colgroup><col class="col_1"><col class="col_2"><col class="col_3"></colgroup><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Feature</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Query supports it</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Read supports it</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>SQL</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>no</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Partial read</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>no</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Limits</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>no</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Offsets</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>no</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Secondary index</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Read using index range</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>no</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>yes</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p>Sorting</p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>yes</p></td><td style="" align="left" valign="top"><p>no</p></td></tr></tbody></table></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_write_update" href="#_write_update"></a>13.3.4 Write / Update</h3></div></div></div><p>The write methods of <code class="literal">SpannerOperations</code> accept a POJO and writes all of its properties to Spanner.
|
|
The corresponding Spanner table and entity metadata is obtained from the given object’s actual type.</p><p>If a POJO was retrieved from Spanner and its primary key properties values were changed and then written or updated, the operation will occur as if against a row with the new primary key values.
|
|
The row with the original primary key values will not be affected.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_insert" href="#_insert"></a>Insert</h4></div></div></div><p>The <code class="literal">insert</code> method of <code class="literal">SpannerOperations</code> accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if a row with the POJO’s primary key already exists in the table.</p><pre class="programlisting">Trade t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trade();
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.insert(t);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_update" href="#_update"></a>Update</h4></div></div></div><p>The <code class="literal">update</code> method of <code class="literal">SpannerOperations</code> accepts a POJO and writes all of its properties to Spanner, which means the operation will fail if the POJO’s primary key does not already exist in the table.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// t was retrieved from a previous operation</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.update(t);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_upsert" href="#_upsert"></a>Upsert</h4></div></div></div><p>The <code class="literal">upsert</code> method of <code class="literal">SpannerOperations</code> accepts a POJO and writes all of its properties to Spanner using update-or-insert.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// t was retrieved from a previous operation or it's new</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.upsert(t);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_partial_update" href="#_partial_update"></a>Partial Update</h4></div></div></div><p>The update methods of <code class="literal">SpannerOperations</code> operate by default on all properties within the given object, but also accept <code class="literal">String[]</code> and <code class="literal">Optional<Set<String>></code> of column names.
|
|
If the <code class="literal">Optional</code> of set of column names is empty, then all columns are written to Spanner.
|
|
However, if the Optional is occupied by an empty set, then no columns will be written.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// t was retrieved from a previous operation or it's new</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.update(t, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"symbol"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"action"</span>);</pre></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_dml" href="#_dml"></a>13.3.5 DML</h3></div></div></div><p>DML statements can be executed using <code class="literal">SpannerOperations.executeDmlStatement</code>.
|
|
Inserts, updates, and deletions can affect any number of rows and entities.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_transactions" href="#_transactions"></a>13.3.6 Transactions</h3></div></div></div><p><code class="literal">SpannerOperations</code> provides methods to run <code class="literal">java.util.Function</code> objects within a single transaction while making available the read and write methods from <code class="literal">SpannerOperations</code>.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_readwrite_transaction" href="#_readwrite_transaction"></a>Read/Write Transaction</h4></div></div></div><p>Read and write transactions are provided by <code class="literal">SpannerOperations</code> via the <code class="literal">performReadWriteTransaction</code> method:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerOperations mySpannerOperations;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String doWorkInsideTransaction() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> mySpannerOperations.performReadWriteTransaction(
|
|
transActionSpannerOperations -> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Work with transActionSpannerOperations here.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// It is also a SpannerOperations object.</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transaction completed"</span>;
|
|
}
|
|
);
|
|
}</pre><p>The <code class="literal">performReadWriteTransaction</code> method accepts a <code class="literal">Function</code> that is provided an instance of a <code class="literal">SpannerOperations</code> object.
|
|
The final returned value and type of the function is determined by the user.
|
|
You can use this object just as you would a regular <code class="literal">SpannerOperations</code> with a few exceptions:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Its read functionality cannot perform stale reads, because all reads and writes happen at the single point in time of the transaction.</li><li class="listitem">It cannot perform sub-transactions via <code class="literal">performReadWriteTransaction</code> or <code class="literal">performReadOnlyTransaction</code>.</li></ul></div><p>As these read-write transactions are locking, it is recommended that you use the <code class="literal">performReadOnlyTransaction</code> if your function does not perform any writes.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_read_only_transaction" href="#_read_only_transaction"></a>Read-only Transaction</h4></div></div></div><p>The <code class="literal">performReadOnlyTransaction</code> method is used to perform read-only transactions using a <code class="literal">SpannerOperations</code>:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerOperations mySpannerOperations;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String doWorkInsideTransaction() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> mySpannerOperations.performReadOnlyTransaction(
|
|
transActionSpannerOperations -> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Work with transActionSpannerOperations here.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// It is also a SpannerOperations object.</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transaction completed"</span>;
|
|
}
|
|
);
|
|
}</pre><p>The <code class="literal">performReadOnlyTransaction</code> method accepts a <code class="literal">Function</code> that is provided an instance of a
|
|
<code class="literal">SpannerOperations</code> object.
|
|
This method also accepts a <code class="literal">ReadOptions</code> object, but the only attribute used is the timestamp used to determine the snapshot in time to perform the reads in the transaction.
|
|
If the timestamp is not set in the read options the transaction is run against the current state of the database.
|
|
The final returned value and type of the function is determined by the user.
|
|
You can use this object just as you would a regular <code class="literal">SpannerOperations</code> with
|
|
a few exceptions:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Its read functionality cannot perform stale reads, because all reads happen at the single point in time of the transaction.</li><li class="listitem">It cannot perform sub-transactions via <code class="literal">performReadWriteTransaction</code> or <code class="literal">performReadOnlyTransaction</code></li><li class="listitem">It cannot perform any write operations.</li></ul></div><p>Because read-only transactions are non-locking and can be performed on points in time in the past, these are recommended for functions that do not perform write operations.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_declarative_transactions_with_transactional_annotation" href="#_declarative_transactions_with_transactional_annotation"></a>Declarative Transactions with @Transactional Annotation</h4></div></div></div><p>This feature requires a bean of <code class="literal">SpannerTransactionManager</code>, which is provided when using <code class="literal">spring-cloud-gcp-starter-data-spanner</code>.</p><p><code class="literal">SpannerTemplate</code> and <code class="literal">SpannerRepository</code> support running methods with the <code class="literal">@Transactional</code> [annotation](<a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative" target="_top">https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative</a>) as transactions.
|
|
If a method annotated with <code class="literal">@Transactional</code> calls another method also annotated, then both methods will work within the same transaction.
|
|
<code class="literal">performReadOnlyTransaction</code> and <code class="literal">performReadWriteTransaction</code> cannot be used in <code class="literal">@Transactional</code> annotated methods because Cloud Spanner does not support transactions within transactions.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_dml_statements" href="#_dml_statements"></a>13.3.7 DML Statements</h3></div></div></div><p><code class="literal">SpannerTemplate</code> supports [DML](<a class="link" href="https://cloud.google.com/spanner/docs/dml-tasks" target="_top">https://cloud.google.com/spanner/docs/dml-tasks</a>) <code class="literal">Statements</code>.
|
|
DML statements can be executed in transactions via <code class="literal">performReadWriteTransaction</code> or using the <code class="literal">@Transactional</code> annotation.</p><p>When DML statements are executed outside of transactions, they are executed in [partitioned-mode](<a class="link" href="https://cloud.google.com/spanner/docs/dml-tasks#partitioned-dml" target="_top">https://cloud.google.com/spanner/docs/dml-tasks#partitioned-dml</a>).</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_repositories" href="#_repositories"></a>13.4 Repositories</h2></div></div></div><p><a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#repositories" target="_top">Spring Data Repositories</a> are a powerful abstraction that can save you a lot of boilerplate code.</p><p>For example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TraderRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trader, String> {
|
|
}</pre><p>Spring Data generates a working implementation of the specified interface, which can be conveniently autowired into an application.</p><p>The <code class="literal">Trader</code> type parameter to <code class="literal">SpannerRepository</code> refers to the underlying domain type.
|
|
The second type parameter, <code class="literal">String</code> in this case, refers to the type of the key of the domain type.</p><p>For POJOs with a composite primary key, this ID type parameter can be any descendant of <code class="literal">Object[]</code> compatible with all primary key properties, any descendant of <code class="literal">Iterable</code>, or <code class="literal">com.google.cloud.spanner.Key</code>.
|
|
If the domain POJO type only has a single primary key column, then the primary key property type can be used or the <code class="literal">Key</code> type.</p><p>For example in case of Trades, that belong to a Trader, <code class="literal">TradeRepository</code> would look like this:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, String[]> {
|
|
|
|
}</pre><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> MyApplication {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerTemplate spannerTemplate;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
StudentRepository studentRepository;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> demo() {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.tradeRepository.deleteAll();
|
|
String traderId = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"demo_trader"</span>;
|
|
Trade t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trade();
|
|
t.symbol = stock;
|
|
t.action = action;
|
|
t.traderId = traderId;
|
|
t.price = <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">100.0</xslthl:number>;
|
|
t.shares = <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">12345.6</xslthl:number>;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.spannerTemplate.insert(t);
|
|
|
|
Iterable<Trade> allTrades = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.tradeRepository.findAll();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> count = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.tradeRepository.countByAction(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"BUY"</span>);
|
|
|
|
}
|
|
}</pre><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_crud_repository" href="#_crud_repository"></a>13.4.1 CRUD Repository</h3></div></div></div><p><code class="literal">CrudRepository</code> methods work as expected, with one thing Spanner specific: the <code class="literal">save</code> and <code class="literal">saveAll</code> methods work as update-or-insert.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_paging_and_sorting_repository" href="#_paging_and_sorting_repository"></a>13.4.2 Paging and Sorting Repository</h3></div></div></div><p>You can also use <code class="literal">PagingAndSortingRepository</code> with Spanner Spring Data.
|
|
The sorting and pageable <code class="literal">findAll</code> methods available from this interface operate on the current state of the Spanner database.
|
|
As a result, beware that the state of the database (and the results) might change when moving page to page.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_spanner_repository" href="#_spanner_repository"></a>13.4.3 Spanner Repository</h3></div></div></div><p>The <code class="literal">SpannerRepository</code> extends the <code class="literal">PagingAndSortingRepository</code>, but adds the read-only and the read-write transaction functionality provided by Spanner.
|
|
These transactions work very similarly to those of <code class="literal">SpannerOperations</code>, but is specific to the repository’s domain type and provides repository functions instead of template functions.</p><p>For example, this is a read-write transaction:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
SpannerRepository myRepo;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String doWorkInsideTransaction() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> myRepo.performReadOnlyTransaction(
|
|
transactionSpannerRepo -> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Work with the single-transaction transactionSpannerRepo here.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// This is a SpannerRepository object.</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transaction completed"</span>;
|
|
}
|
|
);
|
|
}</pre><p>When creating custom repositories for your own domain types and query methods, you can extend <code class="literal">SpannerRepository</code> to access Cloud Spanner-specific features as well as all features from <code class="literal">PagingAndSortingRepository</code> and <code class="literal">CrudRepository</code>.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_query_methods" href="#_query_methods"></a>13.5 Query Methods</h2></div></div></div><p><code class="literal">SpannerRepository</code> supports Query Methods.
|
|
Described in the following sections, these are methods residing in your custom repository interfaces of which implementations are generated based on their names and annotations.
|
|
Query Methods can read, write, and delete entities in Cloud Spanner.
|
|
Parameters to these methods can be any Cloud Spanner data type supported directly or via custom configured converters.
|
|
Parameters can also be of type <code class="literal">Struct</code> or POJOs.
|
|
If a POJO is given as a parameter, it will be converted to a <code class="literal">Struct</code> with the same type-conversion logic as used to create write mutations.
|
|
Comparisons using Struct parameters are limited to <a class="link" href="https://cloud.google.com/spanner/docs/data-types#limited-comparisons-for-struct" target="_top">what is available with Cloud Spanner</a>.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_query_methods_by_convention" href="#_query_methods_by_convention"></a>13.5.1 Query methods by convention</h3></div></div></div><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, String[]> {
|
|
List<Trade> findByAction(String action);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> countByAction(String action);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Named methods are powerful, but can get unwieldy</span>
|
|
List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(
|
|
String action, String symbol, String traderId);
|
|
}</pre><p>In the example above, the <a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#repositories.query-methods" target="_top">query methods</a> in <code class="literal">TradeRepository</code> are generated based on the name of the methods, using the <a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation" target="_top">Spring Data Query creation naming convention</a>.</p><p><code class="literal">List<Trade> findByAction(String action)</code> would translate to a <code class="literal">SELECT * FROM trades WHERE action = ?</code>.</p><p>The function <code class="literal">List<Trade> findTop3DistinctByActionAndSymbolIgnoreCaseOrTraderIdOrderBySymbolDesc(String action, String symbol, String traderId);</code> will be translated as the equivalent of this SQL query:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">SELECT</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">DISTINCT</span> * <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">FROM</span> trades
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">WHERE</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">ACTION</span> = ? <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">AND</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">LOWER</span>(SYMBOL) = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">LOWER</span>(?) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">AND</span> TRADER_ID = ?
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">ORDER</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">BY</span> SYMBOL <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">DESC</span>
|
|
LIMIT <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">3</xslthl:number></pre><p>The following filter options are supported:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Equality</li><li class="listitem">Greater than or equals</li><li class="listitem">Greater than</li><li class="listitem">Less than or equals</li><li class="listitem">Less than</li><li class="listitem">Is null</li><li class="listitem">Is not null</li><li class="listitem">Is true</li><li class="listitem">Is false</li><li class="listitem">Like a string</li><li class="listitem">Not like a string</li><li class="listitem">Contains a string</li><li class="listitem">Not contains a string</li></ul></div><p>Note that the phrase <code class="literal">SymbolIgnoreCase</code> is translated to <code class="literal">LOWER(SYMBOL) = LOWER(?)</code> indicating a non-case-sensitive matching.
|
|
The <code class="literal">IgnoreCase</code> phrase may only be appended to fields that correspond to columns of type STRING or BYTES.
|
|
The Spring Data "AllIgnoreCase" phrase appended at the end of the method name is not supported.</p><p>The <code class="literal">Like</code> or <code class="literal">NotLike</code> naming conventions:</p><pre class="programlisting">List<Trade> findBySymbolLike(String symbolFragment);</pre><p>The param <code class="literal">symbolFragment</code> can contain <a class="link" href="https://cloud.google.com/spanner/docs/functions-and-operators#comparison-operators" target="_top">wildcard characters</a> for string matching such as <code class="literal">_</code> and <code class="literal">%</code>.</p><p>The <code class="literal">Contains</code> and <code class="literal">NotContains</code> naming conventions:</p><pre class="programlisting">List<Trade> findBySymbolContains(String symbolFragment);</pre><p>The param <code class="literal">symbolFragment</code> is a <a class="link" href="https://cloud.google.com/spanner/docs/functions-and-operators#regexp_contains" target="_top">regular expression</a> that is checked for occurrences.</p><p>Delete queries are also supported.
|
|
For example, query methods such as <code class="literal">deleteByAction</code> or <code class="literal">removeByAction</code> delete entities found by <code class="literal">findByAction</code>.
|
|
The delete operation happens in a single transaction.</p><p>Delete queries can have the following return types:
|
|
* An integer type that is the number of entities deleted
|
|
* A collection of entities that were deleted
|
|
* <code class="literal">void</code></p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_sqldml_query_methods" href="#_custom_sqldml_query_methods"></a>13.5.2 Custom SQL/DML query methods</h3></div></div></div><p>The example above for <code class="literal">List<Trade> fetchByActionNamedQuery(String action)</code> does not match the <a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation" target="_top">Spring Data Query creation naming convention</a>, so we have to map a parametrized Spanner SQL query to it.</p><p>The SQL query for the method can be mapped to repository methods in one of two ways:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">namedQueries</code> properties file</li><li class="listitem">using the <code class="literal">@Query</code> annotation</li></ul></div><p>The names of the tags of the SQL correspond to the <code class="literal">@Param</code> annotated names of the method parameters.</p><p>Custom SQL query methods can accept a single <code class="literal">Sort</code> or <code class="literal">Pageable</code> parameter that is applied on top of any sorting or paging in the SQL:</p><pre class="programlisting"> <xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM trades ORDER BY action DESC")</xslthl:annotation>
|
|
List<Trade> sortedTrades(Pageable pageable);
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM trades ORDER BY action DESC LIMIT 1")</xslthl:annotation>
|
|
Trade sortedTopTrade(Pageable pageable);</pre><p>This can be used:</p><pre class="programlisting"> List<Trade> customSortedTrades = tradeRepository.sortedTrades(PageRequest
|
|
.of(<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">2</xslthl:number>, <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">2</xslthl:number>, org.springframework.data.domain.Sort.by(Order.asc(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"id"</span>))));</pre><p>The results would be sorted by "id" in ascending order.</p><p>Your query method can also return non-entity types:</p><pre class="programlisting"> <xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT COUNT(1) FROM trades WHERE action = @action")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> countByActionQuery(String action);
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT EXISTS(SELECT COUNT(1) FROM trades WHERE action = @action)")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">boolean</span> existsByActionQuery(String action);
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT action FROM trades WHERE action = @action LIMIT 1")</xslthl:annotation>
|
|
String getFirstString(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("action")</xslthl:annotation> String action);
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT action FROM trades WHERE action = @action")</xslthl:annotation>
|
|
List<String> getFirstStringList(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("action")</xslthl:annotation> String action);</pre><p>DML statements can also be executed by query methods, but the only possible return value is a <code class="literal">long</code> representing the number of affected rows.
|
|
The <code class="literal">dmlStatement</code> boolean setting must be set on <code class="literal">@Query</code> to indicate that the query method is executed as a DML statement.</p><pre class="programlisting"> <xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query(value = "DELETE FROM trades WHERE action = @action", dmlStatement = true)</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> deleteByActionQuery(String action);</pre><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_query_methods_with_named_queries_properties" href="#_query_methods_with_named_queries_properties"></a>Query methods with named queries properties</h4></div></div></div><p>By default, the <code class="literal">namedQueriesLocation</code> attribute on <code class="literal">@EnableSpannerRepositories</code> points to the <code class="literal">META-INF/spanner-named-queries.properties</code> file.
|
|
You can specify the query for a method in the properties file by providing the SQL as the value for the "interface.method" property:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">Trade.fetchByActionNamedQuery</span>=SELECT * FROM trades WHERE trades.action = @tag0</pre><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, String[]> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// This method uses the query from the properties file instead of one generated based on name.</span>
|
|
List<Trade> fetchByActionNamedQuery(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("tag0")</xslthl:annotation> String action);
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_query_methods_with_annotation" href="#_query_methods_with_annotation"></a>Query methods with annotation</h4></div></div></div><p>Using the <code class="literal">@Query</code> annotation:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, String[]> {
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM trades WHERE trades.action = @tag0")</xslthl:annotation>
|
|
List<Trade> fetchByActionNamedQuery(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("tag0")</xslthl:annotation> String action);
|
|
}</pre><p>Table names can be used directly.
|
|
For example, "trades" in the above example.
|
|
Alternatively, table names can be resolved from the <code class="literal">@Table</code> annotation on domain classes as well.
|
|
In this case, the query should refer to table names with fully qualified class names between <code class="literal">:</code>
|
|
characters: <code class="literal">:fully.qualified.ClassName:</code>.
|
|
A full example would look like:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0")</xslthl:annotation>
|
|
List<Trade> fetchByActionNamedQuery(String action);</pre><p>This allows table names evaluated with SpEL to be used in custom queries.</p><p>SpEL can also be used to provide SQL parameters:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM :com.example.Trade: WHERE trades.action = @tag0
|
|
AND price > #{#priceRadius * -1} AND price < #{#priceRadius * 2}")</xslthl:annotation>
|
|
List<Trade> fetchByActionNamedQuery(String action, Double priceRadius);</pre></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_projections" href="#_projections"></a>13.5.3 Projections</h3></div></div></div><p>Spring Data Spanner supports <a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#projections" target="_top">projections</a>.
|
|
You can define projection interfaces based on domain types and add query methods that return them in your repository:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeProjection {
|
|
|
|
String getAction();
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Value("#{target.symbol + ' ' + target.action}")</xslthl:annotation>
|
|
String getSymbolAndAction();
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, Key> {
|
|
|
|
List<Trade> findByTraderId(String traderId);
|
|
|
|
List<TradeProjection> findByAction(String action);
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT action, symbol FROM trades WHERE action = @action")</xslthl:annotation>
|
|
List<TradeProjection> findByQuery(String action);
|
|
}</pre><p>Projections can be provided by name-convention-based query methods as well as by custom SQL queries.
|
|
If using custom SQL queries, you can further restrict the columns retrieved from Spanner to just those required by the projection to improve performance.</p><p>Properties of projection types defined using SpEL use the fixed name <code class="literal">target</code> for the underlying domain object.
|
|
As a result accessing underlying properties take the form <code class="literal">target.<property-name></code>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_rest_repositories" href="#_rest_repositories"></a>13.5.4 REST Repositories</h3></div></div></div><p>When running with Spring Boot, repositories can be exposed as REST services by simply adding this dependency to your pom file:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.boot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-boot-starter-data-rest<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>If you prefer to configure parameters (such as path), you can use <code class="literal">@RepositoryRestResource</code> annotation:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@RepositoryRestResource(collectionResourceRel = "trades", path = "trades")</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TradeRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerRepository<Trade, String[]> {
|
|
}</pre><p>For example, you can retrieve all <code class="literal">Trade</code> objects in the repository by using <code class="literal">curl http://<server>:<port>/trades</code>, or any specific trade via <code class="literal">curl http://<server>:<port>/trades/<trader_id>,<trade_id></code>.</p><p>The separator between your primary key components, <code class="literal">id</code> and <code class="literal">trader_id</code> in this case, is a comma by default, but can be configured to any string not found in your key values by extending the <code class="literal">SpannerKeyIdConverter</code> class:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Component</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> MySpecialIdConverter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> SpannerKeyIdConverter {
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Override</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> String getUrlIdSeparator() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":"</span>;
|
|
}
|
|
}</pre><p>You can also write trades using <code class="literal">curl -XPOST -H"Content-Type: application/json" -<a class="link" href="mailto:d@test.json" target="_top">d@test.json</a> http://<server>:<port>/trades/</code> where the file <code class="literal">test.json</code> holds the JSON representation of a <code class="literal">Trade</code> object.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_database_and_schema_admin" href="#_database_and_schema_admin"></a>13.6 Database and Schema Admin</h2></div></div></div><p>Databases and tables inside Spanner instances can be created automatically from <code class="literal">SpannerPersistentEntity</code> objects:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> SpannerSchemaUtils spannerSchemaUtils;
|
|
|
|
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> SpannerDatabaseAdminTemplate spannerDatabaseAdminTemplate;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> createTable(SpannerPersistentEntity entity) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">if</span>(!spannerDatabaseAdminTemplate.tableExists(entity.tableName()){
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// The boolean parameter indicates that the database will be created if it does not exist.</span>
|
|
spannerDatabaseAdminTemplate.executeDdlStrings(Arrays.asList(
|
|
spannerSchemaUtils.getCreateTableDDLString(entity.getType())), true);
|
|
}
|
|
}</pre><p>Schemas can be generated for entire object hierarchies with interleaved relationships and composite keys.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_9" href="#_sample_9"></a>13.7 Sample</h2></div></div></div><p>A <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-sample" target="_top">sample application</a> is available.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__spring_cloud_config.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="multi__spring_data_cloud_datastore.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">12. Spring Cloud Config </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-gcp.html">Home</a></td><td width="40%" align="right" valign="top"> 14. Spring Data Cloud Datastore</td></tr></table></div></body></html> |