Sync docs from v1.1.5.RELEASE to gh-pages

This commit is contained in:
buildmaster
2020-01-31 22:59:00 +00:00
parent d5f79f0c53
commit f053f59796
70 changed files with 8761 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
require 'asciidoctor'
require 'erb'
guard 'shell' do
watch(/.*\.adoc$/) {|m|
Asciidoctor.render_file('index.adoc', \
:in_place => true, \
:safe => Asciidoctor::SafeMode::UNSAFE, \
:attributes=> { \
'source-highlighter' => 'prettify', \
'icons' => 'font', \
'linkcss'=> 'true', \
'copycss' => 'true', \
'doctype' => 'book'})
}
end
guard 'livereload' do
watch(%r{^.+\.(css|js|html)$})
end

View File

@@ -0,0 +1,35 @@
/*
code highlight CSS resemblign the Eclipse IDE default color schema
@author Costin Leau
*/
.hl-keyword {
color: #7F0055;
font-weight: bold;
}
.hl-comment {
color: #3F5F5F;
font-style: italic;
}
.hl-multiline-comment {
color: #3F5FBF;
font-style: italic;
}
.hl-tag {
color: #3F7F7F;
}
.hl-attribute {
color: #7F007F;
}
.hl-value {
color: #2A00FF;
}
.hl-string {
color: #2A00FF;
}

View File

@@ -0,0 +1,9 @@
@IMPORT url("manual.css");
body.firstpage {
background: url("../images/background.png") no-repeat center top;
}
div.part h1 {
border-top: none;
}

View File

@@ -0,0 +1,6 @@
@IMPORT url("manual.css");
body {
background: url("../images/background.png") no-repeat center top;
}

View File

@@ -0,0 +1,342 @@
@IMPORT url("highlight.css");
html {
padding: 0pt;
margin: 0pt;
}
body {
color: #333333;
margin: 15px 30px;
font-family: Helvetica, Arial, Freesans, Clean, Sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
code {
font-size: 16px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
:not(a) > code {
color: #6D180B;
}
:not(pre) > code {
background-color: #F2F2F2;
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 1px 3px 0;
text-shadow: none;
white-space: nowrap;
}
body > *:first-child {
margin-top: 0 !important;
}
div {
margin: 0pt;
}
hr {
border: 1px solid #CCCCCC;
background: #CCCCCC;
}
h1, h2, h3, h4, h5, h6 {
color: #000000;
cursor: text;
font-weight: bold;
margin: 30px 0 10px;
padding: 0;
}
h1, h2, h3 {
margin: 40px 0 10px;
}
h1 {
margin: 70px 0 30px;
padding-top: 20px;
}
div.part h1 {
border-top: 1px dotted #CCCCCC;
}
h1, h1 code {
font-size: 32px;
}
h2, h2 code {
font-size: 24px;
}
h3, h3 code {
font-size: 20px;
}
h4, h1 code, h5, h5 code, h6, h6 code {
font-size: 18px;
}
div.book, div.chapter, div.appendix, div.part, div.preface {
min-width: 300px;
max-width: 1200px;
margin: 0 auto;
}
p.releaseinfo {
font-weight: bold;
margin-bottom: 40px;
margin-top: 40px;
}
div.authorgroup {
line-height: 1;
}
p.copyright {
line-height: 1;
margin-bottom: -5px;
}
.legalnotice p {
font-style: italic;
font-size: 14px;
line-height: 1;
}
div.titlepage + p, div.titlepage + p {
margin-top: 0;
}
pre {
line-height: 1.0;
color: black;
}
a {
color: #4183C4;
text-decoration: none;
}
p {
margin: 15px 0;
text-align: left;
}
ul, ol {
padding-left: 30px;
}
li p {
margin: 0;
}
div.table {
margin: 1em;
padding: 0.5em;
text-align: center;
}
div.table table, div.informaltable table {
display: table;
width: 100%;
}
div.table td {
padding-left: 7px;
padding-right: 7px;
}
.sidebar {
line-height: 1.4;
padding: 0 20px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
}
.sidebar p.title {
color: #6D180B;
}
pre.programlisting, pre.screen {
font-size: 15px;
padding: 6px 10px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
clear: both;
overflow: auto;
line-height: 1.4;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #DDDDDD !important;
border-radius: 4px !important;
border-collapse: separate !important;
line-height: 1.6;
}
table thead {
background: #F5F5F5;
}
table tr {
border: none;
border-bottom: none;
}
table th {
font-weight: bold;
}
table th, table td {
border: none !important;
padding: 6px 13px;
}
table tr:nth-child(2n) {
background-color: #F8F8F8;
}
td p {
margin: 0 0 15px 0;
}
div.table-contents td p {
margin: 0;
}
div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * {
border: none !important;
background: none !important;
margin: 0;
}
div.important p, div.note p, div.tip p, div.warning p {
color: #6F6F6F;
line-height: 1.6;
}
div.important code, div.note code, div.tip code, div.warning code {
background-color: #F2F2F2 !important;
border: 1px solid #CCCCCC !important;
border-radius: 4px !important;
padding: 1px 3px 0 !important;
text-shadow: none !important;
white-space: nowrap !important;
}
.note th, .tip th, .warning th {
display: none;
}
.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td {
border-right: 1px solid #CCCCCC !important;
padding-top: 10px;
}
div.calloutlist p, div.calloutlist td {
padding: 0;
margin: 0;
}
div.calloutlist > table > tbody > tr > td:first-child {
padding-left: 10px;
width: 30px !important;
}
div.important, div.note, div.tip, div.warning {
margin-left: 0px !important;
margin-right: 20px !important;
margin-top: 20px;
margin-bottom: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
div.toc {
line-height: 1.2;
}
dl, dt {
margin-top: 1px;
margin-bottom: 0;
}
div.toc > dl > dt {
font-size: 32px;
font-weight: bold;
margin: 30px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dt {
font-size: 24px;
font-weight: bold;
margin: 20px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dd > dl > dt {
font-weight: bold;
font-size: 20px;
margin: 10px 0 0 0;
}
tbody.footnotes * {
border: none !important;
}
div.footnote p {
margin: 0;
line-height: 1;
}
div.footnote p sup {
margin-right: 6px;
vertical-align: middle;
}
div.navheader {
border-bottom: 1px solid #CCCCCC;
}
div.navfooter {
border-top: 1px solid #CCCCCC;
}
.title {
margin-left: -1em;
padding-left: 1em;
}
.title > a {
position: absolute;
visibility: hidden;
display: block;
font-size: 0.85em;
margin-top: 0.05em;
margin-left: -1em;
vertical-align: text-top;
color: black;
}
.title > a:before {
content: "\00A7";
}
.title:hover > a, .title > a:hover, .title:hover > a:hover {
visibility: visible;
}
.title:focus > a, .title > a:focus, .title:focus > a:focus {
outline: 0;
}

View File

@@ -0,0 +1,330 @@
#!/bin/bash -x
set -e
# Set default props like MAVEN_PATH, ROOT_FOLDER etc.
function set_default_props() {
# The script should be executed from the root folder
ROOT_FOLDER=`pwd`
echo "Current folder is ${ROOT_FOLDER}"
if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then
echo "You're not in the root folder of the project!"
exit 1
fi
# Prop that will let commit the changes
COMMIT_CHANGES="no"
MAVEN_PATH=${MAVEN_PATH:-}
echo "Path to Maven is [${MAVEN_PATH}]"
REPO_NAME=${PWD##*/}
echo "Repo name is [${REPO_NAME}]"
SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git}
echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}"
}
# Check if gh-pages exists and docs have been built
function check_if_anything_to_sync() {
git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'`
if ! (git remote set-branches --add origin gh-pages && git fetch -q); then
echo "No gh-pages, so not syncing"
exit 0
fi
if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then
echo "No gh-pages sources in docs/target/generated-docs, so not syncing"
exit 0
fi
}
function retrieve_current_branch() {
# Code getting the name of the current branch. For master we want to publish as we did until now
# https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
# If there is a branch already passed will reuse it - otherwise will try to find it
CURRENT_BRANCH=${BRANCH}
if [[ -z "${CURRENT_BRANCH}" ]] ; then
CURRENT_BRANCH=$(git symbolic-ref -q HEAD)
CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/}
CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD}
fi
echo "Current branch is [${CURRENT_BRANCH}]"
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
}
# Switches to the provided value of the release version. We always prefix it with `v`
function switch_to_tag() {
git checkout v${VERSION}
}
# Build the docs if switch is on
function build_docs_if_applicable() {
if [[ "${BUILD}" == "yes" ]] ; then
./mvnw clean install -P docs -pl docs -DskipTests
fi
}
# Get the name of the `docs.main` property
# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile
function retrieve_doc_properties() {
MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \
-Dexec.executable="echo" \
-Dexec.args='${docs.main}' \
--non-recursive \
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec)
echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]"
WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"}
WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \
-Dexec.executable="echo" \
-Dexec.args="\${${WHITELIST_PROPERTY}}" \
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \
-P docs \
-pl docs)
echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]"
}
# Stash any outstanding changes
function stash_changes() {
git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1")
if [ "$dirty" != "0" ]; then git stash; fi
}
# Switch to gh-pages branch to sync it with current branch
function add_docs_from_target() {
local DESTINATION_REPO_FOLDER
if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then
DESTINATION_REPO_FOLDER=${ROOT_FOLDER}
elif [[ "${CLONE}" == "yes" ]]; then
mkdir -p ${ROOT_FOLDER}/target
local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static
if [[ ! -e "${clonedStatic}/.git" ]]; then
echo "Cloning Spring Cloud Static to target"
git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages
else
echo "Spring Cloud Static already cloned - will pull changes"
cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages
fi
DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME}
mkdir -p ${DESTINATION_REPO_FOLDER}
else
if [[ ! -e "${DESTINATION}/.git" ]]; then
echo "[${DESTINATION}] is not a git repository"
exit 1
fi
DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME}
mkdir -p ${DESTINATION_REPO_FOLDER}
echo "Destination was provided [${DESTINATION}]"
fi
cd ${DESTINATION_REPO_FOLDER}
git checkout gh-pages
git pull origin gh-pages
# Add git branches
###################################################################
if [[ -z "${VERSION}" ]] ; then
copy_docs_for_current_version
else
copy_docs_for_provided_version
fi
commit_changes_if_applicable
}
# Copies the docs by using the retrieved properties from Maven build
function copy_docs_for_current_version() {
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then
echo -e "Current branch is master - will copy the current docs only to the root folder"
for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
# Not ignored...
cp -rf $f ${ROOT_FOLDER}/
git add -A ${ROOT_FOLDER}/$file
fi
done
COMMIT_CHANGES="yes"
else
echo -e "Current branch is [${CURRENT_BRANCH}]"
# https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin
if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then
mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH}
echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder"
for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
# Not ignored...
# We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then
# We don't want to copy the spring-cloud-sleuth.html
# we want it to be converted to index.html
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
else
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file
fi
fi
done
COMMIT_CHANGES="yes"
else
echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in
[docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch."
fi
fi
}
# Copies the docs by using the explicitly provided version
function copy_docs_for_provided_version() {
local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION}
mkdir -p ${FOLDER}
echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder"
for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do
file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*}
copy_docs_for_branch ${file} ${FOLDER}
done
COMMIT_CHANGES="yes"
CURRENT_BRANCH="v${VERSION}"
}
# Copies the docs from target to the provided destination
# Params:
# $1 - file from target
# $2 - destination to which copy the files
function copy_docs_for_branch() {
local file=$1
local destination=$2
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then
# Not ignored...
# We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then
# We don't want to copy the spring-cloud-sleuth.html
# we want it to be converted to index.html
cp -rf $f ${destination}/index.html
git add -A ${destination}/index.html
else
cp -rf $f ${destination}
git add -A ${destination}/$file
fi
fi
}
function commit_changes_if_applicable() {
if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then
COMMIT_SUCCESSFUL="no"
git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes"
# Uncomment the following push if you want to auto push to
# the gh-pages branch whenever you commit to master locally.
# This is a little extreme. Use with care!
###################################################################
if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then
git push origin gh-pages
fi
fi
}
# Switch back to the previous branch and exit block
function checkout_previous_branch() {
# If -version was provided we need to come back to root project
cd ${ROOT_FOLDER}
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
if [ "$dirty" != "0" ]; then git stash pop; fi
exit 0
}
# Assert if properties have been properly passed
function assert_properties() {
echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]"
if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi
if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi
if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi
}
# Prints the usage
function print_usage() {
cat <<EOF
The idea of this script is to update gh-pages branch with the generated docs. Without any options
the script will work in the following manner:
- if there's no gh-pages / target for docs module then the script ends
- for master branch the generated docs are copied to the root of gh-pages branch
- for any other branch (if that branch is whitelisted) a subfolder with branch name is created
and docs are copied there
- if the version switch is passed (-v) then a tag with (v) prefix will be retrieved and a folder
with that version number will be created in the gh-pages branch. WARNING! No whitelist verification will take place
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
USAGE:
You can use the following options:
-v|--version - the script will apply the whole procedure for a particular library version
-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path.
E.g. point to spring-cloud-static folder. Can't be used with (-c)
-b|--build - will run the standard build process after checking out the branch
-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination.
Obviously can't be used with (-d)
EOF
}
# ==========================================
# ____ ____ _____ _____ _____ _______
# / ____|/ ____| __ \|_ _| __ \__ __|
# | (___ | | | |__) | | | | |__) | | |
# \___ \| | | _ / | | | ___/ | |
# ____) | |____| | \ \ _| |_| | | |
# |_____/ \_____|_| \_\_____|_| |_|
#
# ==========================================
while [[ $# > 0 ]]
do
key="$1"
case ${key} in
-v|--version)
VERSION="$2"
shift # past argument
;;
-d|--destination)
DESTINATION="$2"
shift # past argument
;;
-b|--build)
BUILD="yes"
;;
-c|--clone)
CLONE="yes"
;;
-h|--help)
print_usage
exit 0
;;
*)
echo "Invalid option: [$1]"
print_usage
exit 1
;;
esac
shift # past argument or value
done
assert_properties
set_default_props
check_if_anything_to_sync
if [[ -z "${VERSION}" ]] ; then
retrieve_current_branch
else
switch_to_tag
fi
build_docs_if_applicable
retrieve_doc_properties
stash_changes
add_docs_from_target
checkout_previous_branch

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,35 @@
/*
code highlight CSS resemblign the Eclipse IDE default color schema
@author Costin Leau
*/
.hl-keyword {
color: #7F0055;
font-weight: bold;
}
.hl-comment {
color: #3F5F5F;
font-style: italic;
}
.hl-multiline-comment {
color: #3F5FBF;
font-style: italic;
}
.hl-tag {
color: #3F7F7F;
}
.hl-attribute {
color: #7F007F;
}
.hl-value {
color: #2A00FF;
}
.hl-string {
color: #2A00FF;
}

View File

@@ -0,0 +1,9 @@
@IMPORT url("manual.css");
body.firstpage {
background: url("../images/background.png") no-repeat center top;
}
div.part h1 {
border-top: none;
}

View File

@@ -0,0 +1,6 @@
@IMPORT url("manual.css");
body {
background: url("../images/background.png") no-repeat center top;
}

View File

