This commit is contained in:
j 2024-06-10 21:20:03 +08:00
commit b8618281d7
6973 changed files with 188454 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.project
.classpath
.springBeans
.settings/
target/
#IntelliJ Stuff
.idea
*.iml
#Geode Stuff
*.log
*.dat

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

40
.travis.yml Normal file
View File

@ -0,0 +1,40 @@
language: java
os: linux
jobs:
include:
- env: JDK='OpenJDK 16'
before_install: wget https://github.com/sormuras/bach/raw/master/install-jdk.sh && . ./install-jdk.sh -f 16
addons:
apt:
sources:
- sourceline: 'deb http://packages.couchbase.com/releases/couchbase-server/community/deb/ xenial xenial/main'
key_url: 'http://packages.couchbase.com/ubuntu/couchbase.key'
packages:
- couchbase-server-community
services:
- redis
- docker
cache:
directories:
- $HOME/.m2
- download
# See https://issues.couchbase.com/browse/MB-26556
install:
- curl -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=2000 -d indexMemoryQuota=256 -d ftsMemoryQuota=256
- curl -X POST http://127.0.0.1:8091/node/controller/setupServices -d "services=kv,index,n1ql,fts"
- curl -X POST http://127.0.0.1:8091/settings/indexes -d "storageMode=forestdb"
- curl -X POST http://127.0.0.1:8091/settings/web -d "username=Administrator&password=password&port=8091&"
- /opt/couchbase/bin/cbdocloader -c 127.0.0.1:8091 -u Administrator -p password -b travel-sample -m 256 -d /opt/couchbase/samples/travel-sample.zip
- /opt/couchbase/bin/couchbase-cli user-manage -c 127.0.0.1:8091 -u Administrator -p password --set --rbac-username=travel-sample --rbac-password=password --roles=admin --auth-domain local
script:
- cp -f settings.xml $HOME/.m2/settings.xml
- mvn -version
- java -version
- mvn clean test -U -Dsort -Dmaven.test.redirectTestOutputToFile=true -B -s settings.xml

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

141
README.adoc Normal file
View File