@@ -0,0 +1,342 @@
@IMPORT url("highlight.css");
html {
padding: 0pt;
margin: 0pt;
}
body {
color: #333333;
margin: 15px 30px;
font-family: Helvetica, Arial, Freesans, Clean, Sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
code {
font-size: 16px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
:not(a) > code {
color: #6D180B;
}
:not(pre) > code {
background-color: #F2F2F2;
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 1px 3px 0;
text-shadow: none;
white-space: nowrap;
}
body > *:first-child {
margin-top: 0 !important;
}
div {
margin: 0pt;
}
hr {
border: 1px solid #CCCCCC;
background: #CCCCCC;
}
h1, h2, h3, h4, h5, h6 {
color: #000000;
cursor: text;
font-weight: bold;
margin: 30px 0 10px;
padding: 0;
}
h1, h2, h3 {
margin: 40px 0 10px;
}
h1 {
margin: 70px 0 30px;
padding-top: 20px;
}
div.part h1 {
border-top: 1px dotted #CCCCCC;
}
h1, h1 code {
font-size: 32px;
}
h2, h2 code {
font-size: 24px;
}
h3, h3 code {
font-size: 20px;
}
h4, h1 code, h5, h5 code, h6, h6 code {
font-size: 18px;
}
div.book, div.chapter, div.appendix, div.part, div.preface {
min-width: 300px;
max-width: 1200px;
margin: 0 auto;
}
p.releaseinfo {
font-weight: bold;
margin-bottom: 40px;
margin-top: 40px;
}
div.authorgroup {
line-height: 1;
}
p.copyright {
line-height: 1;
margin-bottom: -5px;
}
.legalnotice p {
font-style: italic;
font-size: 14px;
line-height: 1;
}
div.titlepage + p, div.titlepage + p {
margin-top: 0;
}
pre {
line-height: 1.0;
color: black;
}
a {
color: #4183C4;
text-decoration: none;
}
p {
margin: 15px 0;
text-align: left;
}
ul, ol {
padding-left: 30px;
}
li p {
margin: 0;
}
div.table {
margin: 1em;
padding: 0.5em;
text-align: center;
}
div.table table, div.informaltable table {
display: table;
width: 100%;
}
div.table td {
padding-left: 7px;
padding-right: 7px;
}
.sidebar {
line-height: 1.4;
padding: 0 20px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
}
.sidebar p.title {
color: #6D180B;
}
pre.programlisting, pre.screen {
font-size: 15px;
padding: 6px 10px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
clear: both;
overflow: auto;
line-height: 1.4;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #DDDDDD !important;
border-radius: 4px !important;
border-collapse: separate !important;
line-height: 1.6;
}
table thead {
background: #F5F5F5;
}
table tr {
border: none;
border-bottom: none;
}
table th {
font-weight: bold;
}
table th, table td {
border: none !important;
padding: 6px 13px;
}
table tr:nth-child(2n) {
background-color: #F8F8F8;
}
td p {
margin: 0 0 15px 0;
}
div.table-contents td p {
margin: 0;
}
div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * {
border: none !important;
background: none !important;
margin: 0;
}
div.important p, div.note p, div.tip p, div.warning p {
color: #6F6F6F;
line-height: 1.6;
}
div.important code, div.note code, div.tip code, div.warning code {
background-color: #F2F2F2 !important;
border: 1px solid #CCCCCC !important;
border-radius: 4px !important;
padding: 1px 3px 0 !important;
text-shadow: none !important;
white-space: nowrap !important;
}
.note th, .tip th, .warning th {
display: none;
}
.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td {
border-right: 1px solid #CCCCCC !important;
padding-top: 10px;
}
div.calloutlist p, div.calloutlist td {
padding: 0;
margin: 0;
}
div.calloutlist > table > tbody > tr > td:first-child {
padding-left: 10px;
width: 30px !important;
}
div.important, div.note, div.tip, div.warning {
margin-left: 0px !important;
margin-right: 20px !important;
margin-top: 20px;
margin-bottom: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
div.toc {
line-height: 1.2;
}
dl, dt {
margin-top: 1px;
margin-bottom: 0;
}
div.toc > dl > dt {
font-size: 32px;
font-weight: bold;
margin: 30px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dt {
font-size: 24px;
font-weight: bold;
margin: 20px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dd > dl > dt {
font-weight: bold;
font-size: 20px;
margin: 10px 0 0 0;
}
tbody.footnotes * {
border: none !important;
}
div.footnote p {
margin: 0;
line-height: 1;
}
div.footnote p sup {
margin-right: 6px;
vertical-align: middle;
}
div.navheader {
border-bottom: 1px solid #CCCCCC;
}
div.navfooter {
border-top: 1px solid #CCCCCC;
}
.title {
margin-left: -1em;
padding-left: 1em;
}
.title > a {
position: absolute;
visibility: hidden;
display: block;
font-size: 0.85em;
margin-top: 0.05em;
margin-left: -1em;
vertical-align: text-top;
color: black;
}
.title > a:before {
content: "\00A7";
}
.title:hover > a, .title > a:hover, .title:hover > a:hover {
visibility: visible;
}
.title:focus > a, .title > a:focus, .title:focus > a:focus {
outline: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,8 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>18.&nbsp;Cloud Foundry</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__google_cloud_vision.html" title="17.&nbsp;Google Cloud Vision"><link rel="next" href="multi__kotlin_support.html" title="19.&nbsp;Kotlin Support"></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">18.&nbsp;Cloud Foundry</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__google_cloud_vision.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__kotlin_support.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_cloud_foundry" href="#_cloud_foundry"></a>18.&nbsp;Cloud Foundry</h1></div></div></div><p>Spring Cloud GCP provides support for Cloud Foundry&#8217;s <a class="link" href="https://docs.pivotal.io/partners/gcp-sb/index.html" target="_top">GCP Service Broker</a>.
Our Pub/Sub, Cloud Spanner, Storage, Stackdriver Trace and Cloud SQL MySQL and PostgreSQL starters are Cloud Foundry aware and retrieve properties like project ID, credentials, etc., that are used in auto configuration from the Cloud Foundry environment.</p><p>In cases like Pub/Sub&#8217;s topic and subscription, or Storage&#8217;s bucket name, where those parameters are not used in auto configuration, you can fetch them using the VCAP mapping provided by Spring Boot.
For example, to retrieve the provisioned Pub/Sub topic, you can use the <code class="literal">vcap.services.mypubsub.credentials.topic_name</code> property from the application environment.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>If the same service is bound to the same application more than once, the auto configuration will not be able to choose among bindings and will not be activated for that service.
This includes both MySQL and PostgreSQL bindings to the same app.</p></td></tr></table></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warning]" src="images/warning.png"></td><th align="left">Warning</th></tr><tr><td align="left" valign="top"><p>In order for the Cloud SQL integration to work in Cloud Foundry, auto-reconfiguration must be disabled.
You can do so using the <code class="literal">cf set-env &lt;APP&gt; JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}'</code> command.
Otherwise, Cloud Foundry will produce a <code class="literal">DataSource</code> with an invalid JDBC URL (i.e., <code class="literal">jdbc:mysql://null/null</code>).</p></td></tr></table></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__google_cloud_vision.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__kotlin_support.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">17.&nbsp;Google Cloud Vision&nbsp;</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">&nbsp;19.&nbsp;Kotlin Support</td></tr></table></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>15.&nbsp;Cloud Memorystore for Redis</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_data_cloud_datastore.html" title="14.&nbsp;Spring Data Cloud Datastore"><link rel="next" href="multi__cloud_identity_aware_proxy_iap_authentication.html" title="16.&nbsp;Cloud Identity-Aware Proxy (IAP) Authentication"></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">15.&nbsp;Cloud Memorystore for Redis</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_data_cloud_datastore.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_identity_aware_proxy_iap_authentication.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_cloud_memorystore_for_redis" href="#_cloud_memorystore_for_redis"></a>15.&nbsp;Cloud Memorystore for Redis</h1></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_spring_caching" href="#_spring_caching"></a>15.1&nbsp;Spring Caching</h2></div></div></div><p><a class="link" href="https://cloud.google.com/memorystore/" target="_top">Cloud Memorystore for Redis</a> provides a fully managed in-memory data store service.
Cloud Memorystore is compatible with the Redis protocol, allowing easy integration with <a class="link" href="https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html" target="_top">Spring Caching</a>.</p><p>All you have to do is create a Cloud Memorystore instance and use its IP address in <code class="literal">application.properties</code> file as <code class="literal">spring.redis.host</code> property value.
Everything else is exactly the same as setting up redis-backed Spring caching.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Memorystore instances and your application instances have to be located in the same region.</p></td></tr></table></div><p>In short, the following dependencies are needed:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.boot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-boot-starter-cache<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.boot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-boot-starter-data-redis<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>And then you can use <code class="literal">org.springframework.cache.annotation.Cacheable</code> annotation for methods you&#8217;d like to be cached.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Cacheable("cache1")</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> String hello(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@PathVariable</xslthl:annotation> String name) {
....
}</pre><p>If you are interested in a detailed how-to guide, please check <a class="link" href="https://codelabs.developers.google.com/codelabs/cloud-spring-cache-memorystore/" target="_top">Spring Boot Caching using Cloud Memorystore codelab</a>.</p><p>Cloud Memorystore documentation can be found <a class="link" href="https://cloud.google.com/memorystore/docs/redis/" target="_top">here</a>.</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_data_cloud_datastore.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_identity_aware_proxy_iap_authentication.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">14.&nbsp;Spring Data Cloud Datastore&nbsp;</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">&nbsp;16.&nbsp;Cloud Identity-Aware Proxy (IAP) Authentication</td></tr></table></div></body></html>

View File

@@ -0,0 +1,15 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>2.&nbsp;Dependency Management</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__introduction.html" title="1.&nbsp;Introduction"><link rel="next" href="multi__getting_started.html" title="3.&nbsp;Getting started"></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">2.&nbsp;Dependency Management</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__introduction.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__getting_started.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_dependency_management" href="#_dependency_management"></a>2.&nbsp;Dependency Management</h1></div></div></div><p>The Spring Cloud GCP Bill of Materials (BOM) contains the versions of all the dependencies it uses.</p><p>If you&#8217;re a Maven user, adding the following to your pom.xml file will allow you to not specify any Spring Cloud GCP dependency versions.
Instead, the version of the BOM you&#8217;re using determines the versions of the used dependencies.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependencyManagement&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependencies&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-dependencies<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;version&gt;</span>{project-version}<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/version&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;type&gt;</span>pom<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/type&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;scope&gt;</span>import<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/scope&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependencies&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependencyManagement&gt;</span></pre><p>In the following sections, it will be assumed you are using the Spring Cloud GCP BOM and the dependency snippets will not contain versions.</p><p>Gradle users can achieve the same kind of BOM experience using Spring&#8217;s <a class="link" href="https://github.com/spring-gradle-plugins/dependency-management-plugin" target="_top">dependency-management-plugin</a> Gradle plugin.
For simplicity, the Gradle dependency snippets in the remainder of this document will also omit their versions.</p></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__introduction.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__getting_started.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">1.&nbsp;Introduction&nbsp;</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">&nbsp;3.&nbsp;Getting started</td></tr></table></div></body></html>

View File

@@ -0,0 +1,5 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>3.&nbsp;Getting started</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__dependency_management.html" title="2.&nbsp;Dependency Management"><link rel="next" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;Spring Cloud GCP Core"></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">3.&nbsp;Getting started</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__dependency_management.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi_spring-cloud-gcp-core.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_getting_started" href="#_getting_started"></a>3.&nbsp;Getting started</h1></div></div></div><p>There are many available resources to get you up to speed with our libraries as quickly as possible.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_spring_initializr" href="#_spring_initializr"></a>3.1&nbsp;Spring Initializr</h2></div></div></div><p>There are three entries in <a class="link" href="https://start.spring.io/" target="_top">Spring Initializr</a> for Spring Cloud GCP.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_gcp_support" href="#_gcp_support"></a>3.1.1&nbsp;GCP Support</h3></div></div></div><p>The GCP Support entry contains auto-configuration support for every Spring Cloud GCP integration.
Most of the autoconfiguration code is only enabled if other dependencies are added to the classpath.</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"></colgroup><thead><tr><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Spring Cloud GCP Starter</th><th style="border-bottom: 1px solid ; " align="left" valign="top">Required dependencies</th></tr></thead><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Config</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-config</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Cloud Spanner</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-data-spanner</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Cloud Datastore</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-data-datastore</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Logging</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-logging</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>SQL - MySql</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-sql-mysql</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>SQL - PostgreSQL</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-sql-postgres</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Trace</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-trace</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Vision</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-vision</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p>Security - IAP</p></td><td style="" align="left" valign="top"><p>org.springframework.cloud:spring-cloud-gcp-starter-security-iap</p></td></tr></tbody></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_gcp_messaging" href="#_gcp_messaging"></a>3.1.2&nbsp;GCP Messaging</h3></div></div></div><p>The GCP Messaging entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Pub/Sub integrations work out of the box.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_gcp_storage" href="#_gcp_storage"></a>3.1.3&nbsp;GCP Storage</h3></div></div></div><p>The GCP Storage entry adds the GCP Support entry and all the required dependencies so that the Google Cloud Storage integrations work out of the box.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_code_samples" href="#_code_samples"></a>3.2&nbsp;Code Samples</h2></div></div></div><p>There are <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples" target="_top">code samples</a> available that demonstrate the usage of all our integrations.</p><p>For example, <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-vision-api-sample" target="_top">the Vision API sample</a> shows how to use <code class="literal">spring-cloud-gcp-starter-vision</code> to automatically configure Vision API clients.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_code_challenges" href="#_code_challenges"></a>3.3&nbsp;Code Challenges</h2></div></div></div><p>In a code challenge, you perform a task step by step, using one integration.
There are a number of challenges available in the <a class="link" href="https://codelabs.developers.google.com/spring" target="_top">Google Developers Codelabs</a> page.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_getting_started_guides" href="#_getting_started_guides"></a>3.4&nbsp;Getting Started Guides</h2></div></div></div><p>A Spring Getting Started guide on messaging with Spring Integration Channel Adapters for Google Cloud Pub/Sub is available from <a class="link" href="https://spring.io/guides/gs/messaging-gcp-pubsub/" target="_top">Spring Guides</a>.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__dependency_management.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi_spring-cloud-gcp-core.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">2.&nbsp;Dependency Management&nbsp;</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">&nbsp;4.&nbsp;Spring Cloud GCP Core</td></tr></table></div></body></html>

View File

@@ -0,0 +1,76 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>5.&nbsp;Google Cloud Pub/Sub</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-gcp-core.html" title="4.&nbsp;Spring Cloud GCP Core"><link rel="next" href="multi__spring_resources.html" title="6.&nbsp;Spring Resources"></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">5.&nbsp;Google Cloud Pub/Sub</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi_spring-cloud-gcp-core.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_resources.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_google_cloud_pubsub" href="#_google_cloud_pubsub"></a>5.&nbsp;Google Cloud Pub/Sub</h1></div></div></div><p>Spring Cloud GCP provides an abstraction layer to publish to and subscribe from Google Cloud Pub/Sub topics and to create, list or delete Google Cloud Pub/Sub topics and subscriptions.</p><p>A Spring Boot starter is provided to auto-configure the various required Pub/Sub components.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-pubsub<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-pubsub'
}</pre><p>This starter is also available from <a class="link" href="https://start.spring.io" target="_top">Spring Initializr</a> through the <code class="literal">GCP Messaging</code> entry.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_pubsub_operations_template" href="#_pubsub_operations_template"></a>5.1&nbsp;Pub/Sub Operations &amp; Template</h2></div></div></div><p><code class="literal">PubSubOperations</code> is an abstraction that allows Spring users to use Google Cloud Pub/Sub without depending on any Google Cloud Pub/Sub API semantics.
It provides the common set of operations needed to interact with Google Cloud Pub/Sub.
<code class="literal">PubSubTemplate</code> is the default implementation of <code class="literal">PubSubOperations</code> and it uses the <a class="link" href="https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-pubsub" target="_top">Google Cloud Java Client for Pub/Sub</a> to interact with Google Cloud Pub/Sub.</p><p><code class="literal">PubSubTemplate</code> depends on a <code class="literal">PublisherFactory</code> and a <code class="literal">SubscriberFactory</code>.
The <code class="literal">PublisherFactory</code> provides a Google Cloud Java Client for Pub/Sub <code class="literal">Publisher</code>.
The <code class="literal">SubscriberFactory</code> provides the <code class="literal">Subscriber</code> for asynchronous message pulling, as well as a <code class="literal">SubscriberStub</code> for synchronous pulling.
The Spring Boot starter for GCP Pub/Sub auto-configures a <code class="literal">PublisherFactory</code> and <code class="literal">SubscriberFactory</code> with default settings and uses the <code class="literal">GcpProjectIdProvider</code> and <code class="literal">CredentialsProvider</code> auto-configured by the Spring Boot GCP starter.</p><p>The <code class="literal">PublisherFactory</code> implementation provided by Spring Cloud GCP Pub/Sub, <code class="literal">DefaultPublisherFactory</code>, caches <code class="literal">Publisher</code> instances by topic name, in order to optimize resource utilization.</p><p>The <code class="literal">PubSubOperations</code> interface is actually a combination of <code class="literal">PubSubPublisherOperations</code> and <code class="literal">PubSubSubscriberOperations</code> with the corresponding <code class="literal">PubSubPublisherTemplate</code> and <code class="literal">PubSubSubscriberTemplate</code> implementations, which can be used individually or via the composite <code class="literal">PubSubTemplate</code>.
The rest of the documentation refers to <code class="literal">PubSubTemplate</code>, but the same applies to <code class="literal">PubSubPublisherTemplate</code> and <code class="literal">PubSubSubscriberTemplate</code>, depending on whether we&#8217;re talking about publishing or subscribing.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_publishing_to_a_topic" href="#_publishing_to_a_topic"></a>5.1.1&nbsp;Publishing to a topic</h3></div></div></div><p><code class="literal">PubSubTemplate</code> provides asynchronous methods to publish messages to a Google Cloud Pub/Sub topic.
The <code class="literal">publish()</code> method takes in a topic name to post the message to, a payload of a generic type and, optionally, a map with the message headers.</p><p>Here is an example of how to publish a message to a Google Cloud Pub/Sub topic:</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">void</span> publishMessage() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.pubSubTemplate.publish(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"topic"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"your message payload"</span>, ImmutableMap.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"key1"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"val1"</span>));
}</pre><p>By default, the <code class="literal">SimplePubSubMessageConverter</code> is used to convert payloads of type <code class="literal">byte[]</code>, <code class="literal">ByteString</code>, <code class="literal">ByteBuffer</code>, and <code class="literal">String</code> to Pub/Sub messages.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_json_support" href="#_json_support"></a>JSON support</h4></div></div></div><p>For serialization and deserialization of POJOs using Jackson JSON, configure a <code class="literal">JacksonPubSubMessageConverter</code> bean, and the Spring Boot starter for GCP Pub/Sub will automatically wire it into the <code class="literal">PubSubTemplate</code>.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Note: The ObjectMapper is used to convert Java POJOs to and from JSON.</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// You will have to configure your own instance if you are unable to depend</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// on the ObjectMapper provided by Spring Boot starters.</span>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> JacksonPubSubMessageConverter jacksonPubSubMessageConverter(ObjectMapper objectMapper) {
<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> JacksonPubSubMessageConverter(objectMapper);
}</pre><p>Alternatively, you can set it directly by calling the <code class="literal">setMessageConverter()</code> method on the <code class="literal">PubSubTemplate</code>.
Other implementations of the <code class="literal">PubSubMessageConverter</code> can also be configured in the same manner.</p><p>Please refer to our <a class="link" href="../spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample" target="_top">Pub/Sub JSON Payload Sample App</a> as a reference for using this functionality.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_subscribing_to_a_subscription" href="#_subscribing_to_a_subscription"></a>5.1.2&nbsp;Subscribing to a subscription</h3></div></div></div><p>Google Cloud Pub/Sub allows many subscriptions to be associated to the same topic.
<code class="literal">PubSubTemplate</code> allows you to listen to subscriptions via the <code class="literal">subscribe()</code> method.
It relies on a <code class="literal">SubscriberFactory</code> object, whose only task is to generate Google Cloud Pub/Sub
<code class="literal">Subscriber</code> objects.
When listening to a subscription, messages will be pulled from Google Cloud Pub/Sub
asynchronously, at a certain interval.</p><p>The Spring Boot starter for Google Cloud Pub/Sub auto-configures a <code class="literal">SubscriberFactory</code>.</p><p>If Pub/Sub message payload conversion is desired, you can use the <code class="literal">subscribeAndConvert()</code> method, which will use the converter configured in the template.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_pulling_messages_from_a_subscription" href="#_pulling_messages_from_a_subscription"></a>5.1.3&nbsp;Pulling messages from a subscription</h3></div></div></div><p>Google Cloud Pub/Sub supports synchronous pulling of messages from a subscription.
This is different from subscribing to a subscription, in the sense that subscribing is an asynchronous task which polls the subscription on a set interval.</p><p>The <code class="literal">pullNext()</code> method allows for a single message to be pulled and automatically acknowledged from a subscription.
The <code class="literal">pull()</code> method pulls a number of messages from a subscription, allowing for the retry settings to be configured.
Any messages received by <code class="literal">pull()</code> are not automatically acknowledged.
Instead, since they are of the kind <code class="literal">AcknowledgeablePubsubMessage</code>, you can acknowledge them by calling the <code class="literal">ack()</code> method, or negatively acknowledge them by calling the <code class="literal">nack()</code> method.
The <code class="literal">pullAndAck()</code> method does the same as the <code class="literal">pull()</code> method and, additionally, acknowledges all received messages.</p><p>The <code class="literal">pullAndConvert()</code> method does the same as the <code class="literal">pull()</code> method and, additionally, converts the Pub/Sub binary payload to an object of the desired type, using the converter configured in the template.</p><p>To acknowledge multiple messages received from <code class="literal">pull()</code> or <code class="literal">pullAndConvert()</code> at once, you can use the <code class="literal">PubSubTemplate.ack()</code> method.
You can also use the <code class="literal">PubSubTemplate.nack()</code> for negatively acknowledging messages.</p><p>Using these methods for acknowledging messages in batches is more efficient than acknowledging messages individually, but they <span class="strong"><strong>require</strong></span> the collection of messages to be from the same project.</p><p>All <code class="literal">ack()</code>, <code class="literal">nack()</code>, and <code class="literal">modifyAckDeadline()</code> methods on messages as well as <code class="literal">PubSubSubscriberTemplate</code> are implemented asynchronously, returning a <code class="literal">ListenableFuture&lt;Void&gt;</code> to be able to process the asynchronous execution.</p><p><code class="literal">PubSubTemplate</code> uses a special subscriber generated by its <code class="literal">SubscriberFactory</code> to synchronously pull messages.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_pubsub_management" href="#_pubsub_management"></a>5.2&nbsp;Pub/Sub management</h2></div></div></div><p><code class="literal">PubSubAdmin</code> is the abstraction provided by Spring Cloud GCP to manage Google Cloud Pub/Sub resources.
It allows for the creation, deletion and listing of topics and subscriptions.</p><p><code class="literal">PubSubAdmin</code> depends on <code class="literal">GcpProjectIdProvider</code> and either a <code class="literal">CredentialsProvider</code> or a <code class="literal">TopicAdminClient</code> and a <code class="literal">SubscriptionAdminClient</code>.
If given a <code class="literal">CredentialsProvider</code>, it creates a <code class="literal">TopicAdminClient</code> and a <code class="literal">SubscriptionAdminClient</code> with the Google Cloud Java Library for Pub/Sub default settings.
The Spring Boot starter for GCP Pub/Sub auto-configures a <code class="literal">PubSubAdmin</code> object using the <code class="literal">GcpProjectIdProvider</code> and the <code class="literal">CredentialsProvider</code> auto-configured by the Spring Boot GCP Core starter.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_creating_a_topic" href="#_creating_a_topic"></a>5.2.1&nbsp;Creating a topic</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to create topics:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Topic createTopic(String topicName)</pre><p>Here is an example of how to create a Google Cloud Pub/Sub topic:</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">void</span> newTopic() {
pubSubAdmin.createTopic(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"topicName"</span>);
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_deleting_a_topic" href="#_deleting_a_topic"></a>5.2.2&nbsp;Deleting a topic</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to delete topics:</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">void</span> deleteTopic(String topicName)</pre><p>Here is an example of how to delete a Google Cloud Pub/Sub topic:</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">void</span> deleteTopic() {
pubSubAdmin.deleteTopic(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"topicName"</span>);
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_listing_topics" href="#_listing_topics"></a>5.2.3&nbsp;Listing topics</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to list topics:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> List&lt;Topic&gt; listTopics</pre><p>Here is an example of how to list every Google Cloud Pub/Sub topic name in a project:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> List&lt;String&gt; listTopics() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> pubSubAdmin
.listTopics()
.stream()
.map(Topic::getNameAsTopicName)
.map(TopicName::getTopic)
.collect(Collectors.toList());
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_creating_a_subscription" href="#_creating_a_subscription"></a>5.2.4&nbsp;Creating a subscription</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to create subscriptions to existing topics:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline, String pushEndpoint)</pre><p>Here is an example of how to create a Google Cloud Pub/Sub subscription:</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">void</span> newSubscription() {
pubSubAdmin.createSubscription(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"subscriptionName"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"topicName"</span>, <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">10</xslthl:number>, &#8220;https:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//my.endpoint/push&#8221;);</span>
}</pre><p>Alternative methods with default settings are provided for ease of use.
The default value for <code class="literal">ackDeadline</code> is 10 seconds.
If <code class="literal">pushEndpoint</code> isn&#8217;t specified, the subscription uses message pulling, instead.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Subscription createSubscription(String subscriptionName, String topicName)</pre><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Subscription createSubscription(String subscriptionName, String topicName, Integer ackDeadline)</pre><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Subscription createSubscription(String subscriptionName, String topicName, String pushEndpoint)</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_deleting_a_subscription" href="#_deleting_a_subscription"></a>5.2.5&nbsp;Deleting a subscription</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to delete subscriptions:</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">void</span> deleteSubscription(String subscriptionName)</pre><p>Here is an example of how to delete a Google Cloud Pub/Sub subscription:</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">void</span> deleteSubscription() {
pubSubAdmin.deleteSubscription(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"subscriptionName"</span>);
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_listing_subscriptions" href="#_listing_subscriptions"></a>5.2.6&nbsp;Listing subscriptions</h3></div></div></div><p><code class="literal">PubSubAdmin</code> implements a method to list subscriptions:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> List&lt;Subscription&gt; listSubscriptions()</pre><p>Here is an example of how to list every subscription name in a project:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> List&lt;String&gt; listSubscriptions() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> pubSubAdmin
.listSubscriptions()
.stream()
.map(Subscription::getNameAsSubscriptionName)
.map(SubscriptionName::getSubscription)
.collect(Collectors.toList());
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="pubsub-configuration" href="#pubsub-configuration"></a>5.3&nbsp;Configuration</h2></div></div></div><p>The Spring Boot starter for Google Cloud Pub/Sub provides the following configuration options:</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.pubsub.enabled</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Enables or disables Pub/Sub auto-configuration</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.pubsub.subscriber.executor-threads</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Number of threads used by <code class="literal">Subscriber</code> instances created by <code class="literal">SubscriberFactory</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>4</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.pubsub.publisher.executor-threads</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Number of threads used by <code class="literal">Publisher</code> instances created by <code class="literal">PublisherFactory</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>4</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.pubsub.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 Pub/Sub API is hosted, if different from the one in the <a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.pubsub.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 Pub/Sub API, if different from the ones in the
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.pubsub.credentials.encoded-key</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Base64-encoded contents of OAuth2 account private key for authenticating with the
Google Cloud Pub/Sub API, if different from the ones in the
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.pubsub.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
Pub/Sub 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/pubsub" target="_top">https://www.googleapis.com/auth/pubsub</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.pubsub.subscriber.parallel-pull-count</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The number of pull workers</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>The available number of processors</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.pubsub.subscriber.max-ack-extension-period</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The maximum period a message ack deadline will be extended, in seconds</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</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.pubsub.subscriber.pull-endpoint</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The endpoint for synchronous pulling messages</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>pubsub.googleapis.com:443</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.pubsub.[subscriber,publisher].retry.total-timeout-seconds</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>TotalTimeout has ultimate control over how long the logic should keep trying the remote call until it gives up completely.
The higher the total timeout, the more retries can be attempted.</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</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.pubsub.[subscriber,publisher].retry.initial-retry-delay-second</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>InitialRetryDelay controls the delay before the first retry.
Subsequent retries will use this value adjusted according to the RetryDelayMultiplier.</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</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.pubsub.[subscriber,publisher].retry.retry-delay-multiplier</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>RetryDelayMultiplier controls the change in retry delay.
The retry delay of the previous call is multiplied by the RetryDelayMultiplier to calculate the retry delay for the next call.</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>1</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.pubsub.[subscriber,publisher].retry.max-retry-delay-seconds</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>MaxRetryDelay puts a limit on the value of the retry delay, so that the RetryDelayMultiplier
can&#8217;t increase the retry delay higher than this amount.</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</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.pubsub.[subscriber,publisher].retry.max-attempts</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>MaxAttempts defines the maximum number of attempts to perform.
If this value is greater than 0, and the number of attempts reaches this limit, the logic will give up retrying even if the total retry time is still lower than TotalTimeout.</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</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.pubsub.[subscriber,publisher].retry.jittered</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Jitter determines if the delay time should be randomized.</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>true</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.pubsub.[subscriber,publisher].retry.initial-rpc-timeout-seconds</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>InitialRpcTimeout controls the timeout for the initial RPC.
Subsequent calls will use this value adjusted according to the RpcTimeoutMultiplier.</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</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.pubsub.[subscriber,publisher].retry.rpc-timeout-multiplier</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>RpcTimeoutMultiplier controls the change in RPC timeout.
The timeout of the previous call is multiplied by the RpcTimeoutMultiplier to calculate the timeout for the next call.</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>1</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.pubsub.[subscriber,publisher].retry.max-rpc-timeout-seconds</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>MaxRpcTimeout puts a limit on the value of the RPC timeout, so that the RpcTimeoutMultiplier
can&#8217;t increase the RPC timeout higher than this amount.</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</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.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-element-count</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Maximum number of outstanding elements to keep in memory before enforcing flow control.</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>unlimited</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.pubsub.[subscriber,publisher.batching].flow-control.max-outstanding-request-bytes</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Maximum number of outstanding bytes to keep in memory before enforcing flow control.</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>unlimited</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.pubsub.[subscriber,publisher.batching].flow-control.limit-exceeded-behavior</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The behavior when the specified limits are exceeded.</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>Block</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.pubsub.publisher.batching.element-count-threshold</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The element count threshold to use for batching.</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>unset (threshold does not apply)</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.pubsub.publisher.batching.request-byte-threshold</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The request byte threshold to use for batching.</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>unset (threshold does not apply)</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.pubsub.publisher.batching.delay-threshold-seconds</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>The delay threshold to use for batching.
After this amount of time has elapsed (counting from the first element added), the elements will be wrapped up in a batch and sent.</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>unset (threshold does not apply)</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.pubsub.publisher.batching.enabled</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>Enables batching.</p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="" align="left" valign="top"><p>false</p></td></tr></tbody></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample" href="#_sample"></a>5.4&nbsp;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-pubsub-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-gcp-core.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_resources.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">4.&nbsp;Spring Cloud GCP Core&nbsp;</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">&nbsp;6.&nbsp;Spring Resources</td></tr></table></div></body></html>

View File

@@ -0,0 +1,27 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>17.&nbsp;Google Cloud Vision</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__cloud_identity_aware_proxy_iap_authentication.html" title="16.&nbsp;Cloud Identity-Aware Proxy (IAP) Authentication"><link rel="next" href="multi__cloud_foundry.html" title="18.&nbsp;Cloud Foundry"></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">17.&nbsp;Google Cloud Vision</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__cloud_identity_aware_proxy_iap_authentication.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_foundry.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_google_cloud_vision" href="#_google_cloud_vision"></a>17.&nbsp;Google Cloud Vision</h1></div></div></div><p>The <a class="link" href="https://cloud.google.com/vision/" target="_top">Google Cloud Vision API</a> allows users to leverage machine learning algorithms for processing images including: image classification, face detection, text extraction, and others.</p><p>Spring Cloud GCP provides:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">A convenience starter which automatically configures authentication settings and client objects needed to begin using the <a class="link" href="https://cloud.google.com/vision/" target="_top">Google Cloud Vision API</a>.</li><li class="listitem"><p class="simpara">A Cloud Vision Template which simplifies interactions with the Cloud Vision API.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">Allows you to easily send images to the API as Spring Resources.</li><li class="listitem">Offers convenience methods for common operations, such as extracting the text from an image.</li></ul></div></li></ul></div><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-vision<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-vision'
}</pre><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_cloud_vision_template" href="#_cloud_vision_template"></a>17.1&nbsp;Cloud Vision Template</h2></div></div></div><p>The <code class="literal">CloudVisionTemplate</code> offers a simple way to use the Cloud Vision APIs with Spring Resources.</p><p>After you add the <code class="literal">spring-cloud-gcp-starter-vision</code> dependency to your project, you may <code class="literal">@Autowire</code> an instance of <code class="literal">CloudVisionTemplate</code> to use in your code.</p><p>The <code class="literal">CloudVisionTemplate</code> offers the following method for interfacing with Cloud Vision:</p><p><code class="literal">public AnnotateImageResponse analyzeImage(Resource imageResource, Feature.Type&#8230;&#8203; featureTypes)</code></p><p><span class="strong"><strong>Parameters:</strong></span></p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">Resource imageResource</code> refers to the Spring Resource of the image object you wish to analyze.
The Google Cloud Vision documentation provides a <a class="link" href="https://cloud.google.com/vision/docs/supported-files" target="_top">list of the image types that they support</a>.</li><li class="listitem"><code class="literal">Feature.Type&#8230;&#8203; featureTypes</code> refers to a var-arg array of Cloud Vision Features to extract from the image.
A feature refers to a kind of image analysis one wishes to perform on an image, such as label detection, OCR recognition, facial detection, etc.
One may specify multiple features to analyze within one request.
A full list of Cloud Vision Features is provided in the <a class="link" href="https://cloud.google.com/vision/docs/features" target="_top">Cloud Vision Feature docs</a>.</li></ul></div><p><span class="strong"><strong>Returns:</strong></span></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/vision/docs/reference/rpc/google.cloud.vision.v1#google.cloud.vision.v1.AnnotateImageResponse" target="_top"><code class="literal">AnnotateImageResponse</code></a> contains the results of all the feature analyses that were specified in the request.
For each feature type that you provide in the request, <code class="literal">AnnotateImageResponse</code> provides a getter method to get the result of that feature analysis.
For example, if you analyzed an image using the <code class="literal">LABEL_DETECTION</code> feature, you would retrieve the results from the response using <code class="literal">annotateImageResponse.getLabelAnnotationsList()</code>.</p><p class="simpara"><code class="literal">AnnotateImageResponse</code> is provided by the Google Cloud Vision libraries; please consult the <a class="link" href="https://cloud.google.com/vision/docs/reference/rpc/google.cloud.vision.v1#google.cloud.vision.v1.AnnotateImageResponse" target="_top">RPC reference</a> or <a class="link" href="https://googleapis.github.io/googleapis/java/all/latest/apidocs/com/google/cloud/vision/v1/AnnotateImageResponse.html" target="_top">Javadoc</a> for more details.
Additionally, you may consult the <a class="link" href="https://cloud.google.com/vision/docs/" target="_top">Cloud Vision docs</a> to familiarize yourself with the concepts and features of the API.</p></li></ul></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_detect_image_labels_example" href="#_detect_image_labels_example"></a>17.2&nbsp;Detect Image Labels Example</h2></div></div></div><p><a class="link" href="https://cloud.google.com/vision/docs/detecting-labels" target="_top">Image labeling</a> refers to producing labels that describe the contents of an image.
Below is a code sample of how this is done using the Cloud Vision Spring Template.</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> ResourceLoader resourceLoader;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> CloudVisionTemplate cloudVisionTemplate;
<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> processImage() {
Resource imageResource = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.resourceLoader.getResource(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"my_image.jpg"</span>);
AnnotateImageResponse response = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.cloudVisionTemplate.analyzeImage(
imageResource, Type.LABEL_DETECTION);
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Image Classification results: "</span> + response.getLabelAnnotationsList());
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_12" href="#_sample_12"></a>17.3&nbsp;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-vision-api-sample" target="_top">Sample Spring Boot Application</a> is provided to show how to use the Cloud Vision starter and template.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__cloud_identity_aware_proxy_iap_authentication.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_foundry.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">16.&nbsp;Cloud Identity-Aware Proxy (IAP) Authentication&nbsp;</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">&nbsp;18.&nbsp;Cloud Foundry</td></tr></table></div></body></html>

View File

@@ -0,0 +1,3 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>1.&nbsp;Introduction</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-gcp.html" title="Spring Cloud GCP"><link rel="next" href="multi__dependency_management.html" title="2.&nbsp;Dependency Management"></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">1.&nbsp;Introduction</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi_spring-cloud-gcp.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__dependency_management.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_introduction" href="#_introduction"></a>1.&nbsp;Introduction</h1></div></div></div><p>The Spring Cloud GCP project makes the Spring Framework a first-class citizen of Google Cloud Platform (GCP).</p><p>Spring Cloud GCP lets you leverage the power and simplicity of the Spring Framework to:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Analyze your images for text, objects, and other content with Google Cloud Vision</li><li class="listitem">Use Spring Security via Google Cloud IAP</li><li class="listitem">Map objects, relationships, and collections with Spring Data Cloud Spanner and Spring Data Cloud Datastore</li><li class="listitem">Publish and subscribe to Google Cloud Pub/Sub topics</li><li class="listitem">Configure Spring JDBC with a few properties to use Google Cloud SQL</li><li class="listitem">Write and read from Spring Resources backed up by Google Cloud Storage</li><li class="listitem">Exchange messages with Spring Integration using Google Cloud Pub/Sub on the background</li><li class="listitem">Trace the execution of your app with Spring Cloud Sleuth and Google Stackdriver Trace</li><li class="listitem">Configure your app with Spring Cloud Config, backed up by the Google Runtime Configuration API</li><li class="listitem">Consume and produce Google Cloud Storage data via Spring Integration GCS Channel Adapters</li></ol></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-gcp.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__dependency_management.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Spring Cloud GCP&nbsp;</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">&nbsp;2.&nbsp;Dependency Management</td></tr></table></div></body></html>

View File

@@ -0,0 +1,5 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>19.&nbsp;Kotlin Support</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__cloud_foundry.html" title="18.&nbsp;Cloud Foundry"><link rel="next" href="multi__sample_13.html" title="20.&nbsp;Sample"></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">19.&nbsp;Kotlin Support</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__cloud_foundry.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__sample_13.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_kotlin_support" href="#_kotlin_support"></a>19.&nbsp;Kotlin Support</h1></div></div></div><p>The latest version of the Spring Framework provides first-class support for Kotlin.
For Kotlin users of Spring, the Spring Cloud GCP libraries work out-of-the-box and are fully interoperable with Kotlin applications.</p><p>For more information on building a Spring application in Kotlin, please consult the <a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#kotlin" target="_top">Spring Kotlin documentation</a>.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_prerequisites_2" href="#_prerequisites_2"></a>19.1&nbsp;Prerequisites</h2></div></div></div><p>Ensure that your Kotlin application is properly set up.
Based on your build system, you will need to include the correct Kotlin build plugin in your project:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="link" href="https://kotlinlang.org/docs/reference/using-maven.html" target="_top">Kotlin Maven Plugin</a></li><li class="listitem"><a class="link" href="https://kotlinlang.org/docs/reference/using-gradle.html" target="_top">Kotlin Gradle Plugin</a></li></ul></div><p>Depending on your application&#8217;s needs, you may need to augment your build configuration with compiler plugins:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="link" href="https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support" target="_top">Kotlin Spring Plugin</a>: Makes your Spring configuration classes/members non-final for convenience.</li><li class="listitem"><a class="link" href="https://kotlinlang.org/docs/reference/compiler-plugins.html#jpa-support" target="_top">Kotlin JPA Plugin</a>: Enables using JPA in Kotlin applications.</li></ul></div><p>Once your Kotlin project is properly configured, the Spring Cloud GCP libraries will work within your application without any additional setup.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__cloud_foundry.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__sample_13.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">18.&nbsp;Cloud Foundry&nbsp;</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">&nbsp;20.&nbsp;Sample</td></tr></table></div></body></html>

View File

@@ -0,0 +1,3 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>20.&nbsp;Sample</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__kotlin_support.html" title="19.&nbsp;Kotlin Support"></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">20.&nbsp;Sample</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__kotlin_support.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;</td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_sample_13" href="#_sample_13"></a>20.&nbsp;Sample</h1></div></div></div><p>A <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample" target="_top">Kotlin sample application</a> is provided to demonstrate a working Maven setup and various Spring Cloud GCP integrations from within Kotlin.</p></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__kotlin_support.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;</td></tr><tr><td width="40%" align="left" valign="top">19.&nbsp;Kotlin Support&nbsp;</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">&nbsp;</td></tr></table></div></body></html>

View File

@@ -0,0 +1,35 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>12.&nbsp;Spring Cloud Config</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__stackdriver_logging.html" title="11.&nbsp;Stackdriver Logging"><link rel="next" href="multi__spring_data_cloud_spanner.html" title="13.&nbsp;Spring Data Cloud Spanner"></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">12.&nbsp;Spring Cloud Config</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__stackdriver_logging.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_data_cloud_spanner.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_cloud_config" href="#_spring_cloud_config"></a>12.&nbsp;Spring Cloud Config</h1></div></div></div><p>Spring Cloud GCP makes it possible to use the <a class="link" href="https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/" target="_top">Google Runtime Configuration API</a> as a <a class="link" href="https://cloud.spring.io/spring-cloud-config/" target="_top">Spring Cloud Config</a> server to remotely store your application configuration data.</p><p>The Spring Cloud GCP Config support is provided via its own Spring Boot starter.
It enables the use of the Google Runtime Configuration API as a source for Spring Boot configuration properties.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The Google Cloud Runtime Configuration service is in beta status.</p></td></tr></table></div><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-config<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-config'
}</pre><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_3" href="#_configuration_3"></a>12.1&nbsp;Configuration</h2></div></div></div><p>The following parameters are configurable in Spring Cloud GCP Config:</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.config.enabled</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Enables the Config client</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">false</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.config.name</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Name of your application</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>Value of the <code class="literal">spring.application.name</code> property.
If none, <code class="literal">application</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.config.profile</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Active profile</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>Value of the <code class="literal">spring.profiles.active</code> property.
If more than a single profile, last one is chosen</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.config.timeout-millis</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Timeout in milliseconds for connecting to the Google Runtime Configuration API</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">60000</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.config.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 Runtime Configuration API is hosted</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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.config.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 Runtime Configuration API</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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.config.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 Runtime Configuration API</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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.config.credentials.scopes</code></p></td><td style="border-right: 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 Config credentials</p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="" align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/cloudruntimeconfig" target="_top">https://www.googleapis.com/auth/cloudruntimeconfig</a></p></td></tr></tbody></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>These properties should be specified in a <a class="link" href="https://cloud.spring.io/spring-cloud-static/spring-cloud.html#_the_bootstrap_application_context" target="_top"><code class="literal">bootstrap.yml</code>/<code class="literal">bootstrap.properties</code></a> file, rather than the usual <code class="literal">applications.yml</code>/<code class="literal">application.properties</code>.</p></td></tr></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Core properties, as described in <a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;Spring Cloud GCP Core">Spring Cloud GCP Core Module</a>, do not apply to Spring Cloud GCP Config.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_quick_start" href="#_quick_start"></a>12.2&nbsp;Quick start</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p class="simpara">Create a configuration in the Google Runtime Configuration API that is called <code class="literal">${spring.application.name}_${spring.profiles.active}</code>.
In other words, if <code class="literal">spring.application.name</code> is <code class="literal">myapp</code> and <code class="literal">spring.profiles.active</code> is <code class="literal">prod</code>, the configuration should be called <code class="literal">myapp_prod</code>.</p><p class="simpara">In order to do that, you should have the <a class="link" href="https://cloud.google.com/sdk/" target="_top">Google Cloud SDK</a> installed, own a Google Cloud Project and run the following command:</p></li></ol></div><pre class="screen">gcloud init # if this is your first Google Cloud SDK run.
gcloud beta runtime-config configs create myapp_prod
gcloud beta runtime-config configs variables set myapp.queue-size 25 --config-name myapp_prod</pre><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p class="simpara">Configure your <code class="literal">bootstrap.properties</code> file with your application&#8217;s configuration data:</p><pre class="screen">spring.application.name=myapp
spring.profiles.active=prod</pre></li><li class="listitem"><p class="simpara">Add the <code class="literal">@ConfigurationProperties</code> annotation to a Spring-managed bean:</p><pre class="screen">@Component
@ConfigurationProperties("myapp")
public class SampleConfig {
private int queueSize;
public int getQueueSize() {
return this.queueSize;
}
public void setQueueSize(int queueSize) {
this.queueSize = queueSize;
}
}</pre></li></ol></div><p>When your Spring application starts, the <code class="literal">queueSize</code> field value will be set to 25 for the above <code class="literal">SampleConfig</code> bean.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_refreshing_the_configuration_at_runtime" href="#_refreshing_the_configuration_at_runtime"></a>12.3&nbsp;Refreshing the configuration at runtime</h2></div></div></div><p><a class="link" href="https://cloud.spring.io/spring-cloud-static/docs/1.0.x/spring-cloud.html#_endpoints" target="_top">Spring Cloud</a> provides support to have configuration parameters be reloadable with the POST request to <code class="literal">/actuator/refresh</code> endpoint.</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Add the Spring Boot Actuator dependency:</li></ol></div><p>Maven coordinates:</p><pre class="screen">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
&lt;/dependency&gt;</pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
}</pre><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Add <code class="literal">@RefreshScope</code> to your Spring configuration class to have parameters be reloadable at runtime.</li><li class="listitem">Add <code class="literal">management.endpoints.web.exposure.include=refresh</code> to your <code class="literal">application.properties</code> to allow unrestricted access to <code class="literal">/actuator/refresh</code>.</li><li class="listitem"><p class="simpara">Update a property with <code class="literal">gcloud</code>:</p><pre class="literallayout">$ gcloud beta runtime-config configs variables set \
myapp.queue_size 200 \
--config-name myapp_prod</pre></li><li class="listitem"><p class="simpara">Send a POST request to the refresh endpoint:</p><pre class="literallayout">$ curl -XPOST https://myapp.host.com/actuator/refresh</pre></li></ol></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_8" href="#_sample_8"></a>12.4&nbsp;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-config-sample" target="_top">sample application</a> and a <a class="link" href="https://codelabs.developers.google.com/codelabs/cloud-spring-runtime-config/index.html" target="_top">codelab</a> are 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__stackdriver_logging.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_data_cloud_spanner.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">11.&nbsp;Stackdriver Logging&nbsp;</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">&nbsp;13.&nbsp;Spring Data Cloud Spanner</td></tr></table></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>9.&nbsp;Spring Cloud Stream</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_integration.html" title="8.&nbsp;Spring Integration"><link rel="next" href="multi__spring_cloud_sleuth.html" title="10.&nbsp;Spring Cloud Sleuth"></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">9.&nbsp;Spring Cloud Stream</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_integration.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_sleuth.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_cloud_stream" href="#_spring_cloud_stream"></a>9.&nbsp;Spring Cloud Stream</h1></div></div></div><p>Spring Cloud GCP provides a <a class="link" href="https://cloud.spring.io/spring-cloud-stream/" target="_top">Spring Cloud Stream</a> binder to Google Cloud Pub/Sub.</p><p>The provided binder relies on the <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-pubsub/src/main/java/org/springframework/cloud/gcp/pubsub/integration" target="_top">Spring Integration Channel Adapters for Google Cloud Pub/Sub</a>.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-pubsub-stream-binder<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub-stream-binder'
}</pre><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_overview" href="#_overview"></a>9.1&nbsp;Overview</h2></div></div></div><p>This binder binds producers to Google Cloud Pub/Sub topics and consumers to subscriptions.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Partitioning is currently not supported by this binder.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_2" href="#_configuration_2"></a>9.2&nbsp;Configuration</h2></div></div></div><p>You can configure the Spring Cloud Stream Binder for Google Cloud Pub/Sub to automatically generate the underlying resources, like the Google Cloud Pub/Sub topics and subscriptions for producers and consumers.
For that, you can use the <code class="literal">spring.cloud.stream.gcp.pubsub.bindings.&lt;channelName&gt;.&lt;consumer|producer&gt;.auto-create-resources</code> property, which is turned ON by default.</p><p>Starting with version 1.1, these and other binder properties can be configured globally for all the bindings, e.g. <code class="literal">spring.cloud.stream.gcp.pubsub.default.consumer.auto-create-resources</code>.</p><p>If you are using Pub/Sub auto-configuration from the Spring Cloud GCP Pub/Sub Starter, you should refer to the <a class="link" href="multi__google_cloud_pubsub.html#pubsub-configuration" title="5.3&nbsp;Configuration">configuration</a> section for other Pub/Sub parameters.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>To use this binder with a <a class="link" href="https://cloud.google.com/pubsub/docs/emulator" target="_top">running emulator</a>, configure its host and port via <code class="literal">spring.cloud.gcp.pubsub.emulator-host</code>.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_producer_destination_configuration" href="#_producer_destination_configuration"></a>9.2.1&nbsp;Producer Destination Configuration</h3></div></div></div><p>If automatic resource creation is turned ON and the topic corresponding to the destination name does not exist, it will be created.</p><p>For example, for the following configuration, a topic called <code class="literal">myEvents</code> would be created.</p><p><b>application.properties.&nbsp;</b>
</p><pre class="screen">spring.cloud.stream.bindings.events.destination=myEvents
spring.cloud.stream.gcp.pubsub.bindings.events.producer.auto-create-resources=true</pre><p>
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_consumer_destination_configuration" href="#_consumer_destination_configuration"></a>9.2.2&nbsp;Consumer Destination Configuration</h3></div></div></div><p>If automatic resource creation is turned ON and the subscription and/or the topic do not exist for a consumer, a subscription and potentially a topic will be created.
The topic name will be the same as the destination name, and the subscription name will be the destination name followed by the consumer group name.</p><p>Regardless of the <code class="literal">auto-create-resources</code> setting, if the consumer group is not specified, an anonymous one will be created with the name <code class="literal">anonymous.&lt;destinationName&gt;.&lt;randomUUID&gt;</code>.
Then when the binder shuts down, all Pub/Sub subscriptions created for anonymous consumer groups will be automatically cleaned up.</p><p>For example, for the following configuration, a topic named <code class="literal">myEvents</code> and a subscription called <code class="literal">myEvents.counsumerGroup1</code> would be created.
If the consumer group is not specified, a subscription called <code class="literal">anonymous.myEvents.a6d83782-c5a3-4861-ac38-e6e2af15a7be</code> would be created and later cleaned up.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>If you are manually creating Pub/Sub subscriptions for consumers, make sure that they follow the naming convention of <code class="literal">&lt;destinationName&gt;.&lt;consumerGroup&gt;</code>.</p></td></tr></table></div><p><b>application.properties.&nbsp;</b>
</p><pre class="screen">spring.cloud.stream.bindings.events.destination=myEvents
spring.cloud.stream.gcp.pubsub.bindings.events.consumer.auto-create-resources=true
# specify consumer group, and avoid anonymous consumer group generation
spring.cloud.stream.bindings.events.group=consumerGroup1</pre><p>
</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_5" href="#_sample_5"></a>9.3&nbsp;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-pubsub-binder-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_integration.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_sleuth.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">8.&nbsp;Spring Integration&nbsp;</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">&nbsp;10.&nbsp;Spring Cloud Sleuth</td></tr></table></div></body></html>

View File

@@ -0,0 +1,467 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>14.&nbsp;Spring Data Cloud Datastore</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_data_cloud_spanner.html" title="13.&nbsp;Spring Data Cloud Spanner"><link rel="next" href="multi__cloud_memorystore_for_redis.html" title="15.&nbsp;Cloud Memorystore for Redis"></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">14.&nbsp;Spring Data Cloud Datastore</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_data_cloud_spanner.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_memorystore_for_redis.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_data_cloud_datastore" href="#_spring_data_cloud_datastore"></a>14.&nbsp;Spring Data Cloud Datastore</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/datastore/" target="_top">Google Cloud Datastore</a>.</p><p>Maven coordinates for this module only, using <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/blob/master/spring-cloud-gcp-dependencies/pom.xml" target="_top">Spring Cloud GCP BOM</a>:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-data-datastore<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-data-datastore'
}</pre><p>We provide a <a class="link" href="../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore" target="_top">Spring Boot Starter for Spring Data Datastore</a>, with which you can use our recommended auto-configuration setup.
To use the starter, see the coordinates below.</p><p>Maven:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-data-datastore<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-data-datastore'
}</pre><p>This setup takes care of bringing in the latest compatible version of Cloud Java Cloud Datastore libraries as well.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_5" href="#_configuration_5"></a>14.1&nbsp;Configuration</h2></div></div></div><p>To setup Spring Data Cloud Datastore, 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 Datastore.</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_cloud_datastore_settings" href="#_cloud_datastore_settings"></a>14.1.1&nbsp;Cloud Datastore settings</h3></div></div></div><p>You can the use <a class="link" href="../spring-cloud-gcp-starters/spring-cloud-gcp-starter-data-datastore" target="_top">Spring Boot Starter for Spring Data Datastore</a> to autoconfigure Google Cloud Datastore 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.datastore.enabled</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Enables the Cloud Datastore client</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.datastore.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 Datastore API is hosted, if different from the one in the <a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.datastore.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 Datastore API, if different from the ones in the
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.datastore.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 Datastore API, if different from the ones in the
<a class="link" href="multi_spring-cloud-gcp-core.html" title="4.&nbsp;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">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.datastore.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 Datastore 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/datastore" target="_top">https://www.googleapis.com/auth/datastore</a></p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.datastore.namespace</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>The Cloud Datastore namespace to use</p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>No</p></td><td style="" align="left" valign="top"><p>the Default namespace of Cloud Datastore in your GCP project</p></td></tr></tbody></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_repository_settings_2" href="#_repository_settings_2"></a>14.1.2&nbsp;Repository settings</h3></div></div></div><p>Spring Data Repositories can be configured via the <code class="literal">@EnableDatastoreRepositories</code> annotation on your main <code class="literal">@Configuration</code> class.
With our Spring Boot Starter for Spring Data Cloud Datastore, <code class="literal">@EnableDatastoreRepositories</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-datastore/src/main/java/org/springframework/cloud/gcp/data/datastore/repository/config/EnableDatastoreRepositories.java" target="_top"><code class="literal">@EnableDatastoreRepositories</code></a>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_autoconfiguration_2" href="#_autoconfiguration_2"></a>14.1.3&nbsp;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">DatastoreTemplate</code></li><li class="listitem">an instance of all user defined repositories extending <code class="literal">CrudRepository</code>, <code class="literal">PagingAndSortingRepository</code>, and <code class="literal">DatastoreRepository</code> (an extension of <code class="literal">PagingAndSortingRepository</code> with additional Cloud Datastore features) when repositories are enabled</li><li class="listitem">an instance of <code class="literal">Datastore</code> from the Google Cloud Java Client for Datastore, 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_2" href="#_object_mapping_2"></a>14.2&nbsp;Object Mapping</h2></div></div></div><p>Spring Data Cloud Datastore allows you to map domain POJOs to Cloud Datastore kinds and entities via annotations:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity(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/">@Id</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Field(name = "trader_id")</xslthl:annotation>
String traderId;
String firstName;
String lastName;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Transient</xslthl:annotation>
Double temporaryNumber;
}</pre><p>Spring Data Cloud Datastore will ignore any property annotated with <code class="literal">@Transient</code>.
These properties will not be written to or read from Cloud Datastore.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_constructors_2" href="#_constructors_2"></a>14.2.1&nbsp;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/">@Entity(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/">@Id</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Field(name = "trader_id")</xslthl:annotation>
String traderId;
String firstName;
String lastName;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Transient</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="_kind" href="#_kind"></a>14.2.2&nbsp;Kind</h3></div></div></div><p>The <code class="literal">@Entity</code> annotation can provide the name of the Cloud Datastore kind that stores instances of the annotated class, one per row.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_keys" href="#_keys"></a>14.2.3&nbsp;Keys</h3></div></div></div><p><code class="literal">@Id</code> identifies the property corresponding to the ID value.</p><p>You must annotate one of your POJO&#8217;s fields as the ID value, because every entity in Cloud Datastore requires a single ID value:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity(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/">@Id</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Field(name = "trade_id")</xslthl:annotation>
String tradeId;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Field(name = "trader_id")</xslthl:annotation>
String traderId;
String action;
Double price;
Double shares;
String symbol;
}</pre><p>Datastore can automatically allocate integer ID values.
If a POJO instance with a <code class="literal">Long</code> ID property is written to Cloud Datastore with <code class="literal">null</code> as the ID value, then Spring Data Cloud Datastore will obtain a newly allocated ID value from Cloud Datastore and set that in the POJO for saving.
Because primitive <code class="literal">long</code> ID properties cannot be <code class="literal">null</code> and default to <code class="literal">0</code>, keys will not be allocated.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_fields" href="#_fields"></a>14.2.4&nbsp;Fields</h3></div></div></div><p>All accessible properties on POJOs are automatically recognized as a Cloud Datastore field.
Field naming is generated by the <code class="literal">PropertyNameFieldNamingStrategy</code> by default defined on the <code class="literal">DatastoreMappingContext</code> bean.
The <code class="literal">@Field</code> annotation optionally provides a different field name than that of the property.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_supported_types_2" href="#_supported_types_2"></a>14.2.5&nbsp;Supported Types</h3></div></div></div><p>Spring Data Cloud Datastore supports the following types for regular fields and elements of collections:</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"></colgroup><thead><tr><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Type</th><th style="border-bottom: 1px solid ; " align="left" valign="top">Stored as</th></tr></thead><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">com.google.cloud.Timestamp</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.TimestampValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">com.google.cloud.datastore.Blob</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.BlobValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">com.google.cloud.datastore.LatLng</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.LatLngValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">java.lang.Boolean</code>, <code class="literal">boolean</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.BooleanValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">java.lang.Double</code>, <code class="literal">double</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.DoubleValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">java.lang.Long</code>, <code class="literal">long</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.LongValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">java.lang.Integer</code>, <code class="literal">int</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.LongValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">java.lang.String</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.StringValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">com.google.cloud.datastore.Entity</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.EntityValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">com.google.cloud.datastore.Key</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.KeyValue</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">byte[]</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>com.google.cloud.datastore.BlobValue</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p>Java <code class="literal">enum</code> values</p></td><td style="" align="left" valign="top"><p>com.google.cloud.datastore.StringValue</p></td></tr></tbody></table></div><p>In addition, all types that can be converted to the ones listed in the table by
<code class="literal">org.springframework.core.convert.support.DefaultConversionService</code> are supported.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_types_2" href="#_custom_types_2"></a>14.2.6&nbsp;Custom types</h3></div></div></div><p>Custom converters can be used extending 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">The user defined type needs to be mapped to one of the basic types supported by Cloud Datastore.</li><li class="listitem">An instance of both Converters (read and write) needs to be passed to the <code class="literal">DatastoreCustomConversions</code> constructor, which then has to be made available as a <code class="literal">@Bean</code> for <code class="literal">DatastoreCustomConversions</code>.</li></ol></div><p>For example:</p><p>We would like to have a field of type <code class="literal">Album</code> on our <code class="literal">Singer</code> POJO and want it to be stored as a string property:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> Singer {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
String singerId;
String name;
Album album;
}</pre><p>Where Album 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> Album {
String albumName;
LocalDate date;
}</pre><p>We have to define the two converters:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//Converter to write custom Album type</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Converter&lt;Album, String&gt; ALBUM_STRING_CONVERTER =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Converter&lt;Album, String&gt;() {
<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(Album album) {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> album.getAlbumName() + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" "</span> + album.getDate().format(DateTimeFormatter.ISO_DATE);
}
};
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//Converters to read custom Album type</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Converter&lt;String, Album&gt; STRING_ALBUM_CONVERTER =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Converter&lt;String, Album&gt;() {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Override</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Album convert(String s) {
String[] parts = s.split(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" "</span>);
<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> Album(parts[<xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">0</xslthl:number>], LocalDate.parse(parts[parts.length - <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">1</xslthl:number>], DateTimeFormatter.ISO_DATE));
}
};</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> DatastoreCustomConversions datastoreCustomConversions() {
<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> DatastoreCustomConversions(
Arrays.asList(
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_collections_and_arrays" href="#_collections_and_arrays"></a>14.2.7&nbsp;Collections and arrays</h3></div></div></div><p>Arrays and collections (types that implement <code class="literal">java.util.Collection</code>) of supported types are supported.
They are stored as <code class="literal">com.google.cloud.datastore.ListValue</code>.
Elements are converted to Cloud Datastore supported types individually. <code class="literal">byte[]</code> is an exception, it is converted to
<code class="literal">com.google.cloud.datastore.Blob</code>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_converter_for_collections" href="#_custom_converter_for_collections"></a>14.2.8&nbsp;Custom Converter for collections</h3></div></div></div><p>Users can provide converters from <code class="literal">List&lt;?&gt;</code> to the custom collection type.
Only read converter is necessary, the Collection API is used on the write side to convert a collection to the internal list type.</p><p>Collection converters need to implement the <code class="literal">org.springframework.core.convert.converter.Converter</code> interface.</p><p>Example:</p><p>Let&#8217;s improve the Singer class from the previous example.
Instead of a field of type <code class="literal">Album</code>, we would like to have a field of type <code class="literal">ImmutableSet&lt;Album&gt;</code>:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> Singer {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
String singerId;
String name;
ImmutableSet&lt;Album&gt; albums;
}</pre><p>We have to define a read converter only:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Converter&lt;List&lt;?&gt;, ImmutableSet&lt;?&gt;&gt; LIST_IMMUTABLE_SET_CONVERTER =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Converter&lt;List&lt;?&gt;, ImmutableSet&lt;?&gt;&gt;() {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Override</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> ImmutableSet&lt;?&gt; convert(List&lt;?&gt; source) {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ImmutableSet.copyOf(source);
}
};</pre><p>And add it to the list of custom converters:</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> DatastoreCustomConversions datastoreCustomConversions() {
<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> DatastoreCustomConversions(
Arrays.asList(
LIST_IMMUTABLE_SET_CONVERTER,
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_relationships_2" href="#_relationships_2"></a>14.3&nbsp;Relationships</h2></div></div></div><p>There are three ways to represent relationships between entities that are described in this section:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Embedded entities stored directly in the field of the containing entity</li><li class="listitem"><code class="literal">@Descendant</code> annotated properties for one-to-many relationships</li><li class="listitem"><code class="literal">@Reference</code> annotated properties for general relationships without hierarchy</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_embedded_entities" href="#_embedded_entities"></a>14.3.1&nbsp;Embedded Entities</h3></div></div></div><p>Fields whose types are also annotated with <code class="literal">@Entity</code> are converted to <code class="literal">EntityValue</code> and stored inside the parent entity.</p><p>Here is an example of Cloud Datastore entity containing an embedded entity in JSON:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"name"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Alexander"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"age"</span> : <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">47</xslthl:number><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"child"</span> : {<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"name"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Philip"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span></pre><p>This corresponds to a simple pair of Java entities:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.data.annotation.Id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity("parents")</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> Parent {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
String name;
Child child;
}
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> Child {
String name;
}</pre><p><code class="literal">Child</code> entities are not stored in their own kind.
They are stored in their entirety in the <code class="literal">child</code> field of the <code class="literal">parents</code> kind.</p><p>Multiple levels of embedded entities are supported.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Embedded entities don&#8217;t need to have <code class="literal">@Id</code> field, it is only required for top level entities.</p></td></tr></table></div><p>Example:</p><p>Entities can hold embedded entities that are their own type.
We can store trees in Cloud Datastore using this feature:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.data.annotation.Id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> EmbeddableTreeNode {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> value;
EmbeddableTreeNode left;
EmbeddableTreeNode right;
Map&lt;String, Long&gt; longValues;
Map&lt;String, List&lt;Timestamp&gt;&gt; listTimestamps;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> EmbeddableTreeNode(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.value = value;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.left = left;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.right = right;
}
}</pre><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_maps" href="#_maps"></a>Maps</h4></div></div></div><p>Maps will be stored as embedded entities where the key values become the field names in the embedded entity.
The value types in these maps can be any regularly supported property type, and the key values will be converted to String using the configured converters.</p><p>Also, a collection of entities can be embedded; it will be converted to <code class="literal">ListValue</code> on write.</p><p>Example:</p><p>Instead of a binary tree from the previous example, we would like to store a general tree
(each node can have an arbitrary number of children) in Cloud Datastore.
To do that, we need to create a field of type <code class="literal">List&lt;EmbeddableTreeNode&gt;</code>:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.data.annotation.Id;
<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> EmbeddableTreeNode {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> value;
List&lt;EmbeddableTreeNode&gt; children;
Map&lt;String, EmbeddableTreeNode&gt; siblingNodes;
Map&lt;String, Set&lt;EmbeddableTreeNode&gt;&gt; subNodeGroups;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> EmbeddableTreeNode(List&lt;EmbeddableTreeNode&gt; children) {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.children = children;
}
}</pre><p>Because Maps are stored as entities, they can further hold embedded entities:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Singular embedded objects in the value can be stored in the values of embedded Maps.</li><li class="listitem">Collections of embedded objects in the value can also be stored as the values of embedded Maps.</li><li class="listitem">Maps in the value are further stored as embedded entities with the same rules applied recursively for their values.</li></ul></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_ancestor_descendant_relationships" href="#_ancestor_descendant_relationships"></a>14.3.2&nbsp;Ancestor-Descendant Relationships</h3></div></div></div><p>Parent-child relationships are supported via the <code class="literal">@Descendants</code> annotation.</p><p>Unlike embedded children, descendants are fully-formed entities residing in their own kinds.
The parent entity does not have an extra field to hold the descendant entities.
Instead, the relationship is captured in the descendants' keys, which refer to their parent entities:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Descendants;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.data.annotation.Id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity("orders")</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> ShoppingOrder {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Descendants</xslthl:annotation>
List&lt;Item&gt; items;
}
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity("purchased_item")</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> Item {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}</pre><p>For example, an instance of a GQL key-literal representation for <code class="literal">Item</code> would also contain the parent <code class="literal">ShoppingOrder</code> ID value:</p><pre class="screen">Key(orders, '12345', purchased_item, 'eggs')</pre><p>The GQL key-literal representation for the parent <code class="literal">ShoppingOrder</code> would be:</p><pre class="screen">Key(orders, '12345')</pre><p>The Cloud Datastore entities exist separately in their own kinds.</p><p>The <code class="literal">ShoppingOrder</code>:</p><pre class="screen">{
"id" : 12345
}</pre><p>The two items inside that order:</p><pre class="screen">{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
"name" : "eggs",
"timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
}
{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
"name" : "sausage",
"timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
}</pre><p>The parent-child relationship structure of objects is stored in Cloud Datastore using Datastore&#8217;s <a class="link" href="https://cloud.google.com/datastore/docs/concepts/entities#ancestor_paths" target="_top">ancestor relationships</a>.
Because the relationships are defined by the Ancestor mechanism, there is no extra column needed in either the parent or child entity to store this relationship.
The relationship link is part of the descendant entity&#8217;s key value.
These relationships can be many levels deep.</p><p>Properties holding child entities must be collection-like, but they can be any of the supported inter-convertible collection-like types that are supported for regular properties such as <code class="literal">List</code>, arrays, <code class="literal">Set</code>, etc&#8230;&#8203;
Child items must have <code class="literal">Key</code> as their ID type because Cloud Datastore stores the ancestor relationship link inside the keys of the children.</p><p>Reading or saving an entity automatically causes all subsequent levels of children under that entity to be read or saved, respectively.
If a new child is created and added to a property annotated <code class="literal">@Descendants</code> and the key property is left null, then a new key will be allocated for that child.
The ordering of the retrieved children may not be the same as the ordering in the original property that was saved.</p><p>Child entities cannot be moved from the property of one parent to that of another unless the child&#8217;s key property is set to <code class="literal">null</code> or a value that contains the new parent as an ancestor.
Since Cloud Datastore entity keys can have multiple parents, it is possible that a child entity appears in the property of multiple parent entities.
Because entity keys are immutable in Cloud Datastore, to change the key of a child you must delete the existing one and re-save it with the new key.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_key_reference_relationships" href="#_key_reference_relationships"></a>14.3.3&nbsp;Key Reference Relationships</h3></div></div></div><p>General relationships can be stored using the <code class="literal">@Reference</code> annotation.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Reference;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.data.annotation.Id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> ShoppingOrder {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> id;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Reference</xslthl:annotation>
List&lt;Item&gt; items;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Reference</xslthl:annotation>
Item specialSingleItem;
}
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Entity</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> Item {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Id</xslthl:annotation>
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}</pre><p><code class="literal">@Reference</code> relationships are between fully-formed entities residing in their own kinds.
The relationship between <code class="literal">ShoppingOrder</code> and <code class="literal">Item</code> entities are stored as a Key field inside <code class="literal">ShoppingOrder</code>, which are resolved to the underlying Java entity type by Spring Data Cloud Datastore:</p><pre class="screen">{
"id" : 12345,
"specialSingleItem" : Key(item, "milk"),
"items" : [ Key(item, "eggs"), Key(item, "sausage") ]
}</pre><p>Reference properties can either be singular or collection-like.
These properties correspond to actual columns in the entity and Cloud Datastore Kind that hold the key values of the referenced entities.
The referenced entities are full-fledged entities of other Kinds.</p><p>Similar to the <code class="literal">@Descendants</code> relationships, reading or writing an entity will recursively read or write all of the referenced entities at all levels.
If referenced entities have <code class="literal">null</code> ID values, then they will be saved as new entities and will have ID values allocated by Cloud Datastore.
There are no requirements for relationships between the key of an entity and the keys that entity holds as references.
The order of collection-like reference properties is not preserved when reading back from Cloud Datastore.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_datastore_operations_template" href="#_datastore_operations_template"></a>14.4&nbsp;Datastore Operations &amp; Template</h2></div></div></div><p><code class="literal">DatastoreOperations</code> and its implementation, <code class="literal">DatastoreTemplate</code>, provides the Template pattern familiar to Spring developers.</p><p>Using the auto-configuration provided by Spring Boot Starter for Datastore, your Spring application context will contain a fully configured <code class="literal">DatastoreTemplate</code> object that you can 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> DatastoreTemplateExample {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
DatastoreTemplate datastoreTemplate;
<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>.datastoreTemplate.deleteAll(Trader.<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>
Trader t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trader();
<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>.datastoreTemplate.save(t);
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
List&lt;Trader&gt; traders = datastoreTemplate.findAll(Trader.<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">Write operations (saving and deleting)</li><li class="listitem">Read-write transactions</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_gql_query" href="#_gql_query"></a>14.4.1&nbsp;GQL Query</h3></div></div></div><p>In addition to retrieving entities by their IDs, you can also submit queries.</p><pre class="programlisting"> &lt;T&gt; Iterable&lt;T&gt; query(Query&lt;? <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> BaseEntity&gt; query, Class&lt;T&gt; entityClass);
&lt;A, T&gt; Iterable&lt;T&gt; query(Query&lt;A&gt; query, Function&lt;A, T&gt; entityFunc);
Iterable&lt;Key&gt; queryKeys(Query&lt;Key&gt; query);</pre><p>These methods, respectively, allow querying for:
* entities mapped by a given entity class using all the same mapping and converting features
* arbitrary types produced by a given mapping function
* only the Cloud Datastore keys of the entities found by the query</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_find_by_ids" href="#_find_by_ids"></a>14.4.2&nbsp;Find by ID(s)</h3></div></div></div><p>Datstore reading a single entity or multiple entities in a kind.</p><p>Using <code class="literal">DatastoreTemplate</code> you can execute reads, for example:</p><pre class="programlisting">Trader trader = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.datastoreTemplate.findById(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"trader1"</span>, Trader.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
List&lt;Trader&gt; traders = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.datastoreTemplate.findAllById(ImmutableList.of(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"trader1"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"trader2"</span>), Trader.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
List&lt;Trader&gt; allTraders = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.datastoreTemplate.findAll(Trader.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);</pre><p>Cloud Datastore executes key-based reads with strong consistency, but queries with eventual consistency.
In the example above the first two reads utilize keys, while the third is executed using a query based on the corresponding Kind of <code class="literal">Trader</code>.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_indexes" href="#_indexes"></a>Indexes</h4></div></div></div><p>By default, all fields are indexed.
To disable indexing on a particular field, <code class="literal">@Unindexed</code> annotation can be used.</p><p>Example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed;
<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> ExampleItem {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> indexedField;
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Unindexed</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> unindexedField;
}</pre><p>When using queries directly or via Query Methods, Cloud Datastore requires <a class="link" href="https://cloud.google.com/datastore/docs/concepts/indexes" target="_top">composite custom indexes</a> if the select statement is not <code class="literal">SELECT *</code> or if there is more than one filtering condition in the <code class="literal">WHERE</code> clause.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_read_with_offsets_limits_and_sorting" href="#_read_with_offsets_limits_and_sorting"></a>Read with offsets, limits, and sorting</h4></div></div></div><p><code class="literal">DatastoreRepository</code> and custom-defined entity repositories implement the Spring Data <code class="literal">PagingAndSortingRepository</code>, which supports offsets and limits using page numbers and page sizes.
Paging and sorting options are also supported in <code class="literal">DatastoreTemplate</code> by supplying a <code class="literal">DatastoreQueryOptions</code> to <code class="literal">findAll</code>.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_partial_read_2" href="#_partial_read_2"></a>Partial read</h4></div></div></div><p>This feature is not supported yet.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_write_update_2" href="#_write_update_2"></a>14.4.3&nbsp;Write / Update</h3></div></div></div><p>The write methods of <code class="literal">DatastoreOperations</code> accept a POJO and writes all of its properties to Datastore.
The required Datastore kind and entity metadata is obtained from the given object&#8217;s actual type.</p><p>If a POJO was retrieved from Datastore and its ID value was changed and then written or updated, the operation will occur as if against a row with the new ID value.
The entity with the original ID value will not be affected.</p><pre class="programlisting">Trader t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trader();
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.datastoreTemplate.save(t);</pre><p>The <code class="literal">save</code> method behaves as update-or-insert.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_partial_update_2" href="#_partial_update_2"></a>Partial Update</h4></div></div></div><p>This feature is not supported yet.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_transactions_2" href="#_transactions_2"></a>14.4.4&nbsp;Transactions</h3></div></div></div><p>Read and write transactions are provided by <code class="literal">DatastoreOperations</code> via the <code class="literal">performTransaction</code> method:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Autowired</xslthl:annotation>
DatastoreOperations myDatastoreOperations;
<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> myDatastoreOperations.performTransaction(
transactionDatastoreOperations -&gt; {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Work with transactionDatastoreOperations here.</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// It is also a DatastoreOperations 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">performTransaction</code> method accepts a <code class="literal">Function</code> that is provided an instance of a <code class="literal">DatastoreOperations</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">DatastoreOperations</code> with an exception:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">It cannot perform sub-transactions.</li></ul></div><p>Because of Cloud Datastore&#8217;s consistency guarantees, there are <a class="link" href="https://cloud.google.com/datastore/docs/concepts/transactions#what_can_be_done_in_a_transaction" target="_top">limitations</a> to the operations and relationships among entities used inside transactions.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_declarative_transactions_with_transactional_annotation_2" href="#_declarative_transactions_with_transactional_annotation_2"></a>Declarative Transactions with @Transactional Annotation</h4></div></div></div><p>This feature requires a bean of <code class="literal">DatastoreTransactionManager</code>, which is provided when using <code class="literal">spring-cloud-gcp-starter-data-datastore</code>.</p><p><code class="literal">DatastoreTemplate</code> and <code class="literal">DatastoreRepository</code> support running methods with the <code class="literal">@Transactional</code> <a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative" target="_top">annotation</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">performTransaction</code> cannot be used in <code class="literal">@Transactional</code> annotated methods because Cloud Datastore does not support transactions within transactions.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_read_write_support_for_maps" href="#_read_write_support_for_maps"></a>14.4.5&nbsp;Read-Write Support for Maps</h3></div></div></div><p>You can work with Maps of type <code class="literal">Map&lt;String, ?&gt;</code> instead of with entity objects by directly reading and writing them to and from Cloud Datastore.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>This is a different situation than using entity objects that contain Map properties.</p></td></tr></table></div><p>The map keys are used as field names for a Datastore entity and map values are converted to Datastore supported types.
Only simple types are supported (i.e. collections are not supported).
Converters for custom value types can be added (see <a class="xref" href="multi__spring_data_cloud_spanner.html#_custom_types" title="13.2.10&nbsp;Custom types">Section&nbsp;13.2.10, &#8220;Custom types&#8221;</a> section).</p><p>Example:</p><pre class="programlisting">Map&lt;String, Long&gt; map = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> HashMap&lt;&gt;();
map.put(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"field1"</span>, <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">1L</xslthl:number>);
map.put(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"field2"</span>, <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">2L</xslthl:number>);
map.put(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"field3"</span>, <xslthl:number xmlns:xslthl="http://xslthl.sourceforge.net/">3L</xslthl:number>);
keyForMap = datastoreTemplate.createKey(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"kindName"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"id"</span>);
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//write a map</span>
datastoreTemplate.writeMap(keyForMap, map);
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//read a map</span>
Map&lt;String, Long&gt; loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_repositories_2" href="#_repositories_2"></a>14.5&nbsp;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 an abstraction that can reduce 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> DatastoreRepository&lt;Trader, String&gt; {
}</pre><p>Spring Data generates a working implementation of the specified interface, which can be autowired into an application.</p><p>The <code class="literal">Trader</code> type parameter to <code class="literal">DatastoreRepository</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><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>
TraderRepository traderRepository;
<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>.traderRepository.deleteAll();
String traderId = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"demo_trader"</span>;
Trader t = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Trader();
t.traderId = traderId;
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.tradeRepository.save(t);
Iterable&lt;Trader&gt; allTraders = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.traderRepository.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>.traderRepository.count();
}
}</pre><p>Repositories allow you to define custom Query Methods (detailed in the following sections) for retrieving, counting, and deleting based on filtering and paging parameters.
Filtering parameters can be of types supported by your configured custom converters.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_query_methods_by_convention_2" href="#_query_methods_by_convention_2"></a>14.5.1&nbsp;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> DatastoreRepository&lt;Trade, String[]&gt; {
List&lt;Trader&gt; 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-keyword">boolean</span> existsByAction(String action);
List&lt;Trade&gt; findTop3ByActionAndSymbolAndPriceGreaterThanAndPriceLessThanOrEqualOrderBySymbolDesc(
String action, String symbol, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">double</span> priceFloor, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">double</span> priceCeiling);
Page&lt;TestEntity&gt; findByAction(String action, Pageable pageable);
Slice&lt;TestEntity&gt; findBySymbol(String symbol, Pageable pageable);
List&lt;TestEntity&gt; findBySymbol(String symbol, Sort sort);
}</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 thehttps://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods.query-creation[Spring Data Query creation naming convention].</p><p>Cloud Datastore only supports filter components joined by AND, and the following operations:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">equals</code></li><li class="listitem"><code class="literal">greater than or equals</code></li><li class="listitem"><code class="literal">greater than</code></li><li class="listitem"><code class="literal">less than or equals</code></li><li class="listitem"><code class="literal">less than</code></li><li class="listitem"><code class="literal">is null</code></li></ul></div><p>After writing a custom repository interface specifying just the signatures of these methods, implementations are generated for you and can be used with an auto-wired instance of the repository.
Because of Cloud Datastore&#8217;s requirement that explicitly selected fields must all appear in a composite index together, <code class="literal">find</code> name-based query methods are run as <code class="literal">SELECT *</code>.</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>.
Delete queries are executed as separate read and delete operations instead of as a single transaction because Cloud Datastore cannot query in transactions unless ancestors for queries are specified.
As a result, <code class="literal">removeBy</code> and <code class="literal">deleteBy</code> name-convention query methods cannot be used inside transactions via either <code class="literal">performInTransaction</code> or <code class="literal">@Transactional</code> annotation.</p><p>Delete queries can have the following return types:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">An integer type that is the number of entities deleted</li><li class="listitem">A collection of entities that were deleted</li><li class="listitem">'void'</li></ul></div><p>Methods can have <code class="literal">org.springframework.data.domain.Pageable</code> parameter to control pagination and sorting, or <code class="literal">org.springframework.data.domain.Sort</code> parameter to control sorting only.
See <a class="link" href="https://docs.spring.io/spring-data/data-commons/docs/current/reference/html/#repositories.query-methods" target="_top">Spring Data documentation</a> for details.</p><p>For returning multiple items in a repository method, we support Java collections as well as <code class="literal">org.springframework.data.domain.Page</code> and <code class="literal">org.springframework.data.domain.Slice</code>.
If a method&#8217;s return type is <code class="literal">org.springframework.data.domain.Page</code>, the returned object will include current page, total number of results and total number of pages.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Methods that return <code class="literal">Page</code> execute an additional query to compute total number of pages.
Methods that return <code class="literal">Slice</code>, on the other hand, don&#8217;t execute any additional queries and therefore are much more efficient.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_custom_gql_query_methods" href="#_custom_gql_query_methods"></a>14.5.2&nbsp;Custom GQL query methods</h3></div></div></div><p>Custom GQL queries 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><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_query_methods_with_annotation_2" href="#_query_methods_with_annotation_2"></a>Query methods with annotation</h4></div></div></div><p>Using the <code class="literal">@Query</code> annotation:</p><p>The names of the tags of the GQL correspond to the <code class="literal">@Param</code> annotated names of the method parameters.</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> DatastoreRepository&lt;Trader, String&gt; {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM traders WHERE name = @trader_name")</xslthl:annotation>
List&lt;Trader&gt; tradersByName(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("trader_name")</xslthl:annotation> String traderName);
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM test_entities_ci WHERE id = @id_val")</xslthl:annotation>
TestEntity getOneTestEntity(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("id_val")</xslthl:annotation> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> id);
}</pre><p>The following parameter types are supported:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">com.google.cloud.Timestamp</code></li><li class="listitem"><code class="literal">com.google.cloud.datastore.Blob</code></li><li class="listitem"><code class="literal">com.google.cloud.datastore.Key</code></li><li class="listitem"><code class="literal">com.google.cloud.datastore.Cursor</code></li><li class="listitem"><code class="literal">java.lang.Boolean</code></li><li class="listitem"><code class="literal">java.lang.Double</code></li><li class="listitem"><code class="literal">java.lang.Long</code></li><li class="listitem"><code class="literal">java.lang.String</code></li><li class="listitem"><code class="literal">enum</code> values.
These are queried as <code class="literal">String</code> values.</li></ul></div><p>With the exception of <code class="literal">Cursor</code>, array forms of each of the types are also supported.</p><p>If you would like to obtain the count of items of a query or if there are any items returned by the query, set the <code class="literal">count = true</code> or <code class="literal">exists = true</code> properties of the <code class="literal">@Query</code> annotation, respectively.
The return type of the query method in these cases should be an integer type or a boolean type.</p><p>Cloud Datastore provides provides the <code class="literal">SELECT <span class="emphasis"><em>key</em></span> FROM &#8230;&#8203;</code> special column for all kinds that retrieves the <code class="literal">Key`s of each row.
Selecting this special `<span class="emphasis"><em>key</em></span></code> column is especially useful and efficient for <code class="literal">count</code> and <code class="literal">exists</code> queries.</p><p>You can also query for non-entity types:</p><pre class="programlisting"> <xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query(value = "SELECT __key__ from test_entities_ci")</xslthl:annotation>
List&lt;Key&gt; getKeys();
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query(value = "SELECT __key__ from test_entities_ci limit 1")</xslthl:annotation>
Key getKey();
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT id FROM test_entities_ci WHERE id &lt;= @id_val")</xslthl:annotation>
List&lt;String&gt; getIds(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("id_val")</xslthl:annotation> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> id);
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT id FROM test_entities_ci WHERE id &lt;= @id_val limit 1")</xslthl:annotation>
String getOneId(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("id_val")</xslthl:annotation> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">long</span> id);</pre><p>SpEL can be used to provide GQL parameters:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act
AND price &gt; :#{#priceRadius * -1} AND price &lt; :#{#priceRadius * 2}")</xslthl:annotation>
List&lt;Trade&gt; fetchByActionNamedQuery(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("act")</xslthl:annotation> String action, <xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("priceRadius")</xslthl:annotation> Double r);</pre><p>Kind names can be directly written in the GQL annotations.
Kind names can also be resolved from the <code class="literal">@Entity</code> annotation on domain classes.</p><p>In this case, the query should refer to table names with fully qualified class names surrounded by <code class="literal">|</code> characters: <code class="literal">|fully.qualified.ClassName|</code>.
This is useful when SpEL expressions appear in the kind name provided to the <code class="literal">@Entity</code> annotation.
For example:</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")</xslthl:annotation>
List&lt;Trade&gt; fetchByActionNamedQuery(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("act")</xslthl:annotation> String action);</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_query_methods_with_named_queries_properties_2" href="#_query_methods_with_named_queries_properties_2"></a>Query methods with named queries properties</h4></div></div></div><p>You can also specify queries with Cloud Datastore parameter tags and SpEL expressions in properties files.</p><p>By default, the <code class="literal">namedQueriesLocation</code> attribute on <code class="literal">@EnableDatastoreRepositories</code> points to the <code class="literal">META-INF/datastore-named-queries.properties</code> file.
You can specify the query for a method in the properties file by providing the GQL as the value for the "interface.method" property:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">Trader.fetchByName</span>=SELECT * FROM traders WHERE name = @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> TraderRepository <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> DatastoreRepository&lt;Trader, String&gt; {
<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&lt;Trader&gt; fetchByName(<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Param("tag0")</xslthl:annotation> String traderName);
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_transactions_3" href="#_transactions_3"></a>14.5.3&nbsp;Transactions</h3></div></div></div><p>These transactions work very similarly to those of <code class="literal">DatastoreOperations</code>, but is specific to the repository&#8217;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>
DatastoreRepository 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.performTransaction(
transactionDatastoreRepo -&gt; {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Work with the single-transaction transactionDatastoreRepo here.</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// This is a DatastoreRepository 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></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_projections_2" href="#_projections_2"></a>14.5.4&nbsp;Projections</h3></div></div></div><p>Spring Data Cloud Datastore 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> DatastoreRepository&lt;Trade, Key&gt; {
List&lt;Trade&gt; findByTraderId(String traderId);
List&lt;TradeProjection&gt; findByAction(String action);
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT action, symbol FROM trades WHERE action = @action")</xslthl:annotation>
List&lt;TradeProjection&gt; findByQuery(String action);
}</pre><p>Projections can be provided by name-convention-based query methods as well as by custom GQL queries.
If using custom GQL queries, you can further restrict the fields retrieved from Cloud Datastore to just those required by the projection.
However, custom select statements (those not using <code class="literal">SELECT *</code>) require composite indexes containing the selected fields.</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.&lt;property-name&gt;</code>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_rest_repositories_2" href="#_rest_repositories_2"></a>14.5.5&nbsp;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">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.boot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-boot-starter-data-rest<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</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> DatastoreRepository&lt;Trade, String[]&gt; {
}</pre><p>For example, you can retrieve all <code class="literal">Trade</code> objects in the repository by using <code class="literal">curl http://&lt;server&gt;:&lt;port&gt;/trades</code>, or any specific trade via <code class="literal">curl http://&lt;server&gt;:&lt;port&gt;/trades/&lt;trader_id&gt;</code>.</p><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://&lt;server&gt;:&lt;port&gt;/trades/</code> where the file <code class="literal">test.json</code> holds the JSON representation of a <code class="literal">Trade</code> object.</p><p>To delete trades, you can use <code class="literal">curl -XDELETE http://&lt;server&gt;:&lt;port&gt;/trades/&lt;trader_id&gt;</code></p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_10" href="#_sample_10"></a>14.6&nbsp;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-datastore-basic-sample" target="_top">Simple Spring Boot Application</a> and more advanced <a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-sample" target="_top">Sample Spring Boot Application</a> are provided to show how to use the Spring Data Cloud Datastore starter and template.</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_data_cloud_spanner.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__cloud_memorystore_for_redis.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">13.&nbsp;Spring Data Cloud Spanner&nbsp;</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">&nbsp;15.&nbsp;Cloud Memorystore for Redis</td></tr></table></div></body></html>

View File

@@ -0,0 +1,465 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>13.&nbsp;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.&nbsp;Spring Cloud Config"><link rel="next" href="multi__spring_data_cloud_datastore.html" title="14.&nbsp;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.&nbsp;Spring Data Cloud Spanner</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_config.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<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.&nbsp;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">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-data-spanner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</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">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-data-spanner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</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&nbsp;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&nbsp;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">&nbsp;</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">&nbsp;</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.&nbsp;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">&nbsp;</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.&nbsp;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">&nbsp;</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.&nbsp;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">&nbsp;</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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&#8217;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&nbsp;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&nbsp;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&nbsp;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&lt;Album&gt; 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&lt;String&gt; 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&nbsp;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&nbsp;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&lt;Double&gt; 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&nbsp;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&lt;STRUCT&gt;</code> column: <code class="literal">List&lt;TwoInts&gt; 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&nbsp;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&lt;Person, String&gt; {
<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&lt;String, Person&gt; {
<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&nbsp;Custom Converter for Struct Array Columns</h3></div></div></div><p>If a <code class="literal">Converter&lt;Struct, A&gt;</code> is provided, then properties of type <code class="literal">List&lt;A&gt;</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&nbsp;Spanner Operations &amp; 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&lt;Trade&gt; 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&nbsp;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&lt;Trade&gt; 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&nbsp;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&lt;Trade&gt; 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&nbsp;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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&nbsp;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&#8217;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&#8217;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&#8217;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&lt;Set&lt;String&gt;&gt;</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&nbsp;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&nbsp;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 -&gt; {
<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 -&gt; {
<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&nbsp;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&nbsp;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&lt;Trader, String&gt; {
}</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&lt;Trade, String[]&gt; {
}</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&lt;Trade&gt; 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&nbsp;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&nbsp;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&nbsp;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&#8217;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 -&gt; {
<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&nbsp;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&nbsp;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&lt;Trade, String[]&gt; {
List&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&nbsp;Custom SQL/DML query methods</h3></div></div></div><p>The example above for <code class="literal">List&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;Trade&gt; 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&lt;String&gt; 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&lt;Trade, String[]&gt; {
<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&lt;Trade&gt; 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&lt;Trade, String[]&gt; {
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT * FROM trades WHERE trades.action = @tag0")</xslthl:annotation>
List&lt;Trade&gt; 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&lt;Trade&gt; 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 &gt; #{#priceRadius * -1} AND price &lt; #{#priceRadius * 2}")</xslthl:annotation>
List&lt;Trade&gt; 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&nbsp;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&lt;Trade, Key&gt; {
List&lt;Trade&gt; findByTraderId(String traderId);
List&lt;TradeProjection&gt; findByAction(String action);
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Query("SELECT action, symbol FROM trades WHERE action = @action")</xslthl:annotation>
List&lt;TradeProjection&gt; 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.&lt;property-name&gt;</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&nbsp;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">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.boot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-boot-starter-data-rest<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</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&lt;Trade, String[]&gt; {
}</pre><p>For example, you can retrieve all <code class="literal">Trade</code> objects in the repository by using <code class="literal">curl http://&lt;server&gt;:&lt;port&gt;/trades</code>, or any specific trade via <code class="literal">curl http://&lt;server&gt;:&lt;port&gt;/trades/&lt;trader_id&gt;,&lt;trade_id&gt;</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://&lt;server&gt;:&lt;port&gt;/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&nbsp;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&nbsp;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>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_data_cloud_datastore.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">12.&nbsp;Spring Cloud Config&nbsp;</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">&nbsp;14.&nbsp;Spring Data Cloud Datastore</td></tr></table></div></body></html>

View File

@@ -0,0 +1,113 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>8.&nbsp;Spring Integration</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_jdbc.html" title="7.&nbsp;Spring JDBC"><link rel="next" href="multi__spring_cloud_stream.html" title="9.&nbsp;Spring Cloud Stream"></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">8.&nbsp;Spring Integration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_jdbc.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_stream.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_integration" href="#_spring_integration"></a>8.&nbsp;Spring Integration</h1></div></div></div><p>Spring Cloud GCP provides Spring Integration adapters that allow your applications to use Enterprise Integration Patterns backed up by Google Cloud Platform services.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_channel_adapters_for_cloud_pubsub" href="#_channel_adapters_for_cloud_pubsub"></a>8.1&nbsp;Channel Adapters for Cloud Pub/Sub</h2></div></div></div><p>The channel adapters for Google Cloud Pub/Sub connect your Spring <a class="link" href="https://docs.spring.io/spring-integration/reference/html/messaging-channels-section.html#channel" target="_top"><code class="literal">MessageChannels</code></a> to Google Cloud Pub/Sub topics and subscriptions.
This enables messaging between different processes, applications or micro-services backed up by Google Cloud Pub/Sub.</p><p>The Spring Integration Channel Adapters for Google Cloud Pub/Sub are included in the <code class="literal">spring-cloud-gcp-pubsub</code> module.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-pubsub<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.integration<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-integration-core<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-pubsub'
compile group: 'org.springframework.integration', name: 'spring-integration-core'
}</pre><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_inbound_channel_adapter" href="#_inbound_channel_adapter"></a>8.1.1&nbsp;Inbound channel adapter</h3></div></div></div><p><code class="literal">PubSubInboundChannelAdapter</code> is the inbound channel adapter for GCP Pub/Sub that listens to a GCP Pub/Sub subscription for new messages.
It converts new messages to an internal Spring <a class="link" href="https://docs.spring.io/spring-integration/reference/html/messaging-construction-chapter.html#message" target="_top"><code class="literal">Message</code></a> and then sends it to the bound output channel.</p><p>Google Pub/Sub treats message payloads as byte arrays.
So, by default, the inbound channel adapter will construct the Spring <code class="literal">Message</code> with <code class="literal">byte[]</code> as the payload.
However, you can change the desired payload type by setting the <code class="literal">payloadType</code> property of the <code class="literal">PubSubInboundChannelAdapter</code>.
The <code class="literal">PubSubInboundChannelAdapter</code> delegates the conversion to the desired payload type to the <code class="literal">PubSubMessageConverter</code> configured in the <code class="literal">PubSubTemplate</code>.</p><p>To use the inbound channel adapter, a <code class="literal">PubSubInboundChannelAdapter</code> must be provided and configured on the user application side.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageChannel pubsubInputChannel() {
<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> PublishSubscribeChannel();
}
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> PubSubInboundChannelAdapter messageChannelAdapter(
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Qualifier("pubsubInputChannel")</xslthl:annotation> MessageChannel inputChannel,
SubscriberFactory subscriberFactory) {
PubSubInboundChannelAdapter adapter =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> PubSubInboundChannelAdapter(subscriberFactory, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"subscriptionName"</span>);
adapter.setOutputChannel(inputChannel);
adapter.setAckMode(AckMode.MANUAL);
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> adapter;
}</pre><p>In the example, we first specify the <code class="literal">MessageChannel</code> where the adapter is going to write incoming messages to.
The <code class="literal">MessageChannel</code> implementation isn&#8217;t important here.
Depending on your use case, you might want to use a <code class="literal">MessageChannel</code> other than <code class="literal">PublishSubscribeChannel</code>.</p><p>Then, we declare a <code class="literal">PubSubInboundChannelAdapter</code> bean.
It requires the channel we just created and a <code class="literal">SubscriberFactory</code>, which creates <code class="literal">Subscriber</code> objects from the Google Cloud Java Client for Pub/Sub.
The Spring Boot starter for GCP Pub/Sub provides a configured <code class="literal">SubscriberFactory</code>.</p><p>The <code class="literal">PubSubInboundChannelAdapter</code> supports three acknowledgement modes, with <code class="literal">AckMode.AUTO</code> being the default value;</p><p>Automatic acking (<code class="literal">AckMode.AUTO</code>)</p><p>A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown.
If a <code class="literal">RuntimeException</code> is thrown while the message is processed, then the message is nacked.</p><p>Automatic acking OK (<code class="literal">AckMode.AUTO_ACK</code>)</p><p>A message is acked with GCP Pub/Sub if the adapter sent it to the channel and no exceptions were thrown.
If a <code class="literal">RuntimeException</code> is thrown while the message is processed, then the message is neither acked / nor nacked.</p><p>This is useful when using the subscription&#8217;s ack deadline timeout as a retry delivery backoff mechanism.</p><p>Manually acking (<code class="literal">AckMode.MANUAL</code>)</p><p>The adapter attaches a <code class="literal">BasicAcknowledgeablePubsubMessage</code> object to the <code class="literal">Message</code> headers.
Users can extract the <code class="literal">BasicAcknowledgeablePubsubMessage</code> using the <code class="literal">GcpPubSubHeaders.ORIGINAL_MESSAGE</code> key and use it to (n)ack a message.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@ServiceActivator(inputChannel = "pubsubInputChannel")</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageHandler messageReceiver() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> message -&gt; {
LOGGER.info(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Message arrived! Payload: "</span> + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> String((<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">byte</span>[]) message.getPayload()));
BasicAcknowledgeablePubsubMessage originalMessage =
message.getHeaders().get(GcpPubSubHeaders.ORIGINAL_MESSAGE, BasicAcknowledgeablePubsubMessage.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
originalMessage.ack();
};
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_outbound_channel_adapter" href="#_outbound_channel_adapter"></a>8.1.2&nbsp;Outbound channel adapter</h3></div></div></div><p><code class="literal">PubSubMessageHandler</code> is the outbound channel adapter for GCP Pub/Sub that listens for new messages on a Spring <code class="literal">MessageChannel</code>.
It uses <code class="literal">PubSubTemplate</code> to post them to a GCP Pub/Sub topic.</p><p>To construct a Pub/Sub representation of the message, the outbound channel adapter needs to convert the Spring <code class="literal">Message</code> payload to a byte array representation expected by Pub/Sub.
It delegates this conversion to the <code class="literal">PubSubTemplate</code>.
To customize the conversion, you can specify a <code class="literal">PubSubMessageConverter</code> in the <code class="literal">PubSubTemplate</code> that should convert the <code class="literal">Object</code> payload and headers of the Spring <code class="literal">Message</code> to a <code class="literal">PubsubMessage</code>.</p><p>To use the outbound channel adapter, a <code class="literal">PubSubMessageHandler</code> bean must be provided and configured on the user application side.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@ServiceActivator(inputChannel = "pubsubOutputChannel")</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
<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> PubSubMessageHandler(pubsubTemplate, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"topicName"</span>);
}</pre><p>The provided <code class="literal">PubSubTemplate</code> contains all the necessary configuration to publish messages to a GCP Pub/Sub topic.</p><p><code class="literal">PubSubMessageHandler</code> publishes messages asynchronously by default.
A publish timeout can be configured for synchronous publishing.
If none is provided, the adapter waits indefinitely for a response.</p><p>It is possible to set user-defined callbacks for the <code class="literal">publish()</code> call in <code class="literal">PubSubMessageHandler</code> through the <code class="literal">setPublishFutureCallback()</code> method.
These are useful to process the message ID, in case of success, or the error if any was thrown.</p><p>To override the default destination you can use the <code class="literal">GcpPubSubHeaders.DESTINATION</code> header.</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> MessageChannel pubsubOutputChannel;
<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> handleMessage(Message&lt;?&gt; msg) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> MessagingException {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Message&lt;?&gt; message = MessageBuilder
.withPayload(msg.getPayload())
.setHeader(GcpPubSubHeaders.TOPIC, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"customTopic"</span>).build();
pubsubOutputChannel.send(message);
}</pre><p>It is also possible to set an SpEL expression for the topic with the <code class="literal">setTopicExpression()</code> or <code class="literal">setTopicExpressionString()</code> methods.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_header_mapping" href="#_header_mapping"></a>8.1.3&nbsp;Header mapping</h3></div></div></div><p>These channel adapters contain header mappers that allow you to map, or filter out, headers from Spring to Google Cloud Pub/Sub messages, and vice-versa.
By default, the inbound channel adapter maps every header on the Google Cloud Pub/Sub messages to the Spring messages produced by the adapter.
The outbound channel adapter maps every header from Spring messages into Google Cloud Pub/Sub ones, except the ones added by Spring, like headers with key <code class="literal">"id"</code>, <code class="literal">"timestamp"</code> and <code class="literal">"gcp_pubsub_acknowledgement"</code>.
In the process, the outbound mapper also converts the value of the headers into string.</p><p>Each adapter declares a <code class="literal">setHeaderMapper()</code> method to let you further customize which headers you want to map from Spring to Google Cloud Pub/Sub, and vice-versa.</p><p>For example, to filter out headers <code class="literal">"foo"</code>, <code class="literal">"bar"</code> and all headers starting with the prefix "prefix_", you can use <code class="literal">setHeaderMapper()</code> along with the <code class="literal">PubSubHeaderMapper</code> implementation provided by this module.</p><pre class="programlisting">PubSubMessageHandler adapter = ...
...
PubSubHeaderMapper headerMapper = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> PubSubHeaderMapper();
headerMapper.setOutboundHeaderPatterns(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"!foo"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"!bar"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"!prefix_*"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"*"</span>);
adapter.setHeaderMapper(headerMapper);</pre><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The order in which the patterns are declared in <code class="literal">PubSubHeaderMapper.setOutboundHeaderPatterns()</code> and <code class="literal">PubSubHeaderMapper.setInboundHeaderPatterns()</code> matters.
The first patterns have precedence over the following ones.</p></td></tr></table></div><p>In the previous example, the <code class="literal">"*"</code> pattern means every header is mapped.
However, because it comes last in the list, <a class="link" href="https://docs.spring.io/spring-integration/api/org/springframework/integration/util/PatternMatchUtils.html#smartMatch-java.lang.String-java.lang.String%E2%80%A6%E2%80%8B-" target="_top">the previous patterns take precedence</a>.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_3" href="#_sample_3"></a>8.2&nbsp;Sample</h2></div></div></div><p>Available examples:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-sample" target="_top">sender and receiver sample application</a></li><li class="listitem"><a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-integration-pubsub-json-sample" target="_top">JSON payloads sample application</a></li><li class="listitem"><a class="link" href="https://codelabs.developers.google.com/codelabs/cloud-spring-cloud-gcp-pubsub-integration/index.html" target="_top">codelab</a></li></ul></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_channel_adapters_for_google_cloud_storage" href="#_channel_adapters_for_google_cloud_storage"></a>8.3&nbsp;Channel Adapters for Google Cloud Storage</h2></div></div></div><p>The channel adapters for Google Cloud Storage allow you to read and write files to Google Cloud Storage through <code class="literal">MessageChannels</code>.</p><p>Spring Cloud GCP provides two inbound adapters, <code class="literal">GcsInboundFileSynchronizingMessageSource</code> and <code class="literal">GcsStreamingMessageSource</code>, and one outbound adapter, <code class="literal">GcsMessageHandler</code>.</p><p>The Spring Integration Channel Adapters for Google Cloud Storage are included in the <code class="literal">spring-cloud-gcp-storage</code> module.</p><p>To use the Storage portion of Spring Integration for Spring Cloud GCP, you must also provide the <code class="literal">spring-integration-file</code> dependency, since it isn&#8217;t pulled transitively.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-storage<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.integration<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-integration-file<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-storage'
compile group: 'org.springframework.integration', name: 'spring-integration-file'
}</pre><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_inbound_channel_adapter_2" href="#_inbound_channel_adapter_2"></a>8.3.1&nbsp;Inbound channel adapter</h3></div></div></div><p>The Google Cloud Storage inbound channel adapter polls a Google Cloud Storage bucket for new files and sends each of them in a <code class="literal">Message</code> payload to the <code class="literal">MessageChannel</code> specified in the <code class="literal">@InboundChannelAdapter</code> annotation.
The files are temporarily stored in a folder in the local file system.</p><p>Here is an example of how to configure a Google Cloud Storage inbound channel adapter.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@InboundChannelAdapter(channel = "new-file-channel", poller = @Poller(fixedDelay = "5000"))</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageSource&lt;File&gt; synchronizerAdapter(Storage gcs) {
GcsInboundFileSynchronizer synchronizer = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsInboundFileSynchronizer(gcs);
synchronizer.setRemoteDirectory(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"your-gcs-bucket"</span>);
GcsInboundFileSynchronizingMessageSource synchAdapter =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsInboundFileSynchronizingMessageSource(synchronizer);
synchAdapter.setLocalDirectory(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> File(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"local-directory"</span>));
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> synchAdapter;
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_inbound_streaming_channel_adapter" href="#_inbound_streaming_channel_adapter"></a>8.3.2&nbsp;Inbound streaming channel adapter</h3></div></div></div><p>The inbound streaming channel adapter is similar to the normal inbound channel adapter, except it does not require files to be stored in the file system.</p><p>Here is an example of how to configure a Google Cloud Storage inbound streaming channel adapter.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@InboundChannelAdapter(channel = "streaming-channel", poller = @Poller(fixedDelay = "5000"))</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageSource&lt;InputStream&gt; streamingAdapter(Storage gcs) {
GcsStreamingMessageSource adapter =
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsStreamingMessageSource(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsRemoteFileTemplate(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsSessionFactory(gcs)));
adapter.setRemoteDirectory(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"your-gcs-bucket"</span>);
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> adapter;
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_outbound_channel_adapter_2" href="#_outbound_channel_adapter_2"></a>8.3.3&nbsp;Outbound channel adapter</h3></div></div></div><p>The outbound channel adapter allows files to be written to Google Cloud Storage.
When it receives a <code class="literal">Message</code> containing a payload of type <code class="literal">File</code>, it writes that file to the Google Cloud Storage bucket specified in the adapter.</p><p>Here is an example of how to configure a Google Cloud Storage outbound channel adapter.</p><pre class="programlisting"><xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@Bean</xslthl:annotation>
<xslthl:annotation xmlns:xslthl="http://xslthl.sourceforge.net/">@ServiceActivator(inputChannel = "writeFiles")</xslthl:annotation>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> MessageHandler outboundChannelAdapter(Storage gcs) {
GcsMessageHandler outboundChannelAdapter = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsMessageHandler(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> GcsSessionFactory(gcs));
outboundChannelAdapter.setRemoteDirectoryExpression(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> ValueExpression&lt;&gt;(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"your-gcs-bucket"</span>));
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> outboundChannelAdapter;
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_4" href="#_sample_4"></a>8.4&nbsp;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-integration-storage-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_jdbc.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_stream.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">7.&nbsp;Spring JDBC&nbsp;</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">&nbsp;9.&nbsp;Spring Cloud Stream</td></tr></table></div></body></html>

View File

@@ -0,0 +1,42 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>7.&nbsp;Spring JDBC</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_resources.html" title="6.&nbsp;Spring Resources"><link rel="next" href="multi__spring_integration.html" title="8.&nbsp;Spring Integration"></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">7.&nbsp;Spring JDBC</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_resources.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_integration.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_jdbc" href="#_spring_jdbc"></a>7.&nbsp;Spring JDBC</h1></div></div></div><p>Spring Cloud GCP adds integrations with
<a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html" target="_top">Spring JDBC</a> so you can run your MySQL or PostgreSQL databases in Google Cloud SQL using Spring JDBC, or other libraries that depend on it like Spring Data JPA.</p><p>The Cloud SQL support is provided by Spring Cloud GCP in the form of two Spring Boot starters, one for MySQL and another one for PostgreSQL.
The role of the starters is to read configuration from properties and assume default settings so that user experience connecting to MySQL and PostgreSQL is as simple as possible.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-sql-mysql<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-sql-postgresql<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-mysql'
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-sql-postgresql'
}</pre><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_prerequisites" href="#_prerequisites"></a>7.1&nbsp;Prerequisites</h2></div></div></div><p>In order to use the Spring Boot Starters for Google Cloud SQL, the Google Cloud SQL API must be enabled in your GCP project.</p><p>To do that, go to the <a class="link" href="https://console.cloud.google.com/apis/library" target="_top">API library page</a> of the Google Cloud Console, search for "Cloud SQL API", click the first result and enable the API.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>There are several similar "Cloud SQL" results.
You must access the "Google Cloud SQL API" one and enable the API from there.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_spring_boot_starter_for_google_cloud_sql" href="#_spring_boot_starter_for_google_cloud_sql"></a>7.2&nbsp;Spring Boot Starter for Google Cloud SQL</h2></div></div></div><p>The Spring Boot Starters for Google Cloud SQL provide an auto-configured <a class="link" href="https://docs.oracle.com/javase/7/docs/api/javax/sql/DataSource.html" target="_top"><code class="literal">DataSource</code></a> object.
Coupled with Spring JDBC, it provides a
<a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate" target="_top"><code class="literal">JdbcTemplate</code></a> object bean that allows for operations such as querying and modifying a database.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> List&lt;Map&lt;String, Object&gt;&gt; listUsers() {
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> jdbcTemplate.queryForList(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"SELECT * FROM user;"</span>);
}</pre><p>You can rely on
<a class="link" href="https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database" target="_top">Spring Boot data source auto-configuration</a> to configure a <code class="literal">DataSource</code> bean.
In other words, properties like the SQL username, <code class="literal">spring.datasource.username</code>, and password, <code class="literal">spring.datasource.password</code> can be used.
There is also some configuration specific to Google Cloud SQL:</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"></colgroup><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Property name</p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Description</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.sql.enabled</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Enables or disables Cloud SQL auto configuration</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.sql.database-name</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Name of the database to connect to.</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.sql.instance-connection-name</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>A string containing a Google Cloud SQL instance&#8217;s project ID, region and name, each separated by a colon.
For example, <code class="literal">my-project-id:my-region:my-instance-name</code>.</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top">&nbsp;</td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.sql.credentials.location</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>File system path to the Google OAuth2 credentials private key file.
Used to authenticate and authorize new connections to a Google Cloud SQL instance.</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Default credentials provided by the Spring GCP Boot starter</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.cloud.gcp.sql.credentials.encoded-key</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p>Base64-encoded contents of OAuth2 account private key in JSON format.
Used to authenticate and authorize new connections to a Google Cloud SQL instance.</p></td><td style="" align="left" valign="top"><p>Default credentials provided by the Spring GCP Boot starter</p></td></tr></tbody></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>If you provide your own <code class="literal">spring.datasource.url</code>, it will be ignored, unless you disable Cloud SQL auto configuration with <code class="literal">spring.cloud.gcp.sql.enabled=false</code>.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_datasource_creation_flow" href="#_datasource_creation_flow"></a>7.2.1&nbsp;<code class="literal">DataSource</code> creation flow</h3></div></div></div><p>Based on the previous properties, the Spring Boot starter for Google Cloud SQL creates a <code class="literal">CloudSqlJdbcInfoProvider</code> object which is used to obtain an instance&#8217;s JDBC URL and driver class name.
If you provide your own <code class="literal">CloudSqlJdbcInfoProvider</code> bean, it is used instead and the properties related to building the JDBC URL or driver class are ignored.</p><p>The <code class="literal">DataSourceProperties</code> object provided by Spring Boot Autoconfigure is mutated in order to use the JDBC URL and driver class names provided by <code class="literal">CloudSqlJdbcInfoProvider</code>, unless those values were provided in the properties.
It is in the <code class="literal">DataSourceProperties</code> mutation step that the credentials factory is registered in a system property to be <code class="literal">SqlCredentialFactory</code>.</p><p><code class="literal">DataSource</code> creation is delegated to
<a class="link" href="https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html" target="_top">Spring Boot</a>.
You can select the type of connection pool (e.g., Tomcat, HikariCP, etc.) by <a class="link" href="https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html#boot-features-connect-to-production-database" target="_top">adding their dependency to the classpath</a>.</p><p>Using the created <code class="literal">DataSource</code> in conjunction with Spring JDBC provides you with a fully configured and operational <code class="literal">JdbcTemplate</code> object that you can use to interact with your SQL database.
You can connect to your database with as little as a database and instance names.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_troubleshooting_tips" href="#_troubleshooting_tips"></a>7.2.2&nbsp;Troubleshooting tips</h3></div></div></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="connection-issues" href="#connection-issues"></a>Connection issues</h4></div></div></div><p>If you&#8217;re not able to connect to a database and see an endless loop of <code class="literal">Connecting to Cloud SQL instance [&#8230;&#8203;] on IP [&#8230;&#8203;]</code>, it&#8217;s likely that exceptions are being thrown and logged at a level lower than your logger&#8217;s level.
This may be the case with HikariCP, if your logger is set to INFO or higher level.</p><p>To see what&#8217;s going on in the background, you should add a <code class="literal">logback.xml</code> file to your application resources folder, that looks like this:</p><pre class="programlisting"><xslthl:directive xmlns:xslthl="http://xslthl.sourceforge.net/">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</xslthl:directive>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;configuration&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;include</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">resource</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"org/springframework/boot/logging/logback/base.xml"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">/&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;logger</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">name</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"com.zaxxer.hikari.pool"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">level</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"DEBUG"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">/&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/configuration&gt;</span></pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_errors_like_c_g_cloud_sql_core_sslsocketfactory_re_throwing_cached_exception_due_to_attempt_to_refresh_instance_information_too_soon_after_error" href="#_errors_like_c_g_cloud_sql_core_sslsocketfactory_re_throwing_cached_exception_due_to_attempt_to_refresh_instance_information_too_soon_after_error"></a>Errors like <code class="literal">c.g.cloud.sql.core.SslSocketFactory : Re-throwing cached exception due to attempt to refresh instance information too soon after error</code></h4></div></div></div><p>If you see a lot of errors like this in a loop and can&#8217;t connect to your database, this is usually a symptom that something isn&#8217;t right with the permissions of your credentials or the Google Cloud SQL API is not enabled.
Verify that the Google Cloud SQL API is enabled in the Cloud Console and that your service account has the <a class="link" href="https://cloud.google.com/sql/docs/mysql/project-access-control#roles" target="_top">necessary IAM roles</a>.</p><p>To find out what&#8217;s causing the issue, you can enable DEBUG logging level as mentioned <a class="link" href="multi__spring_jdbc.html#connection-issues" title="Connection issues">above</a>.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_postgresql_java_net_socketexception_already_connected_issue" href="#_postgresql_java_net_socketexception_already_connected_issue"></a>PostgreSQL: <code class="literal">java.net.SocketException: already connected</code> issue</h4></div></div></div><p>We found this exception to be common if your Maven project&#8217;s parent is <code class="literal">spring-boot</code> version <code class="literal">1.5.x</code>, or in any other circumstance that would cause the version of the <code class="literal">org.postgresql:postgresql</code> dependency to be an older one (e.g., <code class="literal">9.4.1212.jre7</code>).</p><p>To fix this, re-declare the dependency in its correct version.
For example, in Maven:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.postgresql<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>postgresql<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;version&gt;</span>42.1.1<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/version&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_samples" href="#_samples"></a>7.3&nbsp;Samples</h2></div></div></div><p>Available sample applications and codelabs:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-sql-mysql-sample" target="_top">Spring Cloud GCP MySQL</a></li><li class="listitem"><a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-sql-postgres-sample" target="_top">Spring Cloud GCP PostgreSQL</a></li><li class="listitem"><a class="link" href="https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-samples/spring-cloud-gcp-data-jpa-sample" target="_top">Spring Data JPA with Spring Cloud GCP SQL</a></li><li class="listitem">Codelab: <a class="link" href="https://codelabs.developers.google.com/codelabs/cloud-spring-petclinic-cloudsql/index.html" target="_top">Spring Pet Clinic using Cloud SQL</a></li></ul></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__spring_resources.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_integration.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">6.&nbsp;Spring Resources&nbsp;</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">&nbsp;8.&nbsp;Spring Integration</td></tr></table></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,60 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>11.&nbsp;Stackdriver Logging</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_sleuth.html" title="10.&nbsp;Spring Cloud Sleuth"><link rel="next" href="multi__spring_cloud_config.html" title="12.&nbsp;Spring Cloud Config"></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">11.&nbsp;Stackdriver Logging</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_sleuth.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_config.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_stackdriver_logging" href="#_stackdriver_logging"></a>11.&nbsp;Stackdriver Logging</h1></div></div></div><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter-logging<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-logging'
}</pre><p><a class="link" href="https://cloud.google.com/logging/" target="_top">Stackdriver Logging</a> is the managed logging service provided by Google Cloud Platform.</p><p>This module provides support for associating a web request trace ID with the corresponding log entries.
It does so by retrieving the <code class="literal">X-B3-TraceId</code> value from the <a class="link" href="https://logback.qos.ch/manual/mdc.html" target="_top">Mapped Diagnostic Context (MDC)</a>, which is set by Spring Cloud Sleuth.
If Spring Cloud Sleuth isn&#8217;t used, the configured <code class="literal">TraceIdExtractor</code> extracts the desired header value and sets it as the log entry&#8217;s trace ID.
This allows grouping of log messages by request, for example, in the <a class="link" href="https://console.cloud.google.com/logs/viewer" target="_top">Google Cloud Console Logs viewer</a>.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Due to the way logging is set up, the GCP project ID and credentials defined in <code class="literal">application.properties</code> are ignored.
Instead, you should set the <code class="literal">GOOGLE_CLOUD_PROJECT</code> and <code class="literal">GOOGLE_APPLICATION_CREDENTIALS</code> environment variables to the project ID and credentials private key location, respectively.
You can do this easily if you&#8217;re using the <a class="link" href="https://cloud.google.com/sdk" target="_top">Google Cloud SDK</a>, using the <code class="literal">gcloud config set project [YOUR_PROJECT_ID]</code> and <code class="literal">gcloud auth application-default login</code> commands, respectively.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_web_mvc_interceptor" href="#_web_mvc_interceptor"></a>11.1&nbsp;Web MVC Interceptor</h2></div></div></div><p>For use in Web MVC-based applications, <code class="literal">TraceIdLoggingWebMvcInterceptor</code> is provided that extracts the request trace ID from an HTTP request using a <code class="literal">TraceIdExtractor</code> and stores it in a thread-local, which can then be used in a logging appender to add the trace ID metadata to log messages.</p><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warning]" src="images/warning.png"></td><th align="left">Warning</th></tr><tr><td align="left" valign="top"><p>If Spring Cloud GCP Trace is enabled, the logging module disables itself and delegates log correlation to Spring Cloud Sleuth.</p></td></tr></table></div><p><code class="literal">LoggingWebMvcConfigurer</code> configuration class is also provided to help register the <code class="literal">TraceIdLoggingWebMvcInterceptor</code> in Spring MVC applications.</p><p>Applications hosted on the Google Cloud Platform include trace IDs under the <code class="literal">x-cloud-trace-context</code> header, which will be included in log entries.
However, if Sleuth is used the trace ID will be picked up from the MDC.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_logback_support" href="#_logback_support"></a>11.2&nbsp;Logback Support</h2></div></div></div><p>Currently, only Logback is supported and there are 2 possibilities to log to Stackdriver via this library with Logback: via direct API calls and through JSON-formatted console logs.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_log_via_api" href="#_log_via_api"></a>11.2.1&nbsp;Log via API</h3></div></div></div><p>A Stackdriver appender is available using <code class="literal">org/springframework/cloud/gcp/autoconfigure/logging/logback-appender.xml</code>.
This appender builds a Stackdriver Logging log entry from a JUL or Logback log entry, adds a trace ID to it and sends it to Stackdriver Logging.</p><p><code class="literal">STACKDRIVER_LOG_NAME</code> and <code class="literal">STACKDRIVER_LOG_FLUSH_LEVEL</code> environment variables can be used to customize the <code class="literal">STACKDRIVER</code> appender.</p><p>Your configuration may then look like this:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;configuration&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;include</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">resource</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"org/springframework/cloud/gcp/autoconfigure/logging/logback-appender.xml"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"> /&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;root</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">level</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"INFO"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;appender-ref</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">ref</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"STACKDRIVER"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"> /&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/root&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/configuration&gt;</span></pre><p>If you want to have more control over the log output, you can further configure the appender.
The following properties 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"></colgroup><thead><tr><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Property</th><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Default Value</th><th style="border-bottom: 1px solid ; " align="left" valign="top">Description</th></tr></thead><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">log</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">spring.log</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>The Stackdriver Log name.
This can also be set via the <code class="literal">STACKDRIVER_LOG_NAME</code> environmental variable.</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">flushLevel</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">WARN</code></p></td><td style="" align="left" valign="top"><p>If a log entry with this level is encountered, trigger a flush of locally buffered log to Stackdriver Logging.
This can also be set via the <code class="literal">STACKDRIVER_LOG_FLUSH_LEVEL</code> environmental variable.</p></td></tr></tbody></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_log_via_console" href="#_log_via_console"></a>11.2.2&nbsp;Log via Console</h3></div></div></div><p>For Logback, a <code class="literal">org/springframework/cloud/gcp/autoconfigure/logging/logback-json-appender.xml</code> file is made available for import to make it easier to configure the JSON Logback appender.</p><p>Your configuration may then look something like this:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;configuration&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;include</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">resource</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"org/springframework/cloud/gcp/autoconfigure/logging/logback-json-appender.xml"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"> /&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;root</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">level</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"INFO"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;appender-ref</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">ref</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"CONSOLE_JSON"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"> /&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/root&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/configuration&gt;</span></pre><p>If your application is running on Google Kubernetes Engine, Google Compute Engine or Google App Engine Flexible, your console logging is automatically saved to Google Stackdriver Logging.
Therefore, you can just include <code class="literal">org/springframework/cloud/gcp/autoconfigure/logging/logback-json-appender.xml</code> in your logging configuration, which logs JSON entries to the console.
The trace id will be set correctly.</p><p>If you want to have more control over the log output, you can further configure the appender.
The following properties 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"></colgroup><thead><tr><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Property</th><th style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top">Default Value</th><th style="border-bottom: 1px solid ; " align="left" valign="top">Description</th></tr></thead><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">projectId</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>If not set, default value is determined in the following order:</p>
<div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><code class="literal">SPRING_CLOUD_GCP_LOGGING_PROJECT_ID</code> Environmental Variable.</li><li class="listitem">Value of <code class="literal">DefaultGcpProjectIdProvider.getProjectId()</code></li></ol></div></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>This is used to generate fully qualified Stackdriver Trace ID format: <code class="literal">projects/[PROJECT-ID]/traces/[TRACE-ID]</code>.</p>
<p>This format is required to correlate trace between Stackdriver Trace and Stackdriver Logging.</p>
<p>If <code class="literal">projectId</code> is not set and cannot be determined, then it&#8217;ll log <code class="literal">traceId</code> without the fully qualified format.</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeTraceId</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the <code class="literal">traceId</code> be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeSpanId</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the <code class="literal">spanId</code> be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeLevel</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the severity be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeThreadName</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the thread name be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeMDC</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should all MDC properties be included.
The MDC properties <code class="literal">X-B3-TraceId</code>, <code class="literal">X-B3-SpanId</code> and <code class="literal">X-Span-Export</code> provided by Spring Sleuth will get excluded as they get handled separately</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeLoggerName</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the name of the logger be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeFormattedMessage</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the formatted log message be included.</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeExceptionInMessage</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the stacktrace be appended to the formatted log message.
This setting is only evaluated if <code class="literal">includeFormattedMessage</code> is <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">includeContextName</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">true</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the logging context be included</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">includeMessage</code></p></td><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p><code class="literal">false</code></p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Should the log message with blank placeholders be included</p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">includeException</code></p></td><td style="border-right: 1px solid ; " align="left" valign="top"><p><code class="literal">false</code></p></td><td style="" align="left" valign="top"><p>Should the stacktrace be included as a own field</p></td></tr></tbody></table></div><p>This is an example of such an Logback configuration:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;configuration &gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;property</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">name</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"projectId"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">value</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"${projectId:-${GOOGLE_CLOUD_PROJECT}}"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">/&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;appender</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">name</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"CONSOLE_JSON"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">class</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"ch.qos.logback.core.ConsoleAppender"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;encoder</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">class</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"ch.qos.logback.core.encoder.LayoutWrappingEncoder"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;layout</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">class</span>=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-value">"org.springframework.cloud.gcp.logging.StackdriverJsonLayout"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;projectId&gt;</span>${projectId}<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/projectId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeTraceId&gt;true&lt;/includeTraceId&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeSpanId&gt;true&lt;/includeSpanId&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeLevel&gt;true&lt;/includeLevel&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeThreadName&gt;true&lt;/includeThreadName&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeMDC&gt;true&lt;/includeMDC&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeLoggerName&gt;true&lt;/includeLoggerName&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeFormattedMessage&gt;true&lt;/includeFormattedMessage&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeExceptionInMessage&gt;true&lt;/includeExceptionInMessage&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeContextName&gt;true&lt;/includeContextName&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeMessage&gt;false&lt;/includeMessage&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">&lt;!--&lt;includeException&gt;false&lt;/includeException&gt;--&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/layout&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/encoder&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/appender&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/configuration&gt;</span></pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_sample_7" href="#_sample_7"></a>11.3&nbsp;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-logging-sample" target="_top">Sample Spring Boot Application</a> is provided to show how to use the Cloud logging starter.</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_sleuth.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__spring_cloud_config.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">10.&nbsp;Spring Cloud Sleuth&nbsp;</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">&nbsp;12.&nbsp;Spring Cloud Config</td></tr></table></div></body></html>

View File

@@ -0,0 +1,23 @@
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>4.&nbsp;Spring Cloud GCP Core</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__getting_started.html" title="3.&nbsp;Getting started"><link rel="next" href="multi__google_cloud_pubsub.html" title="5.&nbsp;Google Cloud Pub/Sub"></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">4.&nbsp;Spring Cloud GCP Core</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__getting_started.html">Prev</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="multi__google_cloud_pubsub.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="spring-cloud-gcp-core" href="#spring-cloud-gcp-core"></a>4.&nbsp;Spring Cloud GCP Core</h1></div></div></div><p>Each Spring Cloud GCP module uses <code class="literal">GcpProjectIdProvider</code> and <code class="literal">CredentialsProvider</code> to get the GCP project ID and access credentials.</p><p>Spring Cloud GCP provides a Spring Boot starter to auto-configure the core components.</p><p>Maven coordinates, using Spring Cloud GCP BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;dependency&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;groupId&gt;</span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/groupId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;artifactId&gt;</span>spring-cloud-gcp-starter<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/artifactId&gt;</span>
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag">&lt;/dependency&gt;</span></pre><p>Gradle coordinates:</p><pre class="screen">dependencies {
compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter'
}</pre><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_project_id" href="#_project_id"></a>4.1&nbsp;Project ID</h2></div></div></div><p><code class="literal">GcpProjectIdProvider</code> is a functional interface that returns a GCP project ID string.</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> GcpProjectIdProvider {
String getProjectId();
}</pre><p>The Spring Cloud GCP starter auto-configures a <code class="literal">GcpProjectIdProvider</code>.
If a <code class="literal">spring.cloud.gcp.project-id</code> property is specified, the provided <code class="literal">GcpProjectIdProvider</code> returns that property value.</p><pre class="programlisting">spring.cloud.gcp.project-id=my-gcp-project-id</pre><p>Otherwise, the project ID is discovered based on an
<a class="link" href="https://googlecloudplatform.github.io/google-cloud-java/google-cloud-clients/apidocs/com/google/cloud/ServiceOptions.html#getDefaultProjectId--" target="_top">ordered list of rules</a>:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">The project ID specified by the <code class="literal">GOOGLE_CLOUD_PROJECT</code> environment variable</li><li class="listitem">The Google App Engine project ID</li><li class="listitem">The project ID specified in the JSON credentials file pointed by the <code class="literal">GOOGLE_APPLICATION_CREDENTIALS</code> environment variable</li><li class="listitem">The Google Cloud SDK project ID</li><li class="listitem">The Google Compute Engine project ID, from the Google Compute Engine Metadata Server</li></ol></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_credentials" href="#_credentials"></a>4.2&nbsp;Credentials</h2></div></div></div><p><code class="literal">CredentialsProvider</code> is a functional interface that returns the credentials to authenticate and authorize calls to Google Cloud Client Libraries.</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> CredentialsProvider {
Credentials getCredentials() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> IOException;
}</pre><p>The Spring Cloud GCP starter auto-configures a <code class="literal">CredentialsProvider</code>.
It uses the <code class="literal">spring.cloud.gcp.credentials.location</code> property to locate the OAuth2 private key of a Google service account.
Keep in mind this property is a Spring Resource, so the credentials file can be obtained from a number of <a class="link" href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html#resources-implementations" target="_top">different locations</a> such as the file system, classpath, URL, etc.
The next example specifies the credentials location property in the file system.</p><pre class="screen">spring.cloud.gcp.credentials.location=file:/usr/local/key.json</pre><p>Alternatively, you can set the credentials by directly specifying the <code class="literal">spring.cloud.gcp.credentials.encoded-key</code> property.
The value should be the base64-encoded account private key in JSON format.</p><p>If that credentials aren&#8217;t specified through properties, the starter tries to discover credentials from a <a class="link" href="https://github.com/GoogleCloudPlatform/google-cloud-java#authentication" target="_top">number of places</a>:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Credentials file pointed to by the <code class="literal">GOOGLE_APPLICATION_CREDENTIALS</code> environment variable</li><li class="listitem">Credentials provided by the Google Cloud SDK <code class="literal">gcloud auth application-default login</code> command</li><li class="listitem">Google App Engine built-in credentials</li><li class="listitem">Google Cloud Shell built-in credentials</li><li class="listitem">Google Compute Engine built-in credentials</li></ol></div><p>If your app is running on Google App Engine or Google Compute Engine, in most cases, you should omit the <code class="literal">spring.cloud.gcp.credentials.location</code> property and, instead, let the Spring Cloud GCP Starter get the correct credentials for those environments.
On App Engine Standard, the <a class="link" href="https://cloud.google.com/appengine/docs/standard/java/appidentity/" target="_top">App Identity service account credentials</a> are used, on App Engine Flexible, the <a class="link" href="https://cloud.google.com/appengine/docs/flexible/java/service-account" target="_top">Flexible service account credential</a> are used and on Google Compute Engine, the <a class="link" href="https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#using_the_compute_engine_default_service_account" target="_top">Compute Engine Default Service Account</a> is used.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_scopes" href="#_scopes"></a>4.2.1&nbsp;Scopes</h3></div></div></div><p>By default, the credentials provided by the Spring Cloud GCP Starter contain scopes for every service supported by Spring Cloud GCP.</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"></colgroup><tbody><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Service</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p>Scope</p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Spanner</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/spanner.admin" target="_top">https://www.googleapis.com/auth/spanner.admin</a>, <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>Datastore</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/datastore" target="_top">https://www.googleapis.com/auth/datastore</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Pub/Sub</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/pubsub" target="_top">https://www.googleapis.com/auth/pubsub</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Storage (Read Only)</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/devstorage.read_only" target="_top">https://www.googleapis.com/auth/devstorage.read_only</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Storage (Write/Write)</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/devstorage.read_write" target="_top">https://www.googleapis.com/auth/devstorage.read_write</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Runtime Config</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/cloudruntimeconfig" target="_top">https://www.googleapis.com/auth/cloudruntimeconfig</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Trace (Append)</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/trace.append" target="_top">https://www.googleapis.com/auth/trace.append</a></p></td></tr><tr><td style="border-right: 1px solid ; border-bottom: 1px solid ; " align="left" valign="top"><p>Cloud Platform</p></td><td style="border-bottom: 1px solid ; " align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/cloud-platform" target="_top">https://www.googleapis.com/auth/cloud-platform</a></p></td></tr><tr><td style="border-right: 1px solid ; " align="left" valign="top"><p>Vision</p></td><td style="" align="left" valign="top"><p><a class="link" href="https://www.googleapis.com/auth/cloud-vision" target="_top">https://www.googleapis.com/auth/cloud-vision</a></p></td></tr></tbody></table></div><p>The Spring Cloud GCP starter allows you to configure a custom scope list for the provided credentials.
To do that, specify a comma-delimited list of <a class="link" href="https://developers.google.com/identity/protocols/googlescopes" target="_top">Google OAuth2 scopes</a> in the <code class="literal">spring.cloud.gcp.credentials.scopes</code> property.</p><p><code class="literal">spring.cloud.gcp.credentials.scopes</code> is a comma-delimited list of <a class="link" href="https://developers.google.com/identity/protocols/googlescopes" target="_top">Google OAuth2 scopes</a> for Google Cloud Platform services that the credentials returned by the provided <code class="literal">CredentialsProvider</code> support.</p><pre class="screen">spring.cloud.gcp.credentials.scopes=https://www.googleapis.com/auth/pubsub,https://www.googleapis.com/auth/sqlservice.admin</pre><p>You can also use <code class="literal">DEFAULT_SCOPES</code> placeholder as a scope to represent the starters default scopes, and append the additional scopes you need to add.</p><pre class="screen">spring.cloud.gcp.credentials.scopes=DEFAULT_SCOPES,https://www.googleapis.com/auth/cloud-vision</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_environment" href="#_environment"></a>4.3&nbsp;Environment</h2></div></div></div><p><code class="literal">GcpEnvironmentProvider</code> is a functional interface, auto-configured by the Spring Cloud GCP starter, that returns a <code class="literal">GcpEnvironment</code> enum.
The provider can help determine programmatically in which GCP environment (App Engine Flexible, App Engine Standard, Kubernetes Engine or Compute Engine) the application is deployed.</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> GcpEnvironmentProvider {
GcpEnvironment getCurrentEnvironment();
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_spring_initializr_2" href="#_spring_initializr_2"></a>4.4&nbsp;Spring Initializr</h2></div></div></div><p>This starter is available from <a class="link" href="https://start.spring.io/" target="_top">Spring Initializr</a> through the <code class="literal">GCP Support</code> entry.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__getting_started.html">Prev</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="multi__google_cloud_pubsub.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">3.&nbsp;Getting started&nbsp;</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">&nbsp;5.&nbsp;Google Cloud Pub/Sub</td></tr></table></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
/*
code highlight CSS resemblign the Eclipse IDE default color schema
@author Costin Leau
*/
.hl-keyword {
color: #7F0055;
font-weight: bold;
}
.hl-comment {
color: #3F5F5F;
font-style: italic;
}
.hl-multiline-comment {
color: #3F5FBF;
font-style: italic;
}
.hl-tag {
color: #3F7F7F;
}
.hl-attribute {
color: #7F007F;
}
.hl-value {
color: #2A00FF;
}
.hl-string {
color: #2A00FF;
}

View File

@@ -0,0 +1,9 @@
@IMPORT url("manual.css");
body.firstpage {
background: url("../images/background.png") no-repeat center top;
}
div.part h1 {
border-top: none;
}

View File

@@ -0,0 +1,6 @@
@IMPORT url("manual.css");
body {
background: url("../images/background.png") no-repeat center top;
}

View File

@@ -0,0 +1,342 @@
@IMPORT url("highlight.css");
html {
padding: 0pt;
margin: 0pt;
}
body {
color: #333333;
margin: 15px 30px;
font-family: Helvetica, Arial, Freesans, Clean, Sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
code {
font-size: 16px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
:not(a) > code {
color: #6D180B;
}
:not(pre) > code {
background-color: #F2F2F2;
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 1px 3px 0;
text-shadow: none;
white-space: nowrap;
}
body > *:first-child {
margin-top: 0 !important;
}
div {
margin: 0pt;
}
hr {
border: 1px solid #CCCCCC;
background: #CCCCCC;
}
h1, h2, h3, h4, h5, h6 {
color: #000000;
cursor: text;
font-weight: bold;
margin: 30px 0 10px;
padding: 0;
}
h1, h2, h3 {
margin: 40px 0 10px;
}
h1 {
margin: 70px 0 30px;
padding-top: 20px;
}
div.part h1 {
border-top: 1px dotted #CCCCCC;
}
h1, h1 code {
font-size: 32px;
}
h2, h2 code {
font-size: 24px;
}
h3, h3 code {
font-size: 20px;
}
h4, h1 code, h5, h5 code, h6, h6 code {
font-size: 18px;
}
div.book, div.chapter, div.appendix, div.part, div.preface {
min-width: 300px;
max-width: 1200px;
margin: 0 auto;
}
p.releaseinfo {
font-weight: bold;
margin-bottom: 40px;
margin-top: 40px;
}
div.authorgroup {
line-height: 1;
}
p.copyright {
line-height: 1;
margin-bottom: -5px;
}
.legalnotice p {
font-style: italic;
font-size: 14px;
line-height: 1;
}
div.titlepage + p, div.titlepage + p {
margin-top: 0;
}
pre {
line-height: 1.0;
color: black;
}
a {
color: #4183C4;
text-decoration: none;
}
p {
margin: 15px 0;
text-align: left;
}
ul, ol {
padding-left: 30px;
}
li p {
margin: 0;
}
div.table {
margin: 1em;
padding: 0.5em;
text-align: center;
}
div.table table, div.informaltable table {
display: table;
width: 100%;
}
div.table td {
padding-left: 7px;
padding-right: 7px;
}
.sidebar {
line-height: 1.4;
padding: 0 20px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
}
.sidebar p.title {
color: #6D180B;
}
pre.programlisting, pre.screen {
font-size: 15px;
padding: 6px 10px;
background-color: #F8F8F8;
border: 1px solid #CCCCCC;
border-radius: 3px 3px 3px 3px;
clear: both;
overflow: auto;
line-height: 1.4;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #DDDDDD !important;
border-radius: 4px !important;
border-collapse: separate !important;
line-height: 1.6;
}
table thead {
background: #F5F5F5;
}
table tr {
border: none;
border-bottom: none;
}
table th {
font-weight: bold;
}
table th, table td {
border: none !important;
padding: 6px 13px;
}
table tr:nth-child(2n) {
background-color: #F8F8F8;
}
td p {
margin: 0 0 15px 0;
}
div.table-contents td p {
margin: 0;
}
div.important *, div.note *, div.tip *, div.warning *, div.navheader *, div.navfooter *, div.calloutlist * {
border: none !important;
background: none !important;
margin: 0;
}
div.important p, div.note p, div.tip p, div.warning p {
color: #6F6F6F;
line-height: 1.6;
}
div.important code, div.note code, div.tip code, div.warning code {
background-color: #F2F2F2 !important;
border: 1px solid #CCCCCC !important;
border-radius: 4px !important;
padding: 1px 3px 0 !important;
text-shadow: none !important;
white-space: nowrap !important;
}
.note th, .tip th, .warning th {
display: none;
}
.note tr:first-child td, .tip tr:first-child td, .warning tr:first-child td {
border-right: 1px solid #CCCCCC !important;
padding-top: 10px;
}
div.calloutlist p, div.calloutlist td {
padding: 0;
margin: 0;
}
div.calloutlist > table > tbody > tr > td:first-child {
padding-left: 10px;
width: 30px !important;
}
div.important, div.note, div.tip, div.warning {
margin-left: 0px !important;
margin-right: 20px !important;
margin-top: 20px;
margin-bottom: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
div.toc {
line-height: 1.2;
}
dl, dt {
margin-top: 1px;
margin-bottom: 0;
}
div.toc > dl > dt {
font-size: 32px;
font-weight: bold;
margin: 30px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dt {
font-size: 24px;
font-weight: bold;
margin: 20px 0 10px 0;
display: block;
}
div.toc > dl > dd > dl > dd > dl > dt {
font-weight: bold;
font-size: 20px;
margin: 10px 0 0 0;
}
tbody.footnotes * {
border: none !important;
}
div.footnote p {
margin: 0;
line-height: 1;
}
div.footnote p sup {
margin-right: 6px;
vertical-align: middle;
}
div.navheader {
border-bottom: 1px solid #CCCCCC;
}
div.navfooter {
border-top: 1px solid #CCCCCC;
}
.title {
margin-left: -1em;
padding-left: 1em;
}
.title > a {
position: absolute;
visibility: hidden;
display: block;
font-size: 0.85em;
margin-top: 0.05em;
margin-left: -1em;
vertical-align: text-top;
color: black;
}
.title > a:before {
content: "\00A7";
}
.title:hover > a, .title > a:hover, .title:hover > a:hover {
visibility: visible;
}
.title:focus > a, .title > a:focus, .title:focus > a:focus {
outline: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.7.1">
<title>spring-cloud-gcp</title>
<link rel="stylesheet" href="css/manual-singlepage.css">
<style>
.hidden {
display: none;
}
.switch {
border-width: 1px 1px 0 1px;
border-style: solid;
border-color: #7a2518;
display: inline-block;
}
.switch--item {
padding: 10px;
background-color: #ffffff;
color: #7a2518;
display: inline-block;
cursor: pointer;
}
.switch--item.selected {
background-color: #7a2519;
color: #ffffff;
}
</style>
<script src="http://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js"></script>
<script type="text/javascript">
function addBlockSwitches() {
$('.primary').each(function() {
primary = $(this);
createSwitchItem(primary, createBlockSwitch(primary)).item.addClass("selected");
primary.children('.title').remove();
});
$('.secondary').each(function(idx, node) {
secondary = $(node);
primary = findPrimary(secondary);
switchItem = createSwitchItem(secondary, primary.children('.switch'));
switchItem.content.addClass('hidden');
findPrimary(secondary).append(switchItem.content);
secondary.remove();
});
}
function createBlockSwitch(primary) {
blockSwitch = $('<div class="switch"></div>');
primary.prepend(blockSwitch);
return blockSwitch;
}
function findPrimary(secondary) {
candidate = secondary.prev();
while (!candidate.is('.primary')) {
candidate = candidate.prev();
}
return candidate;
}
function createSwitchItem(block, blockSwitch) {
blockName = block.children('.title').text();
content = block.children('.content').first().append(block.next('.colist'));
item = $('<div class="switch--item">' + blockName + '</div>');
item.on('click', '', content, function(e) {
$(this).addClass('selected');
$(this).siblings().removeClass('selected');
e.data.siblings('.content').addClass('hidden');
e.data.removeClass('hidden');
});
blockSwitch.append(item);
return {'item': item, 'content': content};
}
$(addBlockSwitches);
</script>
</head>
<body class="article">
<div id="header">
<h1>spring-cloud-gcp</h1>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>1.1.5.RELEASE</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_pick_the_documentation_option">Pick The Documentation Option</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p><a href="single/spring-cloud-gcp.html">Single HTML</a></p>
</li>
<li>
<p><a href="multi/multi_spring-cloud-gcp.html">Multi HTML</a></p>
</li>
</ul>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js"></script>
<script>prettyPrint()</script>
</body>
</html>

File diff suppressed because it is too large Load Diff