@ -0,0 +1,141 @@
= Spring Data Examples
image:https://travis-ci.org/spring-projects/spring-data-examples.svg?branch=main[Build Status,link=https://travis-ci.org/spring-projects/spring-data-examples]
This repository contains example projects for the different Spring Data modules to showcase the API and how to use the features provided by the modules.
We have separate folders for the samples of individual modules:
== Spring Data for Apache Cassandra
* `example` - Shows core Spring Data support for Apache Cassandra.
* `kotlin` - Example for using Cassandra with Kotlin.
* `reactive` - Example project to show reactive template and repository support.
== Spring Data Elasticsearch
* `example` - Example how to use basic text search, geo-spatial search and facets. It uses
the High Level REST Client backing template and repository.
* `reactive` - Example how to use reactive client, template and repository features.
Local Elasticsearch instance must be running to run the tests.
== Spring Data for Apache Geode
* `events` - In this example the test will make use of event handlers and async event
queue to handle events.
* `expiration-eviction` - In these examples the server is configured to delete entries
after a certain idle period or after a Time-To-Live period (expiration0 or remove data
from memory when certain thresholds are reached (eviction).
* `function-invocation` - In this example the server will have 3 functions registered. The
client will invoke each of the functions.
* `queries` - In this example a client will query the data in various ways using OQl,
continuous queries, and Apache Lucene indexes.
* `security` - In this example the servers and clients are set up with security (
username/password) authentication using Geode Security and Apache Shiro.
* `storage` - In this example the server is configured to store data off of hte JVM heap
using the `@EnableOffHeap` annotation and to compress region data using
SnappyCompressor`.
* `transactions` - In this example the client will perform operations within a
transaction. First, it will do a successful transaction where entries are saved to the
server, and then a failed transaction where all changes are reverted.
* `wan` - In these example two servers are deployed. One server populates itself with data, and the other server gets populated with that data via WAN replication.
== Spring Data JDBC
* `basic` - Basic usage of Spring Data JDBC.
* `immutables` - Showing Spring Data JDBC usage
with https://immutables.github.io/[Immutables]
== Spring Data JPA
* `eclipselink` - Sample project to show how to use Spring Data JPA with Spring Boot and https://www.eclipse.org/eclipselink/[Eclipselink].
* `example` - Probably the project you want to have a look at first. Contains a variety of sample packages, showcasing the different levels at which you can use Spring Data JPA. Have a look at the `simple` package for the most basic setup.
* `interceptors` - Example of how to enrich the repositories with AOP.
* `jpa21` - Shows support for JPA 2.1 specific features (stored procedures support).
* `multiple-datasources` - Examples of how to use Spring Data JPA with multiple `DataSource`s.
* `query-by-example` - Example project showing usage of Query by Example with Spring Data JPA.
* `security` - Example of how to integrate Spring Data JPA Repositories with Spring Security.
* `showcase` - Refactoring show case of how to improve a plain-JPA-based persistence layer by using Spring Data JPA (read: removing close to all of the implementation code). Follow the `demo.txt` file for detailed instructions.
* `vavr` - Shows the support of https://www.vavr.io[Vavr] collection types as return types for query methods.
== Spring Data LDAP
* `example` - Sample for Spring Data repositories to access an LDAP store.
== Spring Data MongoDB
* `aggregation` - Example project to showcase the MongoDB aggregation framework support.
* `example` - Example project for general repository functionality (including geo-spatial functionality), Querydsl integration and advanced topics.
* `fluent-api` - Example project to show the new fluent API (`MongoTemplate`-alternative) to interact with MongoDB.
* `geo-json` - Example project showing usage of http://geojson.org[GeoJSON] with MongoDB.
* `gridfs` - Example project showing usage of gridFS with MongoDB.
* `jmolecules` - Example of Spring Data MongoDB working with a jMolecules based domain model.
* `kotlin` - Example for using https://kotlinlang.org/[Kotlin] with MongoDB.
* `linking` - Example demonstrating possibilities for linking documents.
* `query-by-example` - Example project showing usage of Query by Example with MongoDB.
* `querydsl` - Example project showing imperative and reactive https://github.com/querydsl/querydsl[Querydsl] support for MongoDB.
* `reactive` - Example project to show reactive template and repository support.
* `repository-metrics` - Example project to show how to collect repository method invocation metrics.
* `security` - Example project showing usage of Spring Security with MongoDB.
* `text-search` - Example project showing usage of MongoDB text search feature.
* `transactions` - Example project for imperative and reactive MongoDB 4.0 transaction support.
== Spring Data Neo4j
* `example` - Example to show basic node and relationship entities and repository usage.
== Spring Data R2DBC
* `example` - Basic usage of Spring Data R2DBC.
== Spring Data Redis
* `cluster` - Example for Redis Cluster support.
* `example` - Example for basic Spring Data Redis setup.
* `reactive` - Example project to show reactive template support.
* `repositories` - Example demonstrating Spring Data repository abstraction on top of Redis.
* `sentinel` - Example for Redis Sentinel support.
* `streams` - Example for https://redis.io/topics/streams-intro[Redis Streams] support.
Local Redis instances must be running to run the tests. One option is to use Docker in a separate terminal:
```
$ docker run -p 6379:6379 redis:5.0
```
WARNING: If you're done using it, don't forget to shut it down!
== Spring Data REST
* `headers` - A sample showing the population of HTTP headers and the usage of them to perform conditional `GET` requests.
* `multi-store` - A sample REST web-service based on both Spring Data JPA and Spring Data MongoDB.
* `projections` - A sample REST web-service showing how to use projections.
* `security` - A sample REST web-service secured using Spring Security.
* `starbucks` - A sample REST web-service built with Spring Data REST and MongoDB.
* `uri-customizations` - Example project to show URI customization capabilities.
== Spring Data web support
* `projections` - Example for Spring Data web support for JSONPath and XPath expressions on projection interfaces.
* `querydsl` - Example for Spring Data Querydsl web integration (creating a `Predicate` from web requests).
* `web` - Example for Spring Data web integration (binding `Pageable` instances to Spring MVC controller methods, using interfaces to bind Spring MVC request payloads).
== Miscellaneous
* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot
scenarios.
* `map` - Example project to show how to use `Map`-backed repositories.
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in
one project.
== Note
* The example projects make use of the https://projectlombok.org/[Lombok] plugin. To get
proper code navigation in your IDE, you must install it separately. Lombok is available
in the IntelliJ plugins repository and as
a https://projectlombok.org/download[download] for Eclipse-based IDEs.
* The code makes use of Java 16 language features therefore you need Java 16 or newer to
run and compile the examples.
* Most store modules examples start their database via Testcontainers or as
embedded/in-memory server unless stated otherwise.

3
bom/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar

36
bom/README.adoc Normal file
View File

@ -0,0 +1,36 @@
= Spring Data - Release Train BOM example
This project shows the usage of the Spring Data BOM in a non-Spring-Boot project with both Maven and Gradle.
== Properties
In both Maven and Gradle a couple of properties are used to define the versions of Spring Framework and Spring Data to use. For Spring Framework a plain version is used. For Spring Data we refer to the https://spring.io/blog/2020/04/30/updates-to-spring-versions[calver revision] of the BOM. The naming of Spring Data releases uses the following conventions:
** `${calver-version}-M1` -> Milestones
** …
** `${calver-version}-RC1` -> Release candidate
** …
** `${calver-version}` -> GA version
** `${calver-version}` -> Services release (bugfixes) for that release train
== Maven
The `<dependencyManagement />` section declares dependencies to the BOMs for both Spring and Spring Data, using the `import` scope and `pom` type.
The standard `<dependencies />` section can now list Spring Framework and Spring Data dependencies without declaring a version and still be sure all libraries are in matching versions.
Note, that we don't declare a Spring Framework dependency here. The import of the Spring Framework BOM nonetheless makes sure we control the version of all transitive Spring Framework dependencies pulled in by the Spring Data modules.
== Gradle
Gradle does not support Maven BOMs (Bill of Materials) out of the box, so the first thing to do is important the
https://github.com/spring-gradle-plugins/dependency-management-plugin[dependency management plugin]. This example is based on Java,
but if you need a different language plugin (e.g. Kotlin), you can do so.
The `dependencyManagement` section can be used to import the Spring Framework BOM and Spring Data BOM.
The standard `dependencies` section can now list Spring and Spring Data dependencies without declaring a version and still
be sure all libraries are align with each other.
Note how you don't declare a Spring Framework dependency. Nevertheless, the dependency management plugin and the Spring Framework BOM
ensures you control the version of all transitive Spring Framework dependencies pulled in by Spring Data.

25
bom/build.gradle Normal file
View File

@ -0,0 +1,25 @@
plugins {
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
id 'java'
}
repositories {
mavenCentral()
}
ext {
springVersion = '5.3.9'
springDataVersion = '2021.0.4'
}
dependencyManagement {
imports {
mavenBom "org.springframework:spring-framework-bom:${springVersion}"
mavenBom "org.springframework.data:spring-data-bom:${springDataVersion}"
}
}
dependencies {
compile 'org.springframework.data:spring-data-rest-webmvc'
compile 'org.springframework.data:spring-data-jpa'
}

BIN
bom/gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
bom/gradlew vendored Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
bom/gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

53
bom/pom.xml Normal file
View File

@ -0,0 +1,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-examples-bom</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<name>Spring Data - Using the BOM for dependency management</name>
<properties>
<spring.version>5.3.9</spring.version>
<spring-data.version>2021.0.4</spring-data.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId>
<version>${spring-data.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
</dependencies>
</project>

5
bom/settings.gradle Normal file
View File

@ -0,0 +1,5 @@
pluginManagement {
repositories {
gradlePluginPortal()
}
}

33
cassandra/example/pom.xml Normal file
View File

@ -0,0 +1,33 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-cassandra-example</artifactId>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Spring Data Cassandra - Example</name>
<description>Basic sample project showing the usage of Spring Data Cassandra.</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,56 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import lombok.Data;
import java.time.Instant;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.cassandra.core.mapping.Table;
import org.springframework.data.domain.Persistable;
/**
* Simple domain object that declares properties annotated with Spring Data's annotations for auditing.
*
* @author Mark Paluch
*/
@Data
@Table
public class AuditedPerson implements Persistable<Long> {
@Id Long id;
@CreatedBy String createdBy;
@LastModifiedBy String lastModifiedBy;
@CreatedDate Instant createdDate;
@LastModifiedDate Instant lastModifiedDate;
@Transient boolean isNew;
@Override
public boolean isNew() {
return isNew;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import org.springframework.data.repository.CrudRepository;
/**
* Simple repository interface for {@link AuditedPerson} instances.
*
* @author Mark Paluch
*/
public interface AuditedPersonRepository extends CrudRepository<AuditedPerson, Long> {}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import java.util.Optional;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.EnableCassandraAuditing;
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
import org.springframework.data.domain.AuditorAware;
import com.datastax.oss.driver.api.core.CqlSession;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link AuditedPerson} table.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = AuditedPerson.class)
@EnableCassandraAuditing
class BasicConfiguration {
/**
* {@code @Bean} method defining a supplier for an auditor. This could be also an integration with a security
* framework such as Spring Security.
*/
@Bean
AuditorAware<String> auditorAware() {
return () -> Optional.of("Some user");
}
/**
* {@code @Bean} method defining a {@link MappingCassandraConverter} as currently the auditing requires a bean
* definition for {@link MappingCassandraConverter}.
*/
@Bean
public MappingCassandraConverter cassandraConverter(CassandraMappingContext mapping,
CassandraCustomConversions conversions, CqlSession session) {
var converter = new MappingCassandraConverter(mapping);
converter.setCodecRegistry(session.getContext().getCodecRegistry());
converter.setCustomConversions(conversions);
converter.setUserTypeResolver(new SimpleUserTypeResolver(session));
return converter;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.basic;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link User} table.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = User.class)
class BasicConfiguration {
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.basic;
import java.util.List;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.CrudRepository;
/**
* Simple repository interface for {@link User} instances. The interface is used to declare so called query methods,
* methods to retrieve single entities or collections of them.
*
* @author Thomas Darimont
*/
public interface BasicUserRepository extends CrudRepository<User, Long> {
/**
* Sample method annotated with {@link Query}. This method executes the CQL from the {@link Query} value.
*
* @param id
* @return
*/
@Query("SELECT * from users where user_id in(?0)")
User findUserByIdIn(long id);
/**
* Derived query method. This query corresponds with {@code SELECT * FROM users WHERE uname = ?0}.
* {@link User#username} is not part of the primary so it requires a secondary index.
*
* @param username
* @return
*/
User findUserByUsername(String username);
/**
* Derived query method using SASI (SSTable Attached Secondary Index) features through the {@code LIKE} keyword. This
* query corresponds with {@code SELECT * FROM users WHERE lname LIKE '?0'}. {@link User#lastname} is not part of the
* primary key so it requires a secondary index.
*
* @param lastnamePrefix
* @return
*/
List<User> findUsersByLastnameStartsWith(String lastnamePrefix);
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.basic;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* Sample user class.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@Data
@NoArgsConstructor
@Table(value = "users")
public class User {
@PrimaryKey("user_id") private Long id;
@Column("uname") private String username;
@Column("fname") private String firstname;
@Column("lname") private String lastname;
public User(Long id) {
this.setId(id);
}
}

View File

@ -0,0 +1,5 @@
/**
* Package showing a simple repository interface to use basic query method execution functionality.
*/
package example.springdata.cassandra.basic;

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
import org.springframework.data.cassandra.core.mapping.Element;
import org.springframework.data.cassandra.core.mapping.Tuple;
/**
* Simple mapped tuple.
*
* @author Mark Paluch
*/
@Tuple
public record Address(@Element(0) String address, @Element(1) String city, @Element(2) String zip) {
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
import lombok.Data;
import java.util.Currency;
import java.util.List;
import java.util.Map;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* Sample Addressbook class.
*
* @author Mark Paluch
*/
@Data
@Table
public class Addressbook {
@Id String id;
Contact me;
List<Contact> friends;
Address address;
Map<Integer, Currency> preferredCurrencies;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
/**
* Sample Contact class.
*
* @author Mark Paluch
*/
public record Contact(String firstname, String lastname) {
}

View File

@ -0,0 +1,118 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;
import org.springframework.util.StringUtils;
import com.datastax.oss.driver.api.core.cql.Row;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link Configuration} class to register custom converters.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = Addressbook.class)
class ConverterConfiguration {
@Bean
public CassandraCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new PersonWriteConverter());
converters.add(new PersonReadConverter());
converters.add(new CustomAddressbookReadConverter());
converters.add(CurrencyToStringConverter.INSTANCE);
converters.add(StringToCurrencyConverter.INSTANCE);
return new CassandraCustomConversions(converters);
}
/**
* Write a {@link Contact} into its {@link String} representation.
*/
static class PersonWriteConverter implements Converter<Contact, String> {
public String convert(Contact source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* Read a {@link Contact} from its {@link String} representation.
*/
static class PersonReadConverter implements Converter<String, Contact> {
public Contact convert(String source) {
if (StringUtils.hasText(source)) {
try {
return new ObjectMapper().readValue(source, Contact.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
}
/**
* Perform custom mapping by reading a {@link Row} into a custom class.
*/
static class CustomAddressbookReadConverter implements Converter<Row, CustomAddressbook> {
public CustomAddressbook convert(Row source) {
return new CustomAddressbook(source.getString("id"), source.getString("me"));
}
}
enum StringToCurrencyConverter implements Converter<String, Currency> {
INSTANCE;
@Override
public Currency convert(String source) {
return Currency.getInstance(source);
}
}
enum CurrencyToStringConverter implements Converter<Currency, String> {
INSTANCE;
@Override
public String convert(Currency source) {
return source.getCurrencyCode();
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
/**
* @author Mark Paluch
*/
public record CustomAddressbook(String theId, String myDetailsAsJson) {
}

View File

@ -0,0 +1,4 @@
/**
* Package showing conversion features.
*/
package example.springdata.cassandra.convert;

View File

@ -0,0 +1,36 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.events;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link User} table.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = User.class)
class BasicConfiguration {
@Bean
LoggingEventListener listener() {
return new LoggingEventListener();
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.events;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener;
import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent;
import org.springframework.data.cassandra.core.mapping.event.BeforeDeleteEvent;
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent;
/**
* {@link ApplicationListener} for Cassandra mapping events logging the events.
*
* @author Mark Paluch
*/
public class LoggingEventListener extends AbstractCassandraEventListener<Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingEventListener.class);
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onBeforeSave(org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent)
*/
@Override
public void onBeforeSave(BeforeSaveEvent<Object> event) {
LOGGER.info("onBeforeSave: {}, {}", event.getSource(), event.getStatement());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterSave(org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent)
*/
@Override
public void onAfterSave(AfterSaveEvent<Object> event) {
LOGGER.info("onAfterSave: {}", event.getSource());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onBeforeDelete(org.springframework.data.cassandra.core.mapping.event.BeforeDeleteEvent)
*/
@Override
public void onBeforeDelete(BeforeDeleteEvent<Object> event) {
LOGGER.info("onBeforeDelete: {}", event.getSource());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterDelete(org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent)
*/
@Override
public void onAfterDelete(AfterDeleteEvent<Object> event) {
LOGGER.info("onAfterDelete: {}", event.getSource());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterLoad(org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent)
*/
@Override
public void onAfterLoad(AfterLoadEvent<Object> event) {
LOGGER.info("onAfterLoad: {}", event.getSource());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterConvert(org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent)
*/
@Override
public void onAfterConvert(AfterConvertEvent<Object> event) {
LOGGER.info("onAfterConvert: {}", event.getSource());
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.events;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* Sample user class.
*
* @author Mark Paluch
*/
@Table(value = "users")
public record User(@PrimaryKey long id, String firstname, String lastname) {
}

View File

@ -0,0 +1,4 @@
/**
* Package usage of Lifecycle events.
*/
package example.springdata.cassandra.events;

View File

@ -0,0 +1,31 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.optimisticlocking;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link OptimisticPerson} table.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = OptimisticPerson.class)
class BasicConfiguration {
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.optimisticlocking;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* Simple domain object that declares properties annotated with Spring Data's {@code @Version} annotation to enable the
* object for optimistic locking.
*
* @author Mark Paluch
*/
@Table
public record OptimisticPerson(@Id Long id, @Version long version, String name) {
public OptimisticPerson withName(String name) {
return new OptimisticPerson(id, version, name);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.optimisticlocking;
import org.springframework.data.repository.CrudRepository;
/**
* Simple repository interface for {@link OptimisticPerson} instances.
*
* @author Mark Paluch
*/
public interface OptimisticPersonRepository extends CrudRepository<OptimisticPerson, Long> {}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.optimisticlocking;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* Simple domain object.
*
* @author Mark Paluch
*/
@Data
@Table
public class SimplePerson {
@Id Long id;
String name;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
import lombok.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* @author Mark Paluch
*/
@Table
record Customer(@Id String id, String firstname, String lastname) {
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
/**
* An example projection interface containing only the firstname.
*
* @author Mark Paluch
*/
interface CustomerProjection {
String getFirstname();
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.repository.CrudRepository;
/**
* Sample repository managing customers to show projecting functionality of Spring Data Cassandra.
*
* @author Mark Paluch
*/
interface CustomerRepository extends CrudRepository<Customer, String> {
/**
* Uses a projection interface to indicate the fields to be returned. As the projection doesn't use any dynamic
* fields, the query execution will be restricted to only the fields needed by the projection.
*
* @return
*/
Collection<CustomerProjection> findAllProjectedBy();
/**
* When a projection is used that contains dynamic properties (i.e. SpEL expressions in an {@link Value} annotation),
* the normal target entity will be loaded but dynamically projected so that the target can be referred to in the
* expression.
*
* @return
*/
Collection<CustomerSummary> findAllSummarizedBy();
/**
* Passes in the projection type dynamically.
*
* @param id
* @param projection
* @return
*/
<T> Collection<T> findById(String id, Class<T> projection);
/**
* Projection for a single entity.
*
* @param id
* @return
*/
CustomerProjection findProjectedById(String id);
/**
* Dynamic projection for a single entity.
*
* @param id
* @param projection
* @return
*/
<T> T findProjectedById(String id, Class<T> projection);
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
import org.springframework.beans.factory.annotation.Value;
/**
* An example of using SpEL with projections.
*
* @author Mark Paluch
*/
interface CustomerSummary {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link Customer} table.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EntityScan(basePackageClasses = Customer.class)
class ProjectionConfiguration {
}

View File

@ -0,0 +1,4 @@
/**
* Package showing projection features.
*/
package example.springdata.cassandra.projection;

View File

@ -0,0 +1,25 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Mark Paluch
*/
@SpringBootApplication
class CassandraConfiguration {
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.ZoneId;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* @author Mark Paluch
*/
@Table("pizza_orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id String id;
LocalDate orderDate;
ZoneId zoneId;
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import java.time.LocalDate;
import java.time.ZoneId;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.Repository;
/**
* Repository to manage {@link Order} instances.
*
* @author Mark Paluch
*/
public interface OrderRepository extends Repository<Order, String> {
/**
* Method parameters are converted according the registered Converters into Cassandra types.
*/
@Query("SELECT * from pizza_orders WHERE orderdate = ?0 and zoneid = ?1 ALLOW FILTERING")
Order findOrderByOrderDateAndZoneId(LocalDate orderDate, ZoneId zoneId);
void deleteAll();
Order save(Order order);
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* @author Mark Paluch
*/
@Table
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
@Id String id;
String firstname, lastname;
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.Repository;
/**
* Repository to manage {@link Person} instances.
*
* @author Mark Paluch
*/
public interface PersonRepository extends Repository<Person, String> {
Optional<Person> findById(String id);
Stream<Person> findAll();
/**
* Sample method to derive a query from using JDK 8's {@link Optional} as return type.
*
* @param id
* @return
*/
@Query("select * from person where id = ?0")
Optional<Person> findPersonById(String id);
/**
* Sample default method to show JDK 8 feature support.
*
* @param person
* @return
*/
default Optional<Person> findByPerson(Person person) {
return findPersonById(person == null ? null : person.id);
}
void deleteAll();
Person save(Person person);
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Package showing Java 8 features.
*/
package example.springdata.cassandra.streamoptional;

View File

@ -0,0 +1,26 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.udt;
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
/**
* @author Mark Paluch
*/
@UserDefinedType
public record Address(String street, String zip, String city) {
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.udt;
import lombok.Data;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.Table;
import com.datastax.oss.driver.api.core.data.UdtValue;
/**
* @author Mark Paluch
*/
@Data
@Table
public class Person {
@Id int id;
String firstname, lastname;
Address current;
List<Address> previous;
@CassandraType(type = CassandraType.Name.UDT, userTypeName = "address") UdtValue alternative;
}

View File

@ -0,0 +1,3 @@
spring.data.cassandra.keyspace-name=example
spring.data.cassandra.schema-action=recreate
spring.data.cassandra.local-datacenter=datacenter1

View File

@ -0,0 +1,92 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.time.Duration;
import java.time.Instant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Integration test showing the basic usage of Auditing through {@link AuditedPersonRepository}.
*
* @author Mark Paluch
*/
@SpringBootTest(classes = BasicConfiguration.class)
@CassandraKeyspace
class AuditedPersonRepositoryTests {
@Autowired AuditedPersonRepository repository;
@BeforeEach
void setUp() {
repository.deleteAll();
}
/**
* Saving an object using the Cassandra Repository will create a persistent representation of the object in Cassandra.
*/
@Test
void insertShouldSetCreatedDate() {
var person = new AuditedPerson();
person.setId(42L);
person.setNew(true); // Cassandra does not support auto-generation hence we need
// to supply whether our object is a new one.
var saved = repository.save(person);
assertThat(saved.getCreatedBy()).isEqualTo("Some user");
assertThat(saved.getLastModifiedBy()).isEqualTo("Some user");
assertThat(saved.getCreatedDate()).isBetween(Instant.now().minus(Duration.ofMinutes(1)),
Instant.now().plus(Duration.ofMinutes(1)));
assertThat(saved.getLastModifiedDate()).isBetween(Instant.now().minus(Duration.ofMinutes(1)),
Instant.now().plus(Duration.ofMinutes(1)));
}
/**
* Modifying an existing object will update the last modified fields.
*/
@Test
void updateShouldSetLastModifiedDate() {
var person = new AuditedPerson();
person.setId(42L);
person.setNew(true); // Cassandra does not support auto-generation hence we need
// to supply whether our object is a new one.
repository.save(person);
person.setNew(false);
var modified = repository.save(person);
assertThat(modified.getCreatedBy()).isEqualTo("Some user");
assertThat(modified.getLastModifiedBy()).isEqualTo("Some user");
assertThat(modified.getCreatedDate()).isBetween(Instant.now().minus(Duration.ofMinutes(1)),
Instant.now().plus(Duration.ofMinutes(1)));
assertThat(modified.getLastModifiedDate())
.isBetween(Instant.now().minus(Duration.ofMinutes(1)), Instant.now().plus(Duration.ofMinutes(1)))
.isNotEqualTo(modified.getCreatedDate());
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.basic;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import example.springdata.cassandra.util.CassandraVersion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version;
import com.datastax.oss.driver.api.core.CqlSession;
/**
* Integration test showing the basic usage of {@link BasicUserRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
@SpringBootTest(classes = BasicConfiguration.class)
@CassandraKeyspace
class BasicUserRepositoryTests {
private final static Version CASSANDRA_3_4 = Version.parse("3.4");
@Autowired BasicUserRepository repository;
@Autowired CqlSession session;
private User user;
@BeforeEach
void setUp() {
user = new User();
user.setId(42L);
user.setUsername("foobar");
user.setFirstname("firstname");
user.setLastname("lastname");
}
/**
* Saving an object using the Cassandra Repository will create a persistent representation of the object in Cassandra.
*/
@Test
void findSavedUserById() {
user = repository.save(user);
assertThat(repository.findById(user.getId())).contains(user);
}
/**
* Cassandra can be queries by using query methods annotated with {@link @Query}.
*/
@Test
void findByAnnotatedQueryMethod() {
repository.save(user);
assertThat(repository.findUserByIdIn(1000)).isNull();
assertThat(repository.findUserByIdIn(42)).isEqualTo(user);
}
/**
* Spring Data Cassandra supports query derivation so annotating query methods with
* {@link org.springframework.data.cassandra.repository.Query} is optional. Querying columns other than the primary
* key requires a secondary index.
*/
@Test
void findByDerivedQueryMethod() throws InterruptedException {
session.execute("CREATE INDEX IF NOT EXISTS user_username ON users (uname);");
/*
Cassandra secondary indexes are created in the background without the possibility to check
whether they are available or not. So we are forced to just wait. *sigh*
*/
Thread.sleep(1000);
repository.save(user);
assertThat(repository.findUserByUsername(user.getUsername())).isEqualTo(user);
}
/**
* Spring Data Cassandra supports {@code LIKE} and {@code CONTAINS} query keywords to for SASI indexes.
*/
@Test
void findByDerivedQueryMethodWithSASI() throws InterruptedException {
assumeThat(CassandraVersion.getReleaseVersion(session).isGreaterThanOrEqualTo(CASSANDRA_3_4)).isTrue();
session.execute("CREATE CUSTOM INDEX ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
/*
Cassandra secondary indexes are created in the background without the possibility to check
whether they are available or not. So we are forced to just wait. *sigh*
*/
Thread.sleep(1000);
repository.save(user);
assertThat(repository.findUsersByLastnameStartsWith("last")).contains(user);
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.basic;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.AsyncCassandraTemplate;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.datastax.oss.driver.api.querybuilder.insert.Insert;
/**
* Integration test showing the basic usage of {@link CassandraTemplate}.
*
* @author Mark Paluch
*/
@SpringBootTest(classes = BasicConfiguration.class)
@CassandraKeyspace
class CassandraOperationsIntegrationTests {
@Autowired CqlSession session;
@Autowired CassandraOperations template;
@BeforeEach
void setUp() throws Exception {
template.getCqlOperations().execute("TRUNCATE users");
}
/**
* Cassandra {@link com.datastax.driver.core.Statement}s can be used together with {@link CassandraTemplate} and the
* mapping layer.
*/
@Test
void insertAndSelect() {
var insert = QueryBuilder.insertInto("users").value("user_id", QueryBuilder.literal(42L)) //
.value("uname", QueryBuilder.literal("heisenberg")) //
.value("fname", QueryBuilder.literal("Walter")) //
.value("lname", QueryBuilder.literal("White")) //
.ifNotExists(); //
template.getCqlOperations().execute(insert.asCql());
var user = template.selectOneById(42L, User.class);
assertThat(user.getUsername()).isEqualTo("heisenberg");
var users = template.select(QueryBuilder.selectFrom("users").all().asCql(), User.class);
assertThat(users).hasSize(1);
assertThat(users.get(0)).isEqualTo(user);
}
/**
* Objects can be inserted and updated using {@link CassandraTemplate}. What you {@code update} is what you
* {@code select}.
*/
@Test
void insertAndUpdate() {
var user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
template.insert(user);
user.setFirstname(null);
template.update(user);
var loaded = template.selectOneById(42L, User.class);
assertThat(loaded.getUsername()).isEqualTo("heisenberg");
assertThat(loaded.getFirstname()).isNull();
}
/**
* Asynchronous query execution using callbacks.
*/
@Test
void insertAsynchronously() throws InterruptedException {
var user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
final var countDownLatch = new CountDownLatch(1);
var asyncTemplate = new AsyncCassandraTemplate(session);
var future = asyncTemplate.insert(user);
future.addCallback(it -> countDownLatch.countDown(), throwable -> countDownLatch.countDown());
countDownLatch.await(5, TimeUnit.SECONDS);
var loaded = template.selectOneById(user.getId(), User.class);
assertThat(loaded).isEqualTo(user);
}
/**
* {@link CassandraTemplate} allows selection of projections on template-level. All basic data types including
* {@link Row} can be selected.
*/
@Test
@SuppressWarnings("unchecked")
void selectProjections() {
var user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
template.insert(user);
var id = template.selectOne(QueryBuilder.selectFrom("users").column("user_id").asCql(), Long.class);
assertThat(id).isEqualTo(user.getId());
var row = template.selectOne(QueryBuilder.selectFrom("users").column("user_id").asCql(), Row.class);
assertThat(row.getLong(0)).isEqualTo(user.getId());
Map<String, Object> map = template.selectOne(QueryBuilder.selectFrom("users").all().asCql(), Map.class);
assertThat(map).containsEntry("user_id", user.getId());
assertThat(map).containsEntry("fname", "Walter");
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.convert;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.Arrays;
import java.util.Currency;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.CassandraOperations;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.data.TupleValue;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
/**
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest(classes = ConverterConfiguration.class)
class ConversionIntegrationTests {
@Autowired CassandraOperations operations;
@BeforeEach
void setUp() {
operations.truncate(Addressbook.class);
}
/**
* Creates and stores a new {@link Addressbook} inside of Cassandra. {@link Contact} classes are converted using the
* custom {@link example.springdata.cassandra.convert.ConverterConfiguration.PersonWriteConverter}.
*/
@Test
void shouldCreateAddressbook() {
var addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
addressbook.setFriends(Arrays.asList(new Contact("Jesse", "Pinkman"), new Contact("Saul", "Goodman")));
operations.insert(addressbook);
var row = operations.selectOne(QueryBuilder.selectFrom("addressbook").all().asCql(), Row.class);
assertThat(row).isNotNull();
assertThat(row.getString("id")).isEqualTo("private");
assertThat(row.getString("me")).contains("\"firstname\":\"Walter\"");
assertThat(row.getList("friends", String.class)).hasSize(2);
}
/**
* Creates and loads a new {@link Addressbook} inside of Cassandra. {@link Contact} classes are converted using the
* custom {@link example.springdata.cassandra.convert.ConverterConfiguration.PersonReadConverter}.
*/
@Test
void shouldReadAddressbook() {
var addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
addressbook.setFriends(Arrays.asList(new Contact("Jesse", "Pinkman"), new Contact("Saul", "Goodman")));
operations.insert(addressbook);
var loaded = operations.selectOne(QueryBuilder.selectFrom("addressbook").all().asCql(), Addressbook.class);
assertThat(loaded.getMe()).isEqualTo(addressbook.getMe());
assertThat(loaded.getFriends()).isEqualTo(addressbook.getFriends());
}
/**
* Creates and stores a new {@link Addressbook} inside of Cassandra. The {@link Addressbook} is read back to a
* {@link CustomAddressbook} class using the
* {@link example.springdata.cassandra.convert.ConverterConfiguration.CustomAddressbookReadConverter}.
*/
@Test
void shouldReadCustomAddressbook() {
var addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
operations.insert(addressbook);
var loaded = operations.selectOne(QueryBuilder.selectFrom("addressbook").all().asCql(),
CustomAddressbook.class);
assertThat(loaded.theId()).isEqualTo(addressbook.getId());
assertThat(loaded.myDetailsAsJson()).contains("\"firstname\":\"Walter\"");
}
/**
* Creates and stores a new {@link Addressbook} inside of Cassandra writing map and tuple columns.
*/
@Test
void shouldWriteConvertedMapsAndTuples() {
var addressbook = new Addressbook();
addressbook.setId("private");
Map<Integer, Currency> preferredCurrencies = new HashMap<>();
preferredCurrencies.put(1, Currency.getInstance("USD"));
preferredCurrencies.put(2, Currency.getInstance("EUR"));
var address = new Address("3828 Piermont Dr", "Albuquerque", "87111");
addressbook.setPreferredCurrencies(preferredCurrencies);
addressbook.setAddress(address);
operations.insert(addressbook);
var row = operations.selectOne(QueryBuilder.selectFrom("addressbook").all().asCql(), Row.class);
assertThat(row).isNotNull();
var tupleValue = row.getTupleValue("address");
assertThat(tupleValue.getString(0)).isEqualTo(address.address());
assertThat(tupleValue.getString(1)).isEqualTo(address.city());
assertThat(tupleValue.getString(2)).isEqualTo(address.zip());
var rawPreferredCurrencies = row.getMap("preferredCurrencies", Integer.class, String.class);
assertThat(rawPreferredCurrencies).containsEntry(1, "USD").containsEntry(2, "EUR");
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.events;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.query.Query;
/**
* Test showing differences between fetching results as {@link List} and {@link Stream streaming} results using
* Cassandra Lifecyle Events.
*
* @author Mark Paluch
*/
@SpringBootTest(classes = BasicConfiguration.class)
@CassandraKeyspace
class LifecycleEventsTests {
@Autowired CassandraOperations operations;
@Test
void shouldStreamEntities() {
insertEntities();
var userStream = operations.stream(Query.empty(), User.class);
userStream.forEach(System.out::println);
}
@Test
void shouldReturnEntitiesAsList() {
insertEntities();
var userStream = operations.select(Query.empty(), User.class);
userStream.forEach(System.out::println);
}
private void insertEntities() {
var walter = new User(1, "Walter", "White");
var skyler = new User(2, "Skyler", "White");
var jesse = new User(3, "Jesse Pinkman", "Jesse Pinkman");
operations.truncate(User.class);
operations.insert(walter);
operations.insert(skyler);
operations.insert(jesse);
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.optimisticlocking;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.UpdateOptions;
import org.springframework.data.cassandra.core.query.Criteria;
/**
* Integration test showing the basic usage of Optimistic Locking through {@link OptimisticPersonRepository}.
*
* @author Mark Paluch
*/
@SpringBootTest(classes = BasicConfiguration.class)
@CassandraKeyspace
class OptimisticPersonRepositoryTests {
@Autowired OptimisticPersonRepository repository;
@Autowired CassandraOperations operations;
@BeforeEach
void setUp() {
repository.deleteAll();
}
/**
* Saving an object using the Cassandra Repository will create a persistent representation of the object in Cassandra
* and increment the version property.
*/
@Test
void insertShouldIncrementVersion() {
var person = new OptimisticPerson(42L, 0, "Walter White");
var saved = repository.save(person);
assertThat(saved.version()).isGreaterThan(0);
}
/**
* Modifying an existing object will update the last modified fields.
*/
@Test
void updateShouldDetectChangedEntity() {
var person = new OptimisticPerson(42L, 0, "Walter White");
// Load the person because we intend to change it.
var saved = repository.save(person);
// Another process has changed the person object in the meantime.
var anotherProcess = repository.findById(person.id()).get();
anotherProcess = anotherProcess.withName("Heisenberg");
repository.save(anotherProcess);
// Now it's our turn to modify the object...
var ourSaved = saved.withName("Walter");
// ...which fails with a OptimisticLockingFailureException, using LWT under the hood.
assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> repository.save(ourSaved));
}
/**
* This tests uses lightweight transactions by leveraging mapped {@code IF} conditions with the {@code UPDATE}
* statement through {@link CassandraOperations#update(Object, UpdateOptions)}.
*/
@Test
void updateUsingLightWeightTransactions() {
var person = new SimplePerson();
person.setId(42L);
person.setName("Walter White");
operations.insert(person);
var success = operations.update(person,
UpdateOptions.builder().ifCondition(Criteria.where("name").is("Walter White")).build());
assertThat(success.wasApplied()).isTrue();
var failed = operations.update(person,
UpdateOptions.builder().ifCondition(Criteria.where("name").is("Heisenberg")).build());
assertThat(failed.wasApplied()).isFalse();
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.projection;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.Collection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.projection.TargetAware;
/**
* Integration tests for {@link CustomerRepository} to show projection capabilities.
*
* @author Mark Paluch
*/
@SpringBootTest(classes = ProjectionConfiguration.class)
@CassandraKeyspace
class CustomerRepositoryIntegrationTest {
@Autowired CustomerRepository customers;
private Customer dave, carter;
@BeforeEach
void setUp() {
customers.deleteAll();
this.dave = customers.save(new Customer("d", "Dave", "Matthews"));
this.carter = customers.save(new Customer("c", "Carter", "Beauford"));
}
@Test
void projectsEntityIntoInterface() {
var result = customers.findAllProjectedBy();
assertThat(result).hasSize(2);
assertThat(result.iterator().next().getFirstname()).isEqualTo("Carter");
}
@Test
void projectsDynamically() {
var result = customers.findById("d", CustomerProjection.class);
assertThat(result).hasSize(1);
assertThat(result.iterator().next().getFirstname()).isEqualTo("Dave");
}
@Test
void projectsIndividualDynamically() {
var result = customers.findProjectedById(dave.id(), CustomerSummary.class);
assertThat(result).isNotNull();
assertThat(result.getFullName()).isEqualTo("Dave Matthews");
// Proxy backed by original instance as the projection uses dynamic elements
assertThat(((TargetAware) result).getTarget()).isInstanceOf(Customer.class);
}
@Test
void projectIndividualInstance() {
var result = customers.findProjectedById(dave.id());
assertThat(result).isNotNull();
assertThat(result.getFirstname()).isEqualTo("Dave");
assertThat(((TargetAware) result).getTarget()).isInstanceOf(Customer.class);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.time.LocalDate;
import java.time.ZoneId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Integration test to show the usage of JSR-310 date/time types with Spring Data Cassandra.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest(classes = CassandraConfiguration.class)
class Jsr310IntegrationTests {
@Autowired OrderRepository repository;
@BeforeEach
void setUp() throws Exception {
repository.deleteAll();
}
@Test
void findOneByJsr310Types() {
var order = new Order("42", LocalDate.now(), ZoneId.systemDefault());
repository.save(order);
assertThat(repository.findOrderByOrderDateAndZoneId(order.getOrderDate(), order.getZoneId())).isEqualTo(order);
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.streamoptional;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Integration test to show the usage of Java {@link Stream} and {@link Optional} features with Spring Data Cassandra.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest(classes = CassandraConfiguration.class)
class StreamOptionalIntegrationTests {
@Autowired PersonRepository repository;
@BeforeEach
void setUp() throws Exception {
repository.deleteAll();
}
@Test
void providesFindOneWithOptional() {
var homer = repository.save(new Person("1", "Homer", "Simpson"));
assertThat(repository.findById(homer.id).isPresent()).isTrue();
assertThat(repository.findById(homer.id + 1)).isEqualTo(Optional.<Person> empty());
}
@Test
void invokesDefaultMethod() {
var homer = repository.save(new Person("1", "Homer", "Simpson"));
var result = repository.findByPerson(homer);
assertThat(result.isPresent()).isTrue();
assertThat(result.get()).isEqualTo(homer);
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream}. Note, that since the
* resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
void useJava8StreamsWithCustomQuery() {
var homer = repository.save(new Person("1", "Homer", "Simpson"));
var bart = repository.save(new Person("2", "Bart", "Simpson"));
try (var stream = repository.findAll()) {
assertThat(stream.collect(Collectors.toList())).contains(homer, bart);
}
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.udt;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraOperations;
import com.datastax.oss.driver.api.core.data.UdtValue;
import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata;
import com.datastax.oss.driver.api.core.type.UserDefinedType;
/**
* Integration test to show User-Defined type support.
*
* @author Mark Paluch
* @author Oliver Gierke
*/
@SpringBootTest
@CassandraKeyspace
class UserDefinedTypeIntegrationTest {
@Configuration
static class Config extends AbstractCassandraConfiguration {
@Override
protected int getPort() {
return Integer.getInteger("spring.data.cassandra.port");
}
@Override
protected String getContactPoints() {
return System.getProperty("spring.data.cassandra.contact-points");
}
@Override
public String getKeyspaceName() {
return "example";
}
@Override
protected String getLocalDataCenter() {
return "datacenter1";
}
@Override
public String[] getEntityBasePackages() {
return new String[] { Person.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
}
@Autowired CassandraOperations operations;
@Autowired CassandraAdminOperations adminOperations;
@BeforeEach
void before() throws Exception {
operations.getCqlOperations().execute("TRUNCATE person");
}
/**
* Insert a row with a mapped User-defined type.
*/
@Test
void insertMappedUdt() {
var person = new Person();
person.setId(42);
person.setFirstname("Walter");
person.setLastname("White");
person.setCurrent(new Address("308 Negra Arroyo Lane", "87104", "Albuquerque"));
person.setPrevious(Collections.singletonList(new Address("12000 12100 Coors Rd SW", "87045", "Albuquerque")));
operations.insert(person);
var loaded = operations.selectOne("SELECT * FROM person WHERE id = 42", Person.class);
assertThat(loaded.getCurrent()).isEqualTo(person.getCurrent());
assertThat(loaded.getPrevious()).containsAll(person.getPrevious());
}
/**
* Insert a row with a raw User-defined type.
*/
@Test
void insertRawUdt() {
var keyspaceMetadata = adminOperations.getKeyspaceMetadata();
var address = keyspaceMetadata.getUserDefinedType("address").get();
var udtValue = address.newValue();
udtValue.setString("street", "308 Negra Arroyo Lane");
udtValue.setString("zip", "87104");
udtValue.setString("city", "Albuquerque");
var person = new Person();
person.setId(42);
person.setFirstname("Walter");
person.setLastname("White");
person.setAlternative(udtValue);
operations.insert(person);
var loaded = operations.selectOne("SELECT * FROM person WHERE id = 42", Person.class);
assertThat(loaded.getAlternative().getString("zip")).isEqualTo("87104");
}
}

View File

@ -0,0 +1,59 @@
# Spring Data Cassandra - Kotlin examples
This project contains samples of Kotlin-specific features of Spring Data (Cassandra).
## Value defaulting on entity construction
Kotlin allows defaulting for constructor- and method arguments.
Defaulting allows usage of substitute values if a field in the document is absent or simply `null`.
Spring Data inspects objects whether they are Kotlin types and uses the appropriate constructor.
```kotlin
@Table
data class Person(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) val firstname: String? = "", val lastname: String = "White")
operations.cqlOperations.execute(QueryBuilder.insertInto("person").value("firstname", "Walter"))
val person = operations.query<Person>()
.matching(query(where("firstname").isEqualTo("Walter")))
.firstValue()!!
assertThat(person.lastname).isEqualTo("White")
```
## Kotlin Extensions
Spring Data exposes methods accepting a target type to either query for or to project results values on.
Kotlin represents classes with its own type, `KClass` which can be an obstacle when attempting to obtain a Java `Class` type.
Spring Data ships with extensions that add overloads for methods accepting a type parameter by either leveraging generics or accepting `KClass` directly.
```kotlin
operations.getTableName<Person>()
operations.getTableName(Person::class)
```
## Nullability
Declaring repository interfaces using Kotlin allows expressing nullability constraints on arguments and return types. Spring Data evaluates nullability of arguments and return types and reacts to these. Passing `null` to a non-nullable argument raises an `IllegalArgumentException`, as you're already used to from Kotlin. Spring Data helps you also to prevent `null` in query results. If you wish to return a nullable result, use Kotlin's nullability marker `?`. To prevent `null` results, declare the return type of a query method as non-nullable. In the case a query yields no result, a non-nullable query method throws `EmptyResultDataAccessException`.
```kotlin
interface PersonRepository : CrudRepository<Person, String> {
/**
* Query method declaring a nullable return type that allows to return null values.
*/
fun findOneOrNoneByFirstname(firstname: String): Person?
/**
* Query method declaring a nullable argument.
*/
fun findNullableByFirstname(firstname: String?): Person?
/**
* Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
*/
fun findOneByFirstname(firstname: String): Person
}
```

62
cassandra/kotlin/pom.xml Normal file
View File

@ -0,0 +1,62 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-data-cassandra-kotlin</artifactId>
<name>Spring Data Cassandra - Kotlin features</name>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
/**
* @author Mark Paluch
*/
@SpringBootApplication
class ApplicationConfiguration
fun main(args: Array<String>) {
runApplication<ApplicationConfiguration>(*args)
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.kotlin
import org.springframework.data.cassandra.core.cql.PrimaryKeyType
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn
import org.springframework.data.cassandra.core.mapping.Table
/**
* An entity to represent a Person.
*
* @author Mark Paluch
*/
@Table
data class Person(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) val firstname: String? = "", val lastname: String = "White")

View File

@ -0,0 +1,41 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.kotlin
import org.springframework.data.repository.CrudRepository
/**
* Repository interface to manage [Person] instances.
*
* @author Mark Paluch
*/
interface PersonRepository : CrudRepository<Person, String> {
/**
* Query method declaring a nullable return type that allows to return null values.
*/
fun findOneOrNoneByFirstname(firstname: String): Person?
/**
* Query method declaring a nullable argument.
*/
fun findNullableByFirstname(firstname: String?): Person?
/**
* Query method requiring a result. Throws [org.springframework.dao.EmptyResultDataAccessException] if no result is found.
*/
fun findOneByFirstname(firstname: String): Person
}

View File

@ -0,0 +1,3 @@
spring.data.cassandra.keyspace-name=example
spring.data.cassandra.schema-action=recreate
spring.data.cassandra.local-datacenter=datacenter1

View File

@ -0,0 +1,73 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.kotlin
import example.springdata.cassandra.util.CassandraKeyspace
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.dao.EmptyResultDataAccessException
/**
* Tests showing Kotlin usage of Spring Data Repositories.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest
class RepositoryTests {
@Autowired
lateinit var repository: PersonRepository
@BeforeEach
fun before() {
repository.deleteAll()
}
@Test
fun `should find one person`() {
repository.save(Person("Walter", "White"))
val walter = repository.findOneByFirstname("Walter")
assertThat(walter).isNotNull()
assertThat(walter.firstname).isEqualTo("Walter")
assertThat(walter.lastname).isEqualTo("White")
}
@Test
fun `should return null if no person found`() {
repository.save(Person("Walter", "White"))
val walter = repository.findOneOrNoneByFirstname("Hank")
assertThat(walter).isNull()
}
@Test
fun `should throw EmptyResultDataAccessException if no person found`() {
repository.save(Person("Walter", "White"))
assertThatThrownBy { repository.findOneByFirstname("Hank") }.isInstanceOf(EmptyResultDataAccessException::class.java)
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.kotlin
import com.datastax.oss.driver.api.core.CqlIdentifier
import com.datastax.oss.driver.api.querybuilder.QueryBuilder
import example.springdata.cassandra.util.CassandraKeyspace
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.cassandra.core.*
import org.springframework.data.cassandra.core.query.Query.query
import org.springframework.data.cassandra.core.query.isEqualTo
import org.springframework.data.cassandra.core.query.where
/**
* Tests showing Kotlin usage of [MongoTemplate] and its Kotlin extensions.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest
class TemplateTests {
@Autowired
lateinit var operations: CassandraOperations
@BeforeEach
fun before() {
operations.truncate<Person>()
}
@Test
fun `should create collection leveraging reified type parameters`() {
assertThat(operations.getTableName<Person>()).isEqualTo(CqlIdentifier.fromCql("person"))
}
@Test
fun `should insert and find person in a fluent API style`() {
operations.insert<Person>().inTable("person").one(Person("Walter", "White"))
val people = operations.query<Person>()
.matching(query(where("firstname").isEqualTo("Walter")))
.all()
assertThat(people).hasSize(1).extracting("firstname").containsOnly("Walter")
}
@Test
fun `should insert and project query results`() {
operations.insert<Person>().inTable("person").one(Person("Walter", "White"))
val firstnameOnly = operations.query<Person>()
.asType<FirstnameOnly>()
.matching(query(where("firstname").isEqualTo("Walter")))
.oneValue()
assertThat(firstnameOnly?.getFirstname()).isEqualTo("Walter")
}
@Test
fun `should insert and count objects in a fluent API style`() {
operations.insert<Person>().inTable("person").one(Person("Walter", "White"))
val count = operations.query<Person>()
.matching(query(where("firstname").isEqualTo("Walter")))
.count()
assertThat(count).isEqualTo(1)
}
@Test
fun `should insert and find person`() {
val person = Person("Walter", "White")
operations.insert(person)
val people = operations.select<Person>(query(where("firstname").isEqualTo("Walter")))
assertThat(people).hasSize(1).extracting("firstname").containsOnly("Walter")
}
@Test
fun `should apply defaulting for absent properties`() {
operations.cqlOperations.execute(QueryBuilder.insertInto("person").value("firstname", QueryBuilder.literal("Walter")).asCql())
val person = operations.query<Person>()
.matching(query(where("firstname").isEqualTo("Walter")))
.firstValue()!!
assertThat(person.firstname).isEqualTo("Walter")
assertThat(person.lastname).isEqualTo("White")
val resultSet = operations.cqlOperations.queryForResultSet("SELECT * FROM person WHERE firstname = 'Walter'")
val walter = resultSet.one()!!
assertThat(walter).isNotNull
assertThat(walter.getString("firstname")).isEqualTo("Walter")
assertThat(walter.getString("lastname")).isNull()
}
interface FirstnameOnly {
fun getFirstname(): String
}
}

40
cassandra/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-cassandra-examples</artifactId>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Data Cassandra - Examples</name>
<description>Sample projects for Spring Data Cassandra</description>
<url>https://projects.spring.io/spring-data-cassandra</url>
<inceptionYear>2014</inceptionYear>
<modules>
<module>util</module>
<module>example</module>
<module>kotlin</module>
<module>reactive</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,56 @@
# Spring Data Cassandra 2.0 - Reactive examples
This project contains samples of reactive data access features with Spring Data (Cassandra).
## Reactive Template API usage with `ReactiveCassandraTemplate`
The main reactive Template API class is `ReactiveCassandraTemplate`, ideally used through its interface `ReactiveCassandraOperations`. It defines a basic set of reactive data access operations using [Project Reactor](http://projectreactor.io) `Mono` and `Flux` reactive types.
```java
template.insert(Flux.just(new Person("Walter", "White", 50),
new Person("Skyler", "White", 45),
new Person("Saul", "Goodman", 42),
new Person("Jesse", "Pinkman", 27)));
Flux<Person> flux = template.select(select()
.from("person")
.where(eq("lastname", "White")), Person.class);
```
The test cases in `ReactiveCassandraTemplateIntegrationTest` show basic Template API usage.
Reactive data access reads and converts individual elements while processing the stream.
## Reactive Repository support
Spring Data Cassandra provides reactive repository support with Project Reactor and RxJava 1 reactive types. The reactive API supports reactive type conversion between reactive types.
```java
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {
Flux<Person> findByLastname(String lastname);
@Query("SELECT * FROM person WHERE firstname = ?0 and lastname = ?1")
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Flux<Person> findByLastname(Mono<String> lastname);
Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);
}
```
```java
public interface RxJava1PersonRepository extends RxJava1CrudRepository<Person, String> {
Observable<Person> findByLastname(String lastname);
@Query("SELECT * FROM person WHERE firstname = ?0 and lastname = ?1")
Single<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Observable<Person> findByLastname(Single<String> lastname);
Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);
}
```

View File

@ -0,0 +1,51 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-data-cassandra-reactive</artifactId>
<name>Spring Data Cassandra - Reactive features</name>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava-reactive-streams</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.cassandra.config.EnableReactiveCassandraAuditing;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.InsertOptions;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.domain.ReactiveAuditorAware;
/**
* Simple configuration for reactive Cassandra auditing.
*
* @author Mark Paluch
*/
@SpringBootApplication
@EnableReactiveCassandraAuditing
class ApplicationConfiguration {
@Bean
ReactiveAuditorAware<String> reactiveAuditorAware() {
return () -> Mono.just("the-current-user");
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import java.time.Instant;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Transient;
import org.springframework.data.cassandra.core.mapping.Table;
import org.springframework.data.domain.Persistable;
/**
* An entity to represent a Person.
*
* @author Mark Paluch
*/
@Data
@Table
@RequiredArgsConstructor
public class Order implements Persistable<String> {
@Transient boolean isNew;
@Id final String orderId;
@CreatedBy String createdBy;
@CreatedDate Instant createdDate;
@LastModifiedBy String lastModifiedBy;
@LastModifiedDate Instant lastModifiedDate;
@Override
public String getId() {
return getOrderId();
}
@Override
public boolean isNew() {
return isNew;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* @author Mark Paluch
*/
public interface OrderRepository extends ReactiveCrudRepository<Order, String> {}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Simple configuration for reactive Cassandra support.
*
* @author Mark Paluch
*/
@SpringBootApplication
class ApplicationConfiguration {
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* An entity to represent a Person.
*
* @author Mark Paluch
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table
public class Person {
@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED, ordinal = 2) //
private String firstname;
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1) //
private String lastname;
private int age;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* Repository interface to manage {@link Person} instances.
*
* @author Mark Paluch
*/
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {
/**
* Derived query selecting by {@code lastname}.
*
* @param lastname
* @return
*/
Flux<Person> findByLastname(String lastname);
/**
* String query selecting one entity.
*
* @param lastname
* @return
*/
@Query("SELECT * FROM person WHERE firstname = ?0 and lastname = ?1")
Mono<Person> findByFirstnameInAndLastname(String firstname, String lastname);
/**
* Derived query selecting by {@code lastname}. {@code lastname} uses deferred resolution that does not require
* blocking to obtain the parameter value.
*
* @param lastname
* @return
*/
Flux<Person> findByLastname(Mono<String> lastname);
/**
* Derived query selecting by {@code firstname} and {@code lastname}. {@code firstname} uses deferred resolution that
* does not require blocking to obtain the parameter value.
*
* @param firstname
* @param lastname
* @return
*/
Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import io.reactivex.Observable;
import io.reactivex.Single;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.reactive.RxJava2CrudRepository;
/**
* Repository interface to manage {@link Person} instances.
*
* @author Mark Paluch
*/
public interface RxJava2PersonRepository extends RxJava2CrudRepository<Person, String> {
/**
* Derived query selecting by {@code lastname}.
*
* @param lastname
* @return
*/
Observable<Person> findByLastname(String lastname);
/**
* String query selecting one entity.
*
* @param lastname
* @return
*/
@Query("SELECT * FROM person WHERE firstname = ?0 and lastname = ?1")
Single<Person> findByFirstnameAndLastname(String firstname, String lastname);
/**
* Derived query selecting by {@code lastname}. {@code lastname} uses deferred resolution that does not require
* blocking to obtain the parameter value.
*
* @param lastname
* @return
*/
Observable<Person> findByLastname(Single<String> lastname);
/**
* Derived query selecting by {@code firstname} and {@code lastname}. {@code firstname} uses deferred resolution that
* does not require blocking to obtain the parameter value.
*
* @param firstname
* @param lastname
* @return
*/
Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.spel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension;
/**
* Simple configuration for reactive Cassandra SpEL support.
*
* @author Mark Paluch
*/
@SpringBootApplication
class ApplicationConfiguration {
@Bean
ReactiveTenantExtension tenantExtension() {
return ReactiveTenantExtension.INSTANCE;
}
/**
* Extension that looks up a {@link Tenant} from the {@link reactor.util.context.Context}.
*/
enum ReactiveTenantExtension implements ReactiveEvaluationContextExtension {
INSTANCE;
@Override
public Mono<? extends EvaluationContextExtension> getExtension() {
return Mono.deferContextual(contextView -> Mono.just(new TenantExtension(contextView.get(Tenant.class))));
}
@Override
public String getExtensionId() {
return "my-reactive-tenant-extension";
}
}
/**
* Actual extension providing access to the {@link Tenant} value object.
*/
@RequiredArgsConstructor
static class TenantExtension implements EvaluationContextExtension {
private final Tenant tenant;
@Override
public String getExtensionId() {
return "my-tenant-extension";
}
@Override
public Tenant getRootObject() {
return tenant;
}
}
/**
* The root object.
*/
@Value
public static class Tenant {
String tenantId;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.spel;
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Table;
/**
* @author Mark Paluch
*/
@Table
public record Employee(@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) String tenantId,
@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED) String name) {
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.spel;
import reactor.core.publisher.Flux;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* @author Mark Paluch
*/
public interface EmployeeRepository extends ReactiveCrudRepository<Employee, String> {
@Query("SELECT * FROM employee WHERE tenantId = :#{getTenantId()} AND name = :name")
Flux<Employee> findAllByName(String name);
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.auditing;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import reactor.test.StepVerifier;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
/**
* Integration tests showing Reactive Auditing with Cassandra in action.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@DataCassandraTest
public class AuditingIntegrationTests {
@Autowired OrderRepository orderRepository;
@Test
public void shouldUpdateAuditor() throws InterruptedException {
var order = new Order("4711");
order.setNew(true);
orderRepository.save(order).as(StepVerifier::create).assertNext(actual -> {
assertThat(actual.getCreatedBy()).isEqualTo("the-current-user");
assertThat(actual.getCreatedDate()).isBetween(Instant.now().minusSeconds(60), Instant.now().plusSeconds(60));
assertThat(actual.getLastModifiedBy()).isEqualTo("the-current-user");
assertThat(actual.getLastModifiedDate()).isBetween(Instant.now().minusSeconds(60), Instant.now().plusSeconds(60));
}).verifyComplete();
Thread.sleep(10);
order = orderRepository.findById("4711").block();
orderRepository.save(order).as(StepVerifier::create).assertNext(actual -> {
assertThat(actual.getCreatedBy()).isEqualTo("the-current-user");
assertThat(actual.getCreatedDate()).isBefore(actual.getLastModifiedDate());
assertThat(actual.getLastModifiedBy()).isEqualTo("the-current-user");
assertThat(actual.getLastModifiedDate()).isBetween(Instant.now().minusSeconds(60), Instant.now().plusSeconds(60));
}).verifyComplete();
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.util.CassandraKeyspace;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.RxReactiveStreams;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.ReactiveCassandraTemplate;
/**
* Integration test for {@link ReactiveCassandraTemplate}.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest
class ReactiveCassandraTemplateIntegrationTest {
@Autowired ReactiveCassandraTemplate template;
/**
* Truncate table and insert some rows.
*/
@BeforeEach
void setUp() {
var truncateAndInsert = template.truncate(Person.class) //
.thenMany(Flux.just(new Person("Walter", "White", 50), //
new Person("Skyler", "White", 45), //
new Person("Saul", "Goodman", 42), //
new Person("Jesse", "Pinkman", 27))) //
.flatMap(template::insert);
StepVerifier.create(truncateAndInsert).expectNextCount(4).verifyComplete();
}
/**
* This sample performs a count, inserts data and performs a count again using reactive operator chaining. It prints
* the two counts ({@code 4} and {@code 6}) to the console.
*/
@Test
void shouldInsertAndCountData() {
var saveAndCount = template.count(Person.class) //
.doOnNext(System.out::println) //
.thenMany(Flux.just(new Person("Hank", "Schrader", 43), //
new Person("Mike", "Ehrmantraut", 62)))
.flatMap(template::insert) //
.last() //
.flatMap(v -> template.count(Person.class)) //
.doOnNext(System.out::println);
StepVerifier.create(saveAndCount).expectNext(6L).verifyComplete();
}
/**
* Note that the all object conversions are performed before the results are printed to the console.
*/
@Test
void convertReactorTypesToRxJava1() throws Exception {
var flux = template.select("SELECT * FROM person WHERE lastname = 'White'", Person.class);
long count = RxReactiveStreams.toObservable(flux) //
.count() //
.toSingle() //
.toBlocking() //
.value(); //
assertThat(count).isEqualTo(2);
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import example.springdata.cassandra.util.CassandraKeyspace;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Integration test for {@link ReactivePersonRepository} using Project Reactor types and operators.
*
* @author Mark Paluch
*/
@SpringBootTest
@CassandraKeyspace
class ReactivePersonRepositoryIntegrationTest {
@Autowired ReactivePersonRepository repository;
/**
* Clear table and insert some rows.
*/
@BeforeEach
void setUp() {
var deleteAndInsert = repository.deleteAll() //
.thenMany(repository.saveAll(Flux.just(new Person("Walter", "White", 50), //
new Person("Skyler", "White", 45), //
new Person("Saul", "Goodman", 42), //
new Person("Jesse", "Pinkman", 27))));
StepVerifier.create(deleteAndInsert).expectNextCount(4).verifyComplete();
}
/**
* This sample performs a count, inserts data and performs a count again using reactive operator chaining.
*/
@Test
void shouldInsertAndCountData() {
var saveAndCount = repository.count() //
.doOnNext(System.out::println) //
.thenMany(repository.saveAll(Flux.just(new Person("Hank", "Schrader", 43), //
new Person("Mike", "Ehrmantraut", 62)))) //
.last() //
.flatMap(v -> repository.count()) //
.doOnNext(System.out::println);
StepVerifier.create(saveAndCount).expectNext(6L).verifyComplete();
}
/**
* Result set {@link com.datastax.driver.core.Row}s are converted to entities as they are emitted. Reactive pull and
* prefetch define the amount of fetched records.
*/
@Test
void shouldPerformConversionBeforeResultProcessing() {
StepVerifier.create(repository.findAll().doOnNext(System.out::println)) //
.expectNextCount(4) //
.verifyComplete();
}
/**
* Fetch data using query derivation.
*/
@Test
void shouldQueryDataWithQueryDerivation() {
StepVerifier.create(repository.findByLastname("White")).expectNextCount(2).verifyComplete();
}
/**
* Fetch data using a string query.
*/
@Test
void shouldQueryDataWithStringQuery() {
StepVerifier.create(repository.findByFirstnameInAndLastname("Walter", "White")).expectNextCount(1).verifyComplete();
}
/**
* Fetch data using query derivation.
*/
@Test
void shouldQueryDataWithDeferredQueryDerivation() {
StepVerifier.create(repository.findByLastname(Mono.just("White"))).expectNextCount(2).verifyComplete();
}
/**
* Fetch data using query derivation and deferred parameter resolution.
*/
@Test
void shouldQueryDataWithMixedDeferredQueryDerivation() {
StepVerifier.create(repository.findByFirstnameAndLastname(Mono.just("Walter"), "White")) //
.expectNextCount(1) //
.verifyComplete();
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.people;
import example.springdata.cassandra.util.CassandraKeyspace;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
/**
* Integration test for {@link RxJava2PersonRepository} using RxJava1 types. Note that
* {@link ReactiveCassandraOperations} is only available using Project Reactor types as the native Template API
* implementation does not come in multiple reactive flavors.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@SpringBootTest
public class RxJava2PersonRepositoryIntegrationTest {
@Autowired RxJava2PersonRepository repository;
@Autowired ReactiveCassandraOperations operations;
@BeforeEach
public void setUp() throws Exception {
var deleteAll = repository.deleteAll();
var save = repository.saveAll(Flowable.just(new Person("Walter", "White", 50), //
new Person("Skyler", "White", 45), //
new Person("Saul", "Goodman", 42), //
new Person("Jesse", "Pinkman", 27)));
deleteAll.andThen(save).test().await().assertNoErrors();
}
/**
* This sample performs a count, inserts data and performs a count again using reactive operator chaining. It prints
* the two counts ({@code 4} and {@code 6}) to the console.
*/
@Test
public void shouldInsertAndCountData() {
repository.count() //
.doOnSuccess(System.out::println) //
.toFlowable() //
.switchMap(count -> repository.saveAll(Flowable.just(new Person("Hank", "Schrader", 43), //
new Person("Mike", "Ehrmantraut", 62)))) //
.lastElement() //
.toSingle() //
.flatMap(v -> repository.count()) //
.doOnSuccess(System.out::println) //
.test() //
.awaitCount(1) //
.assertValue(6L) //
.assertNoErrors() //
.awaitTerminalEvent();
}
/**
* Result set {@link com.datastax.driver.core.Row}s are converted to entities as they are emitted. Reactive pull and
* prefetch define the amount of fetched records.
*/
@Test
public void shouldPerformConversionBeforeResultProcessing() {
repository.findAll() //
.doOnNext(System.out::println) //
.test() //
.awaitCount(4) //
.assertNoErrors() //
.awaitTerminalEvent();
}
/**
* Fetch data using query derivation.
*/
@Test
public void shouldQueryDataWithQueryDerivation() {
repository.findByLastname("White") //
.test() //
.awaitCount(2) //
.assertNoErrors() //
.awaitTerminalEvent();
}
/**
* Fetch data using a string query.
*/
@Test
public void shouldQueryDataWithStringQuery() {
repository.findByFirstnameAndLastname("Walter", "White") //
.test() //
.awaitCount(1) //
.assertNoErrors() //
.awaitTerminalEvent();
}
/**
* Fetch data using query derivation.
*/
@Test
public void shouldQueryDataWithDeferredQueryDerivation() {
repository.findByLastname(Single.just("White")) //
.test() //
.awaitCount(2) //
.assertNoErrors() //
.awaitTerminalEvent();
}
/**
* Fetch data using query derivation and deferred parameter resolution.
*/
@Test
public void shouldQueryDataWithMixedDeferredQueryDerivation() {
repository.findByFirstnameAndLastname(Single.just("Walter"), "White") //
.test() //
.awaitCount(1) //
.assertNoErrors() //
.awaitTerminalEvent();
}
}

View File

@ -0,0 +1,5 @@
/**
* Package showing usage of Spring Data Cassandra Reactive Repositories and reactive Cassandra template.
*/
package example.springdata.cassandra.people;

View File

@ -0,0 +1,67 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.spel;
import static org.assertj.core.api.Assertions.*;
import example.springdata.cassandra.spel.ApplicationConfiguration.Tenant;
import example.springdata.cassandra.util.CassandraKeyspace;
import reactor.test.StepVerifier;
import reactor.util.context.Context;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
/**
* Integration tests showing the SpEL context extension in action.
*
* @author Mark Paluch
*/
@CassandraKeyspace
@DataCassandraTest
class ExpressionIntegrationTests {
@Autowired EmployeeRepository employeeRepository;
@BeforeEach
void before() {
employeeRepository.deleteAll().as(StepVerifier::create).verifyComplete();
employeeRepository
.saveAll(Arrays.asList(new Employee("breaking-bad", "Walter"), new Employee("breaking-bad", "Hank"),
new Employee("south-park", "Hank"))) //
.as(StepVerifier::create) //
.expectNextCount(3) //
.verifyComplete();
}
@Test
void shouldFindByTenantIdAndName() {
employeeRepository.findAllByName("Walter") //
.contextWrite(Context.of(Tenant.class, new Tenant("breaking-bad"))).as(StepVerifier::create) //
.assertNext(actual -> {
assertThat(actual.tenantId()).isEqualTo("breaking-bad");
}).verifyComplete();
}
}

View File

@ -0,0 +1,5 @@
spring.data.cassandra.keyspace-name=example
spring.data.cassandra.schema-action=recreate
spring.data.cassandra.local-datacenter=datacenter1
logging.level.org.springframework.data.cassandra=INFO

45
cassandra/util/pom.xml Normal file
View File

@ -0,0 +1,45 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<name>Spring Data Cassandra - Example Utilities</name>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>cassandra</artifactId>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,114 @@
/*
* Copyright 2021-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.util;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.concurrent.Callable;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.testcontainers.containers.CassandraContainer;
import com.datastax.oss.driver.api.core.CqlSession;
/**
* JUnit 5 {@link BeforeAllCallback} extension to ensure a running Cassandra server.
*
* @author Mark Paluch
* @see CassandraKeyspace
*/
class CassandraExtension implements BeforeAllCallback {
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace
.create(CassandraExtension.class);
private static CassandraContainer container;
@Override
public void beforeAll(ExtensionContext context) {
var store = context.getStore(NAMESPACE);
var cassandra = findAnnotation(context);
var keyspace = store.getOrComputeIfAbsent(CassandraServer.class, it -> {
CassandraContainer container = runTestcontainer();
System.setProperty("spring.data.cassandra.port", "" + container.getMappedPort(9042));
System.setProperty("spring.data.cassandra.contact-points", "" + container.getHost());
return new CassandraServer(container.getHost(), container.getMappedPort(9042),
CassandraServer.RuntimeMode.EMBEDDED_IF_NOT_RUNNING);
}, CassandraServer.class);
keyspace.before();
Callable<CqlSession> sessionFactory = () -> CqlSession.builder()
.addContactPoint(new InetSocketAddress(keyspace.host(), keyspace.port())).withLocalDatacenter("datacenter1")
.build();
Awaitility.await().ignoreExceptions().untilAsserted(() -> {
sessionFactory.call().close();
});
var session = store.getOrComputeIfAbsent(CqlSession.class, it -> {
try {
return sessionFactory.call();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}, CqlSession.class);
session.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s \n"
+ "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", cassandra.keyspace()));
}
private static CassandraKeyspace findAnnotation(ExtensionContext context) {
var testClass = context.getRequiredTestClass();
var annotation = AnnotationUtils.findAnnotation(testClass, CassandraKeyspace.class);
return annotation.orElseThrow(() -> new IllegalStateException("Test class not annotated with @Cassandra"));
}
private CassandraContainer<?> runTestcontainer() {
if (container != null) {
return container;
}
container = new CassandraContainer<>(getCassandraDockerImageName());
container.withReuse(true);
container.start();
return container;
}
private String getCassandraDockerImageName() {
return String.format("cassandra:%s",
Optional.ofNullable(System.getenv("CASSANDRA_VERSION")).filter(StringUtils::hasText).orElse("3.11.10"));
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2021-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Annotation that can activates embedded Cassandra providing a keyspace at {@link #keyspace()}
*
* @author Mark Paluch
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith(CassandraExtension.class)
public @interface CassandraKeyspace {
/**
* Name of the desired keyspace to be provided.
*
* @return
*/
String keyspace() default "example";
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.util;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Assumptions;
import org.springframework.util.Assert;
/**
* Utility for Cassandra server use. This utility can start a Cassandra instance, reuse a running instance or simply
* require a running Cassandra server (will skip the test if Cassandra is not running).
*
* @author Mark Paluch
*/
record CassandraServer(String host, int port,
example.springdata.cassandra.util.CassandraServer.RuntimeMode runtimeMode) {
/**
* Require a running instance on {@code host:port}. Fails with {@link AssumptionViolatedException} if Cassandra is not
* running.
*
* @param host must not be {@literal null} or empty.
* @param port must be between 0 and 65535.
* @return the {@link CassandraServer} rule
*/
public static CassandraServer requireRunningInstance(String host, int port) {
return new CassandraServer(host, port, RuntimeMode.REQUIRE_RUNNING_INSTANCE);
}
/**
* Start an embedded Cassandra instance on {@code host:port} if Cassandra is not running already.
*
* @param host must not be {@literal null} or empty.
* @param port must be between 0 and 65535.
* @return the {@link CassandraServer} rule
*/
public static CassandraServer embeddedIfNotRunning(String host, int port) {
return new CassandraServer(host, port, RuntimeMode.EMBEDDED_IF_NOT_RUNNING);
}
/**
* @param host must not be {@literal null} or empty.
* @param port
* @return {@literal true} if the TCP port accepts a connection.
*/
public static boolean isConnectable(String host, int port) {
Assert.hasText(host, "Host must not be null or empty!");
try (var socket = new Socket()) {
socket.setSoLinger(true, 0);
socket.connect(new InetSocketAddress(host, port), (int) TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS));
return true;
} catch (Exception e) {
return false;
}
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
protected void before() {
if (runtimeMode == RuntimeMode.REQUIRE_RUNNING_INSTANCE) {
Assumptions.assumeTrue(isConnectable(getHost(), getPort()),
() -> String.format("Cassandra is not reachable at %s:%s.", getHost(), getPort()));
}
if (runtimeMode == RuntimeMode.EMBEDDED_IF_NOT_RUNNING) {
if (isConnectable(getHost(), getPort())) {
return;
}
}
}
enum RuntimeMode {
REQUIRE_RUNNING_INSTANCE, EMBEDDED_IF_NOT_RUNNING;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra.util;
import lombok.experimental.UtilityClass;
import org.springframework.data.util.Version;
import org.springframework.util.Assert;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
/**
* Utility to retrieve the Cassandra release version.
*
* @author Mark Paluch
*/
@UtilityClass
public class CassandraVersion {
/**
* Retrieve the Cassandra release version.
*
* @param session must not be {@literal null}.
* @return the release {@link Version}.
*/
public static Version getReleaseVersion(CqlSession session) {
Assert.notNull(session, "Session must not be null");
var resultSet = session.execute("SELECT release_version FROM system.local;");
var row = resultSet.one();
return Version.parse(row.getString(0));
}
}

View File

@ -0,0 +1,234 @@
# Configuration for the DataStax Java driver for Apache Cassandra®.
#
# Unless you use a custom mechanism to load your configuration (see
# SessionBuilder.withConfigLoader), all the values declared here will be used as defaults. You can
# place your own `application.conf` in the classpath to override them.
#
# Options are classified into two categories:
# - basic: what is most likely to be customized first when kickstarting a new application.
# - advanced: more elaborate tuning options, or "expert"-level customizations.
#
# This file is in HOCON format, see https://github.com/typesafehub/config/blob/master/HOCON.md.
datastax-java-driver {
basic.load-balancing-policy {
class = DcInferringLoadBalancingPolicy
}
basic.request {
# How long the driver waits for a request to complete. This is a global limit on the duration of
# a session.execute() call, including any internal retries the driver might do.
#
# By default, this value is set pretty high to ensure that DDL queries don't time out, in order
# to provide the best experience for new users trying the driver with the out-of-the-box
# configuration.
# For any serious deployment, we recommend that you use separate configuration profiles for DDL
# and DML; you can then set the DML timeout much lower (down to a few milliseconds if needed).
#
# Note that, because timeouts are scheduled on the driver's timer thread, the duration specified
# here must be greater than the timer tick duration defined by the
# advanced.netty.timer.tick-duration setting (see below). If that is not the case, timeouts will
# not be triggered as timely as desired.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for requests issued after the change.
# Overridable in a profile: yes
timeout = 8 seconds
}
# ADVANCED OPTIONS -------------------------------------------------------------------------------
advanced.connection {
# The timeout to use for internal queries that run as part of the initialization process, just
# after we open a connection. If this timeout fires, the initialization of the connection will
# fail. If this is the first connection ever, the driver will fail to initialize as well,
# otherwise it will retry the connection later.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for connections created after the
# change.
# Overridable in a profile: no
init-query-timeout = 500 milliseconds
# The driver maintains a connection pool to each node, according to the distance assigned to it
# by the load balancing policy. If the distance is IGNORED, no connections are maintained.
pool {
local {
# The number of connections in the pool.
#
# Required: yes
# Modifiable at runtime: yes; when the change is detected, all active pools will be notified
# and will adjust their size.
# Overridable in a profile: no
size = 1
}
remote {
size = 1
}
}
}
advanced.metrics {
# The session-level metrics (all disabled by default).
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
session {
enabled = []
}
# The node-level metrics (all disabled by default).
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
node {
enabled = []
}
}
advanced.control-connection {
schema-agreement {
# The interval between each attempt.
# Required: yes
# Modifiable at runtime: yes, the new value will be used for checks issued after the change.
# Overridable in a profile: no
interval = 100 seconds
# The timeout after which schema agreement fails.
# If this is set to 0, schema agreement is skipped and will always fail.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for checks issued after the change.
# Overridable in a profile: no
timeout = 100 seconds
# Whether to log a warning if schema agreement fails.
# You might want to change this if you've set the timeout to 0.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for checks issued after the change.
# Overridable in a profile: no
warn-on-failure = true
}
}
# Options related to the Netty event loop groups used internally by the driver.
advanced.netty {
# Whether the threads created by the driver should be daemon threads.
# This will apply to the threads in io-group, admin-group, and the timer thread.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
daemon = false
# The event loop group used for I/O operations (reading and writing to Cassandra nodes).
# By default, threads in this group are named after the session name, "-io-" and an incrementing
# counter, for example "s0-io-0".
io-group {
# The number of threads.
# If this is set to 0, the driver will use `Runtime.getRuntime().availableProcessors() * 2`.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
size = 4
# The options to shut down the event loop group gracefully when the driver closes. If a task
# gets submitted during the quiet period, it is accepted and the quiet period starts over.
# The timeout limits the overall shutdown time.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
shutdown {quiet-period = 0, timeout = 0, unit = SECONDS}
}
# The event loop group used for admin tasks not related to request I/O (handle cluster events,
# refresh metadata, schedule reconnections, etc.)
# By default, threads in this group are named after the session name, "-admin-" and an
# incrementing counter, for example "s0-admin-0".
admin-group {
size = 2
shutdown {quiet-period = 0, timeout = 0, unit = SECONDS}
}
}
advanced.metadata {
# Topology events are external signals that inform the driver of the state of Cassandra nodes
# (by default, they correspond to gossip events received on the control connection).
# The debouncer helps smoothen out oscillations if conflicting events are sent out in short
# bursts.
# Debouncing may be disabled by setting the window to 0 or max-events to 1 (this is not
# recommended).
topology-event-debouncer {
# How long the driver waits to propagate an event. If another event is received within that
# time, the window is reset and a batch of accumulated events will be delivered.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
window = 0 second
# The maximum number of events that can accumulate. If this count is reached, the events are
# delivered immediately and the time window is reset. This avoids holding events indefinitely
# if the window keeps getting reset.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
max-events = 20
}
# Options relating to schema metadata (Cluster.getMetadata.getKeyspaces).
# This metadata is exposed by the driver for informational purposes, and is also necessary for
# token-aware routing.
schema {
# Whether schema metadata is enabled.
# If this is false, the schema will remain empty, or to the last known value.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for refreshes issued after the
# change. It can also be overridden programmatically via Cluster.setSchemaMetadataEnabled.
# Overridable in a profile: no
enabled = true
# Protects against bursts of schema updates (for example when a client issues a sequence of
# DDL queries), by coalescing them into a single update.
# Debouncing may be disabled by setting the window to 0 or max-events to 1 (this is highly
# discouraged for schema refreshes).
debouncer {
# How long the driver waits to apply a refresh. If another refresh is requested within that
# time, the window is reset and a single refresh will be triggered when it ends.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
window = 0 second
# The maximum number of refreshes that can accumulate. If this count is reached, a refresh
# is done immediately and the window is reset.
#
# Required: yes
# Modifiable at runtime: no
# Overridable in a profile: no
max-events = 20
}
}
# Whether token metadata (Cluster.getMetadata.getTokenMap) is enabled.
# This metadata is exposed by the driver for informational purposes, and is also necessary for
# token-aware routing.
# If this is false, it will remain empty, or to the last known value. Note that its computation
# requires information about the schema; therefore if schema metadata is disabled or filtered to
# a subset of keyspaces, the token map will be incomplete, regardless of the value of this
# property.
#
# Required: yes
# Modifiable at runtime: yes, the new value will be used for refreshes issued after the change.
# Overridable in a profile: no
token-map.enabled = true
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.data" level="info" />
<logger name="org.apache.cassandra" level="error" />
<logger name="com.datastax.driver.core" level="error" />
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>

View File

@ -0,0 +1,10 @@
# Spring Data Couchbase - Examples
This project contains samples of data access features with Spring Data (Couchbase).
## Prerequisites
The examples require a running [Couchbase Server](https://www.couchbase.com/downloads) server with the travel sample bucket imported. We assume you're running Couchbase 6.5 and we have updated the `application.properties` class accordingly to adapt RBAC authentication.
For more information, see the [official documentation](https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/#reference).

27
couchbase/example/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-data-couchbase-examples</artifactId>
<groupId>org.springframework.data.examples</groupId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-couchbase-example</artifactId>
<name>Basic sample for Spring Data Couchbase</name>
<description>Small sample project showing the usage of Spring Data Couchbase.</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-couchbase-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,47 @@
/*
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.couchbase.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* A domain object representing an Airline
*
* @author Chandana Kithalagama
*/
@Data
@Document
@JsonIgnoreProperties(ignoreUnknown = true)
public class Airline {
private @Id String id;
private String name;
private String iata;
private String icao;
private String callsign;
private String country;
}

Some files were not shown because too many files have changed in this diff Show More