From 746541447cb1d2a308bde2b12d65e937693a2f10 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 29 Aug 2023 13:36:24 +0200 Subject: [PATCH] Migrate docs to Antora. Closes #1427 --- .gitignore | 5 + spring-data-cassandra-distribution/pom.xml | 30 +- src/main/antora/antora-playbook.yml | 42 + src/main/antora/antora.yml | 12 + .../modules/ROOT/examples}/AppConfig.java | 0 .../ROOT/examples}/CassandraApplication.java | 0 .../ROOT/examples}/CassandraConfig.java | 0 .../examples}/CassandraConfiguration.java | 0 .../examples}/CassandraTemplateExamples.java | 0 .../examples}/CassandraTemplateProducer.java | 0 .../examples}/ConverterConfiguration.java | 0 .../ROOT/examples}/CqlTemplateExamples.java | 0 .../CreateKeyspaceConfiguration.java | 0 .../EntityBasePackagesConfiguration.java | 0 .../ROOT/examples}/FactoryBeanAppConfig.java | 0 .../KeyspacePopulatorConfiguration.java | 0 ...KeyspacePopulatorFailureConfiguration.java | 0 .../modules/ROOT/examples}/LoginEvent.java | 0 .../modules/ROOT/examples}/LoginEventKey.java | 0 .../NamingStrategyConfiguration.java | 0 .../antora/modules/ROOT/examples}/Person.java | 0 .../ROOT/examples}/PersonReadConverter.java | 0 .../ROOT/examples}/PersonWriteConverter.java | 0 .../ReactiveCassandraApplication.java | 0 .../ReactiveCassandraConfiguration.java | 0 .../ReactiveCassandraTemplateExamples.java | 49 + .../ReactiveCqlTemplateExamples.java | 11 +- .../ROOT/examples}/RepositoryClient.java | 0 .../ROOT/examples}/SchemaConfiguration.java | 0 ...essionFactoryInitializerConfiguration.java | 0 .../ROOT/examples}/mapping/Address.java | 0 .../examples}/mapping/BeforeSaveListener.java | 0 .../ROOT/examples}/mapping/Coordinates.java | 0 .../ROOT/examples}/mapping/Person.java | 0 .../mapping/PersonReadConverter.java | 0 .../examples}/mapping/PersonWithIndexes.java | 0 src/main/antora/modules/ROOT/nav.adoc | 45 + .../antora/modules/ROOT/pages/cassandra.adoc | 17 + .../ROOT/pages/cassandra/auditing.adoc} | 19 +- .../ROOT/pages/cassandra/configuration.adoc | 168 +++ .../ROOT/pages/cassandra}/converters.adoc | 10 +- .../ROOT/pages/cassandra/cql-template.adoc | 287 ++++ .../modules/ROOT/pages/cassandra/events.adoc | 68 + .../ROOT/pages/cassandra/getting-started.adoc | 103 ++ .../ROOT/pages/cassandra}/introduction.adoc | 14 +- .../pages/cassandra/prepared-statements.adoc | 104 ++ .../pages/cassandra/reactive-cassandra.adoc | 36 + .../repositories/cdi-integration.adoc | 25 + .../cassandra/repositories/query-methods.adoc | 240 ++++ .../cassandra/repositories/repositories.adoc | 246 ++++ .../pages/cassandra/schema-management.adoc | 253 ++++ .../ROOT/pages/cassandra/template.adoc | 567 ++++++++ .../modules/ROOT/pages/commons/upgrade.adoc | 1 + src/main/antora/modules/ROOT/pages/index.adoc | 22 + .../antora/modules/ROOT/pages/kotlin.adoc | 1 + .../modules/ROOT/pages/kotlin/coroutines.adoc | 1 + .../ROOT/pages/kotlin/extensions.adoc} | 6 +- .../ROOT/pages/kotlin/null-safety.adoc | 1 + .../ROOT/pages/kotlin/object-mapping.adoc | 1 + .../ROOT/pages/kotlin/requirements.adoc | 1 + .../migration-guide-1.5-to-2.0.adoc | 11 +- .../migration-guide-2.2-to-3.0.adoc | 20 +- .../migration-guide-3.0-to-4.0.adoc | 3 +- .../modules/ROOT/pages/migration-guides.adoc | 8 + .../modules/ROOT/pages/object-mapping.adoc} | 65 +- .../modules/ROOT/pages}/observability.adoc | 14 +- .../pages/observability/conventions.adoc} | 2 +- .../ROOT/pages/observability/metrics.adoc} | 4 +- .../ROOT/pages/observability/spans.adoc} | 4 +- .../modules/ROOT/pages}/preface.adoc | 1 + .../modules/ROOT/pages/repositories.adoc | 8 + .../pages/repositories/core-concepts.adoc | 1 + .../repositories/core-domain-events.adoc | 1 + .../pages/repositories/core-extensions.adoc | 1 + .../pages/repositories/create-instances.adoc | 1 + .../repositories/custom-implementations.adoc | 1 + .../ROOT/pages/repositories/definition.adoc | 1 + .../pages/repositories/null-handling.adoc | 1 + .../ROOT/pages/repositories/projections.adoc | 4 + .../query-keywords-reference.adoc | 1 + .../repositories/query-methods-details.adoc | 1 + .../query-return-types-reference.adoc | 1 + .../resources/antora-resources/antora.yml | 20 + src/main/asciidoc/images/epub-cover.png | Bin 52562 -> 0 bytes src/main/asciidoc/images/epub-cover.svg | 10 - src/main/asciidoc/index.adoc | 44 - .../reference/cassandra-entity-callbacks.adoc | 30 - .../reference/cassandra-repositories.adoc | 368 ----- src/main/asciidoc/reference/cassandra.adoc | 1212 ----------------- .../reference/exception-translation.adoc | 10 - .../asciidoc/reference/migration-guides.adoc | 8 - .../reactive-cassandra-repositories.adoc | 181 --- .../reference/reactive-cassandra.adoc | 441 ------ src/main/asciidoc/reference/upgrade.adoc | 9 - 94 files changed, 2458 insertions(+), 2414 deletions(-) create mode 100644 src/main/antora/antora-playbook.yml create mode 100644 src/main/antora/antora.yml rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/AppConfig.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CassandraApplication.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CassandraConfig.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CassandraConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CassandraTemplateExamples.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CassandraTemplateProducer.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/ConverterConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CqlTemplateExamples.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/CreateKeyspaceConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/EntityBasePackagesConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/FactoryBeanAppConfig.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/KeyspacePopulatorConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/KeyspacePopulatorFailureConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/LoginEvent.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/LoginEventKey.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/NamingStrategyConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/Person.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/PersonReadConverter.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/PersonWriteConverter.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/ReactiveCassandraApplication.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/ReactiveCassandraConfiguration.java (100%) create mode 100644 src/main/antora/modules/ROOT/examples/ReactiveCassandraTemplateExamples.java rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/ReactiveCqlTemplateExamples.java (90%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/RepositoryClient.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/SchemaConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/SessionFactoryInitializerConfiguration.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/Address.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/BeforeSaveListener.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/Coordinates.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/Person.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/PersonReadConverter.java (100%) rename {spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example => src/main/antora/modules/ROOT/examples}/mapping/PersonWithIndexes.java (100%) create mode 100644 src/main/antora/modules/ROOT/nav.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra.adoc rename src/main/{asciidoc/reference/cassandra-auditing.adoc => antora/modules/ROOT/pages/cassandra/auditing.adoc} (89%) create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/configuration.adoc rename src/main/{asciidoc/reference => antora/modules/ROOT/pages/cassandra}/converters.adoc (86%) create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/cql-template.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/events.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/getting-started.adoc rename src/main/{asciidoc/reference => antora/modules/ROOT/pages/cassandra}/introduction.adoc (69%) create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/prepared-statements.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/reactive-cassandra.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/repositories/cdi-integration.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/repositories/query-methods.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/repositories/repositories.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/schema-management.adoc create mode 100644 src/main/antora/modules/ROOT/pages/cassandra/template.adoc create mode 100644 src/main/antora/modules/ROOT/pages/commons/upgrade.adoc create mode 100644 src/main/antora/modules/ROOT/pages/index.adoc create mode 100644 src/main/antora/modules/ROOT/pages/kotlin.adoc create mode 100644 src/main/antora/modules/ROOT/pages/kotlin/coroutines.adoc rename src/main/{asciidoc/reference/kotlin.adoc => antora/modules/ROOT/pages/kotlin/extensions.adoc} (82%) create mode 100644 src/main/antora/modules/ROOT/pages/kotlin/null-safety.adoc create mode 100644 src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc create mode 100644 src/main/antora/modules/ROOT/pages/kotlin/requirements.adoc rename src/main/{asciidoc/reference => antora/modules/ROOT/pages/migration-guide}/migration-guide-1.5-to-2.0.adoc (92%) rename src/main/{asciidoc/reference => antora/modules/ROOT/pages/migration-guide}/migration-guide-2.2-to-3.0.adoc (96%) rename src/main/{asciidoc/reference => antora/modules/ROOT/pages/migration-guide}/migration-guide-3.0-to-4.0.adoc (91%) create mode 100644 src/main/antora/modules/ROOT/pages/migration-guides.adoc rename src/main/{asciidoc/reference/mapping.adoc => antora/modules/ROOT/pages/object-mapping.adoc} (83%) rename src/main/{asciidoc/reference => antora/modules/ROOT/pages}/observability.adoc (90%) rename src/main/{asciidoc/observability/_conventions.adoc => antora/modules/ROOT/pages/observability/conventions.adoc} (92%) rename src/main/{asciidoc/observability/_metrics.adoc => antora/modules/ROOT/pages/observability/metrics.adoc} (95%) rename src/main/{asciidoc/observability/_spans.adoc => antora/modules/ROOT/pages/observability/spans.adoc} (95%) rename src/main/{asciidoc => antora/modules/ROOT/pages}/preface.adoc (99%) create mode 100644 src/main/antora/modules/ROOT/pages/repositories.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/core-domain-events.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/create-instances.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/definition.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/null-handling.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/projections.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/query-keywords-reference.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc create mode 100644 src/main/antora/modules/ROOT/pages/repositories/query-return-types-reference.adoc create mode 100644 src/main/antora/resources/antora-resources/antora.yml delete mode 100644 src/main/asciidoc/images/epub-cover.png delete mode 100644 src/main/asciidoc/images/epub-cover.svg delete mode 100644 src/main/asciidoc/index.adoc delete mode 100644 src/main/asciidoc/reference/cassandra-entity-callbacks.adoc delete mode 100644 src/main/asciidoc/reference/cassandra-repositories.adoc delete mode 100644 src/main/asciidoc/reference/cassandra.adoc delete mode 100644 src/main/asciidoc/reference/exception-translation.adoc delete mode 100644 src/main/asciidoc/reference/migration-guides.adoc delete mode 100644 src/main/asciidoc/reference/reactive-cassandra-repositories.adoc delete mode 100644 src/main/asciidoc/reference/reactive-cassandra.adoc delete mode 100644 src/main/asciidoc/reference/upgrade.adoc diff --git a/.gitignore b/.gitignore index bfbc40e72..6dd8dd7e6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,9 @@ build .idea download work +build/ +node_modules +node +package.json +package-lock.json diff --git a/spring-data-cassandra-distribution/pom.xml b/spring-data-cassandra-distribution/pom.xml index 4a3caf7c6..3d1509451 100644 --- a/spring-data-cassandra-distribution/pom.xml +++ b/spring-data-cassandra-distribution/pom.xml @@ -15,24 +15,40 @@ spring-data-cassandra-distribution pom - Spring Data for Apache Cassandra - Distribution - Distribution build for Spring Data for Apache Cassandra - https://github.com/spring-projects/spring-data-cassandra/tree/master/spring-data-cassandra-distribution - ${basedir}/.. - SDCASS + ${project.basedir}/../src/main/antora/antora-playbook.yml + + + ${project.basedir}/../src/main/antora/resources/antora-resources + true + + + + org.apache.maven.plugins + maven-resources-plugin + + + + resources + + + + + + org.apache.maven.plugins maven-assembly-plugin + - org.asciidoctor - asciidoctor-maven-plugin + io.spring.maven.antora + antora-maven-plugin diff --git a/src/main/antora/antora-playbook.yml b/src/main/antora/antora-playbook.yml new file mode 100644 index 000000000..f20a13582 --- /dev/null +++ b/src/main/antora/antora-playbook.yml @@ -0,0 +1,42 @@ +# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2 +# +# The purpose of this Antora playbook is to build the docs in the current branch. +antora: + extensions: + - '@antora/collector-extension' + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: 'data-cassandra' +site: + title: Spring Data Cassandra + url: https://docs.spring.io/spring-data-cassandra/reference/ +content: + sources: + - url: ./../../.. + branches: HEAD + start_path: src/main/antora + worktrees: true + - url: https://github.com/spring-projects/spring-data-commons + # Refname matching: + # https://docs.antora.org/antora/latest/playbook/content-refname-matching/ + branches: [ main, 3.2.x ] + start_path: src/main/antora +asciidoc: + attributes: + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + chomp: 'all' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + sourcemap: true +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn + format: pretty +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip + snapshot: true diff --git a/src/main/antora/antora.yml b/src/main/antora/antora.yml new file mode 100644 index 000000000..3687456a7 --- /dev/null +++ b/src/main/antora/antora.yml @@ -0,0 +1,12 @@ +name: data-cassandra +version: true +title: Spring Data Cassandra +nav: + - modules/ROOT/nav.adoc +ext: + collector: + - run: + command: ./mvnw validate process-resources -pl :spring-data-cassandra-distribution -am -Pantora-process-resources + local: true + scan: + dir: spring-data-cassandra-distribution/target/classes/ diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/AppConfig.java b/src/main/antora/modules/ROOT/examples/AppConfig.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/AppConfig.java rename to src/main/antora/modules/ROOT/examples/AppConfig.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraApplication.java b/src/main/antora/modules/ROOT/examples/CassandraApplication.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraApplication.java rename to src/main/antora/modules/ROOT/examples/CassandraApplication.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraConfig.java b/src/main/antora/modules/ROOT/examples/CassandraConfig.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraConfig.java rename to src/main/antora/modules/ROOT/examples/CassandraConfig.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraConfiguration.java b/src/main/antora/modules/ROOT/examples/CassandraConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraConfiguration.java rename to src/main/antora/modules/ROOT/examples/CassandraConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraTemplateExamples.java b/src/main/antora/modules/ROOT/examples/CassandraTemplateExamples.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraTemplateExamples.java rename to src/main/antora/modules/ROOT/examples/CassandraTemplateExamples.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraTemplateProducer.java b/src/main/antora/modules/ROOT/examples/CassandraTemplateProducer.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CassandraTemplateProducer.java rename to src/main/antora/modules/ROOT/examples/CassandraTemplateProducer.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ConverterConfiguration.java b/src/main/antora/modules/ROOT/examples/ConverterConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ConverterConfiguration.java rename to src/main/antora/modules/ROOT/examples/ConverterConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CqlTemplateExamples.java b/src/main/antora/modules/ROOT/examples/CqlTemplateExamples.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CqlTemplateExamples.java rename to src/main/antora/modules/ROOT/examples/CqlTemplateExamples.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CreateKeyspaceConfiguration.java b/src/main/antora/modules/ROOT/examples/CreateKeyspaceConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/CreateKeyspaceConfiguration.java rename to src/main/antora/modules/ROOT/examples/CreateKeyspaceConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/EntityBasePackagesConfiguration.java b/src/main/antora/modules/ROOT/examples/EntityBasePackagesConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/EntityBasePackagesConfiguration.java rename to src/main/antora/modules/ROOT/examples/EntityBasePackagesConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/FactoryBeanAppConfig.java b/src/main/antora/modules/ROOT/examples/FactoryBeanAppConfig.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/FactoryBeanAppConfig.java rename to src/main/antora/modules/ROOT/examples/FactoryBeanAppConfig.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/KeyspacePopulatorConfiguration.java b/src/main/antora/modules/ROOT/examples/KeyspacePopulatorConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/KeyspacePopulatorConfiguration.java rename to src/main/antora/modules/ROOT/examples/KeyspacePopulatorConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/KeyspacePopulatorFailureConfiguration.java b/src/main/antora/modules/ROOT/examples/KeyspacePopulatorFailureConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/KeyspacePopulatorFailureConfiguration.java rename to src/main/antora/modules/ROOT/examples/KeyspacePopulatorFailureConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/LoginEvent.java b/src/main/antora/modules/ROOT/examples/LoginEvent.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/LoginEvent.java rename to src/main/antora/modules/ROOT/examples/LoginEvent.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/LoginEventKey.java b/src/main/antora/modules/ROOT/examples/LoginEventKey.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/LoginEventKey.java rename to src/main/antora/modules/ROOT/examples/LoginEventKey.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/NamingStrategyConfiguration.java b/src/main/antora/modules/ROOT/examples/NamingStrategyConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/NamingStrategyConfiguration.java rename to src/main/antora/modules/ROOT/examples/NamingStrategyConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/Person.java b/src/main/antora/modules/ROOT/examples/Person.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/Person.java rename to src/main/antora/modules/ROOT/examples/Person.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/PersonReadConverter.java b/src/main/antora/modules/ROOT/examples/PersonReadConverter.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/PersonReadConverter.java rename to src/main/antora/modules/ROOT/examples/PersonReadConverter.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/PersonWriteConverter.java b/src/main/antora/modules/ROOT/examples/PersonWriteConverter.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/PersonWriteConverter.java rename to src/main/antora/modules/ROOT/examples/PersonWriteConverter.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCassandraApplication.java b/src/main/antora/modules/ROOT/examples/ReactiveCassandraApplication.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCassandraApplication.java rename to src/main/antora/modules/ROOT/examples/ReactiveCassandraApplication.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCassandraConfiguration.java b/src/main/antora/modules/ROOT/examples/ReactiveCassandraConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCassandraConfiguration.java rename to src/main/antora/modules/ROOT/examples/ReactiveCassandraConfiguration.java diff --git a/src/main/antora/modules/ROOT/examples/ReactiveCassandraTemplateExamples.java b/src/main/antora/modules/ROOT/examples/ReactiveCassandraTemplateExamples.java new file mode 100644 index 000000000..731de6c38 --- /dev/null +++ b/src/main/antora/modules/ROOT/examples/ReactiveCassandraTemplateExamples.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020-2023 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 org.springframework.data.cassandra.example; + +import static org.springframework.data.cassandra.core.query.Criteria.*; +import static org.springframework.data.cassandra.core.query.Query.*; + +import org.springframework.data.cassandra.core.CassandraTemplate; + +import com.datastax.oss.driver.api.core.cql.SimpleStatement; + +/** + * @author Mark Paluch + */ +// @formatter:off +public class ReactiveCassandraTemplateExamples { + + private ReactiveCassandraTemplate template = null; + + void examples() { + // tag::preparedStatement[] + template.setUsePreparedStatements(true); + + Mono actorByQuery = template.selectOne(query(where("id").is(42)), Actor.class); + + Mono actorByStatement = template.selectOne( + SimpleStatement.newInstance("SELECT id, name FROM actor WHERE id = ?", 42), + Actor.class); + // end::preparedStatement[] + } + + static class Actor { + + } + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCqlTemplateExamples.java b/src/main/antora/modules/ROOT/examples/ReactiveCqlTemplateExamples.java similarity index 90% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCqlTemplateExamples.java rename to src/main/antora/modules/ROOT/examples/ReactiveCqlTemplateExamples.java index 5ea2bf081..479257704 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/ReactiveCqlTemplateExamples.java +++ b/src/main/antora/modules/ROOT/examples/ReactiveCqlTemplateExamples.java @@ -1,11 +1,11 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2023 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 + * 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, @@ -73,6 +73,13 @@ public class ReactiveCqlTemplateExamples { } }); // end::listOfRowMapper[] + + // tag::preparedStatement[] + Flux lastNames = reactiveCqlTemplate.query( + session -> session.prepare("SELECT last_name FROM t_actor WHERE id = ?"), + ps -> ps.bind(1212L), + (row, rowNum) -> row.getString(0)); + // end::preparedStatement[] } // tag::findAllActors[] diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/RepositoryClient.java b/src/main/antora/modules/ROOT/examples/RepositoryClient.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/RepositoryClient.java rename to src/main/antora/modules/ROOT/examples/RepositoryClient.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/SchemaConfiguration.java b/src/main/antora/modules/ROOT/examples/SchemaConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/SchemaConfiguration.java rename to src/main/antora/modules/ROOT/examples/SchemaConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/SessionFactoryInitializerConfiguration.java b/src/main/antora/modules/ROOT/examples/SessionFactoryInitializerConfiguration.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/SessionFactoryInitializerConfiguration.java rename to src/main/antora/modules/ROOT/examples/SessionFactoryInitializerConfiguration.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Address.java b/src/main/antora/modules/ROOT/examples/mapping/Address.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Address.java rename to src/main/antora/modules/ROOT/examples/mapping/Address.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/BeforeSaveListener.java b/src/main/antora/modules/ROOT/examples/mapping/BeforeSaveListener.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/BeforeSaveListener.java rename to src/main/antora/modules/ROOT/examples/mapping/BeforeSaveListener.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Coordinates.java b/src/main/antora/modules/ROOT/examples/mapping/Coordinates.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Coordinates.java rename to src/main/antora/modules/ROOT/examples/mapping/Coordinates.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Person.java b/src/main/antora/modules/ROOT/examples/mapping/Person.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/Person.java rename to src/main/antora/modules/ROOT/examples/mapping/Person.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/PersonReadConverter.java b/src/main/antora/modules/ROOT/examples/mapping/PersonReadConverter.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/PersonReadConverter.java rename to src/main/antora/modules/ROOT/examples/mapping/PersonReadConverter.java diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/PersonWithIndexes.java b/src/main/antora/modules/ROOT/examples/mapping/PersonWithIndexes.java similarity index 100% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example/mapping/PersonWithIndexes.java rename to src/main/antora/modules/ROOT/examples/mapping/PersonWithIndexes.java diff --git a/src/main/antora/modules/ROOT/nav.adoc b/src/main/antora/modules/ROOT/nav.adoc new file mode 100644 index 000000000..5ecc189dd --- /dev/null +++ b/src/main/antora/modules/ROOT/nav.adoc @@ -0,0 +1,45 @@ +* xref:index.adoc[Overview] +** xref:commons/upgrade.adoc[] + +* xref:cassandra.adoc[] +** xref:cassandra/getting-started.adoc[] +** xref:cassandra/configuration.adoc[] +** xref:cassandra/schema-management.adoc[] +** xref:cassandra/cql-template.adoc[] +** xref:cassandra/reactive-cassandra.adoc[] +** xref:cassandra/template.adoc[] +** xref:cassandra/prepared-statements.adoc[] +** xref:object-mapping.adoc[] +** xref:cassandra/converters.adoc[] +** xref:cassandra/events.adoc[] +** xref:cassandra/auditing.adoc[] + +* xref:repositories.adoc[] +** xref:repositories/core-concepts.adoc[] +** xref:repositories/definition.adoc[] +** xref:cassandra/repositories/repositories.adoc[] +** xref:repositories/create-instances.adoc[] +** xref:repositories/query-methods-details.adoc[] +** xref:cassandra/repositories/query-methods.adoc[] +** xref:repositories/projections.adoc[] +** xref:repositories/custom-implementations.adoc[] +** xref:repositories/core-domain-events.adoc[] +** xref:repositories/null-handling.adoc[] +** xref:cassandra/repositories/cdi-integration.adoc[] +** xref:repositories/query-keywords-reference.adoc[] +** xref:repositories/query-return-types-reference.adoc[] + +* xref:observability.adoc[] + +* xref:kotlin.adoc[] +** xref:kotlin/requirements.adoc[] +** xref:kotlin/null-safety.adoc[] +** xref:kotlin/object-mapping.adoc[] +** xref:kotlin/extensions.adoc[] +** xref:kotlin/coroutines.adoc[] + +* xref:migration-guides.adoc[] +** xref:migration-guide/migration-guide-1.5-to-2.0.adoc[] +** xref:migration-guide/migration-guide-2.2-to-3.0.adoc[] +** xref:migration-guide/migration-guide-3.0-to-4.0.adoc[] +* https://github.com/spring-projects/spring-data-commons/wiki[Wiki] diff --git a/src/main/antora/modules/ROOT/pages/cassandra.adoc b/src/main/antora/modules/ROOT/pages/cassandra.adoc new file mode 100644 index 000000000..940516391 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra.adoc @@ -0,0 +1,17 @@ +[[cassandra.core]] += Cassandra Support +:page-section-summary-toc: 1 + +Spring Data support for Apache Cassandra contains a wide range of features: + +* Spring configuration support with Java-based `@Configuration` classes or the XML namespace. +* The `CqlTemplate`, `AsyncCqlTemplate`, and `ReactiveCqlTemplate` helper classes that increases productivity by properly handling common Cassandra data access operations. +* The `CassandraTemplate`, `AsyncCassandraTemplate`, and `ReactiveCassandraTemplate` helper classes that provide object mapping between CQL Tables and POJOs. +* Exception translation into Spring's portable {springDocsUrl}data-access.html#dao-exceptions[Data Access Exception Hierarchy]. +* Feature rich object mapping integrated with _Spring's_ {springDocsUrl}core.html#core-convert[Conversion Service]. +* Annotation-based mapping metadata that is extensible to support other metadata formats. +* Java-based query, criteria, and update DSLs. +* Automatic implementation of imperative and reactive `Repository` interfaces including support for custom finder methods. + +For most data-oriented tasks, you can use the `[Reactive|Async]CassandraTemplate` or the `Repository` support, both of which use the rich object-mapping functionality. `[Reactive|Async]CqlTemplate` is commonly used to increment counters or perform ad-hoc CRUD operations. `[Reactive|Async]CqlTemplate` also provides callback methods that make it easy to get low-level API objects, such as `com.datastax.oss.driver.api.core.CqlSession`, which lets you communicate directly with Cassandra. +Spring Data for Apache Cassandra uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are familiar and so that you can map your existing knowledge onto the Spring APIs. diff --git a/src/main/asciidoc/reference/cassandra-auditing.adoc b/src/main/antora/modules/ROOT/pages/cassandra/auditing.adoc similarity index 89% rename from src/main/asciidoc/reference/cassandra-auditing.adoc rename to src/main/antora/modules/ROOT/pages/cassandra/auditing.adoc index 5791f3677..2b50eda2c 100644 --- a/src/main/asciidoc/reference/cassandra-auditing.adoc +++ b/src/main/antora/modules/ROOT/pages/cassandra/auditing.adoc @@ -1,12 +1,14 @@ [[cassandra.auditing]] -== General Auditing Configuration for Cassandra += Auditing Configuration for Cassandra To activate auditing functionality, create a configuration as the following example shows: -.Activating auditing by using XML configuration -==== -.Java -[source,java,role="primary"] +.Activating auditing through configuration +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- @Configuration @EnableCassandraAuditing @@ -19,8 +21,9 @@ class Config { } ---- -.XML -[source,xml,role="secondary"] +XML:: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ---- ---- -==== +====== If you expose a bean of type `AuditorAware` to the `ApplicationContext`, the auditing infrastructure picks it up automatically and uses it to determine the current user to be set on domain types. If you have multiple implementations registered in the `ApplicationContext`, you can select the one to be used by explicitly setting the `auditorAwareRef` attribute of `@EnableCassandraAuditing`. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/configuration.adoc b/src/main/antora/modules/ROOT/pages/cassandra/configuration.adoc new file mode 100644 index 000000000..31f5c9e89 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/configuration.adoc @@ -0,0 +1,168 @@ +[[cassandra.connectors]] += Connecting to Cassandra with Spring + +One of the first tasks when using Apache Cassandra with Spring is to create a `com.datastax.oss.driver.api.core.CqlSession` object by using the Spring IoC container. +You can do so either by using Java-based bean metadata or by using XML-based bean metadata. +These are discussed in the following sections. + +NOTE: For those not familiar with how to configure the Spring container using Java-based bean metadata instead of XML-based metadata, see the high-level introduction in the reference docs +https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/new-in-3.0.html#new-java-configuration[here] +as well as the detailed documentation {springDocsUrl}core.html#beans-java-instantiating-container[here]. + +[[cassandra.cassandra-java-config]] +== Registering a Session Instance by Using Java-based Metadata + +The following example shows how to use Java-based bean metadata to register an instance of a `com.datastax.oss.driver.api.core.CqlSession`: + +.Registering a `com.datastax.oss.driver.api.core.CqlSession` object by using Java-based bean metadata +==== +[source,java] +---- +include::example$AppConfig.java[tags=class] +---- +==== + +This approach lets you use the standard `com.datastax.oss.driver.api.core.CqlSession` API that you may already know. + +An alternative is to register an instance of `com.datastax.oss.driver.api.core.CqlSession` with the container by using Spring's `CqlSessionFactoryBean`. +As compared to instantiating a `com.datastax.oss.driver.api.core.CqlSession` instance directly, the `FactoryBean` approach has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates Cassandra exceptions to exceptions in Spring's portable `DataAccessException` hierarchy. +This hierarchy and the use of +`@Repository` is described in {springDocsUrl}data-access.html[Spring's DAO support features]. + +The following example shows Java-based factory class usage: + +.Registering a com.datastax.oss.driver.api.core.CqlSession object by using Spring's `CqlSessionFactoryBean`: +==== +[source,java] +---- +include::example$FactoryBeanAppConfig.java[tags=class] +---- +==== + +Using `CassandraTemplate` with object mapping and repository support requires a `CassandraTemplate`, +`CassandraMappingContext`, `CassandraConverter`, and enabling repository support. + +The following example shows how to register components to configure object mapping and repository support: + +.Registering components to configure object mapping and repository support +==== +[source,java] +---- +include::example$CassandraConfig.java[tags=class] +---- +==== + +Creating configuration classes that register Spring Data for Apache Cassandra components can be an exhausting challenge, so Spring Data for Apache Cassandra comes with a pre-built configuration support class. +Classes that extend from +`AbstractCassandraConfiguration` register beans for Spring Data for Apache Cassandra use. +`AbstractCassandraConfiguration` lets you provide various configuration options, such as initial entities, default query options, pooling options, socket options, and many more. `AbstractCassandraConfiguration` also supports you with schema generation based on initial entities, if any are provided. +Extending from +`AbstractCassandraConfiguration` requires you to at least provide the keyspace name by implementing the `getKeyspaceName` method. +The following example shows how to register beans by using `AbstractCassandraConfiguration`: + +.Registering Spring Data for Apache Cassandra beans by using `AbstractCassandraConfiguration` +==== +[source,java] +---- +include::example$CassandraConfiguration.java[tags=class] +---- +==== + +[[cassandra-connectors.xmlconfig]] +=== XML Configuration + +This section describes how to configure Spring Data Cassandra with XML. + +[[cassandra-connectors.xmlconfig.ext_properties]] +=== Externalizing Connection Properties + +To externalize connection properties, you should first create a properties file that contains the information needed to connect to Cassandra. `contactpoints` and `keyspace` are the required fields. + +The following example shows our properties file, called `cassandra.properties`: + +==== +[source] +---- +cassandra.contactpoints=10.1.55.80:9042,10.1.55.81:9042 +cassandra.keyspace=showcase +---- +==== + +In the next two examples, we use Spring to load these properties into the Spring context. + +[[registering-a-session-instance-by-using-xml-based-metadata]] +=== Registering a Session Instance by using XML-based Metadata + +While you can use Spring's traditional `` XML namespace to register an instance of +`com.datastax.oss.driver.api.core.CqlSession` with the container, the XML can be quite verbose, because it is general purpose. +XML namespaces are a better alternative to configuring commonly used objects, such as the `CqlSession` instance. +The `cassandra` namespace let you create a `CqlSession` instance. + +The following example shows how to configure the `cassandra` namespace: + +.XML schema to configure Cassandra by using the `cassandra` namespace +==== +[source,xml] +---- + + + + + + + + + + + + +---- +==== + +The XML configuration elements for more advanced Cassandra configuration are shown below. +These elements all use default bean names to keep the configuration code clean and readable. + +While the preceding example shows how easy it is to configure Spring to connect to Cassandra, there are many other options. +Basically, any option available with the DataStax Java Driver is also available in the Spring Data for Apache Cassandra configuration. +This includes but is not limited to authentication, load-balancing policies, retry policies, and pooling options. +All of the Spring Data for Apache Cassandra method names and XML elements are named exactly (or as close as possible) like the configuration options on the driver so that mapping any existing driver configuration should be straight forward. +The following example shows how to configure Spring Data components by using XML + +.Configuring Spring Data components by using XML +==== +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + +---- +==== diff --git a/src/main/asciidoc/reference/converters.adoc b/src/main/antora/modules/ROOT/pages/cassandra/converters.adoc similarity index 86% rename from src/main/asciidoc/reference/converters.adoc rename to src/main/antora/modules/ROOT/pages/cassandra/converters.adoc index ad6066644..6e165bcc5 100644 --- a/src/main/asciidoc/reference/converters.adoc +++ b/src/main/antora/modules/ROOT/pages/cassandra/converters.adoc @@ -1,3 +1,5 @@ +include::{commons}@data-commons::page$custom-conversions.adoc[] + [[cassandra.custom-converters]] == Overriding Default Mapping with Custom Converters @@ -17,7 +19,7 @@ with Jackson 2: [source,java] ---- -include::../{example-root}/PersonWriteConverter.java[tags=class] +include::example$PersonWriteConverter.java[tags=class] ---- [[customconversions.reader]] @@ -29,7 +31,7 @@ The following example uses a `Converter` that converts a `java.lang.String` into [source,java] ---- -include::../{example-root}/PersonReadConverter.java[tags=class] +include::example$PersonReadConverter.java[tags=class] ---- [[customconversions.java]] @@ -41,7 +43,5 @@ The following configuration snippet shows how to manually register converters as [source,java] ---- -include::../{example-root}/ConverterConfiguration.java[tags=class] +include::example$ConverterConfiguration.java[tags=class] ---- - -include::../{spring-data-commons-docs}/custom-conversions.adoc[leveloffset=+3] diff --git a/src/main/antora/modules/ROOT/pages/cassandra/cql-template.adoc b/src/main/antora/modules/ROOT/pages/cassandra/cql-template.adoc new file mode 100644 index 000000000..a8a6b8cff --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/cql-template.adoc @@ -0,0 +1,287 @@ +[[cassandra.cql-template]] += CQL Template API + +The `CqlTemplate` class (and its reactive variant `ReactiveCqlTemplate`) is the central class in the core CQL package. +It handles the creation and release of resources. +It performs the basic tasks of the core CQL workflow, such as statement creation and execution, and leaves application code to provide CQL and extract results. +The `CqlTemplate` class executes CQL queries and update statements, performs iteration over `ResultSet` instances and extraction of returned parameter values. +It also catches CQL exceptions and translates them to the generic, more informative, exception hierarchy defined in the `org.springframework.dao` package. + +When you use the `CqlTemplate` for your code, you need only implement callback interfaces, which have a clearly defined contract. +Given a `Connection`, the `PreparedStatementCreator` callback interface creates a xref:cassandra/prepared-statements.adoc#cassandra.template.prepared-statements.cql[prepared statement] with the provided CQL and any necessary parameter arguments. +The `RowCallbackHandler` interface extracts values from each row of a `ResultSet`. + +The `CqlTemplate` can be used within a DAO implementation through direct instantiation with a `SessionFactory` reference or be configured in the Spring container and given to DAOs as a bean reference. `CqlTemplate` is a foundational building block for xref:cassandra/template.adoc[`CassandraTemplate`]. + +All CQL issued by this class is logged at the `DEBUG` level under the category corresponding to the fully-qualified class name of the template instance (typically `CqlTemplate`, but it may be different if you use a custom subclass of the `CqlTemplate` class). + +You can control fetch size, consistency level, and retry policy defaults by configuring these parameters on the CQL API instances: `CqlTemplate`, `AsyncCqlTemplate`, and `ReactiveCqlTemplate`. +Defaults apply if the particular query option is not set. + +NOTE: `CqlTemplate` comes in different execution model flavors. +The basic `CqlTemplate` uses a blocking execution model. +You can use `AsyncCqlTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or +<> for reactive execution. + +[[cassandracql-template.examples]] +== Examples of `CqlTemplate` Class Usage + +This section provides some examples of the `CqlTemplate` class in action. +These examples are not an exhaustive list of all functionality exposed by the `CqlTemplate`. +See the https://docs.spring.io/spring-data/cassandra/docs/{version}/api/[Javadoc] for that. + +[[cassandra.cql-template.examples.query]] +=== Querying (SELECT) with `CqlTemplate` + +The following query gets the number of rows in a table: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=rowCount] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=rowCount] +---- +====== + +The following query uses a bind variable: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=countOfActorsNamedJoe] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=countOfActorsNamedJoe] +---- +====== + +The following example queries for a `String`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=lastName] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=lastName] +---- +====== + +The following example queries and populates a single domain object: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=rowMapper] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=rowMapper] +---- +====== + +The following example queries and populates multiple domain objects: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=listOfRowMapper] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=listOfRowMapper] +---- +====== + +If the last two snippets of code actually existed in the same application, it would make sense to remove the duplication present in the two `RowMapper` anonymous inner classes and extract them out into a single class (typically a `static` nested class) that can then be referenced by DAO methods. + +For example, it might be better to write the last code snippet as follows: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=findAllActors] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=findAllActors] +---- +====== + +[[cassandra.cql-template.examples.update]] +=== `INSERT`, `UPDATE`, and `DELETE` with `CqlTemplate` + +You can use the `execute(…)` method to perform `INSERT`, `UPDATE`, and `DELETE` operations. +Parameter values are usually provided as variable arguments or, alternatively, as an object array. + +The following example shows how to perform an `INSERT` operation with `CqlTemplate`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=insert] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=insert] +---- +====== + +The following example shows how to perform an `UPDATE` operation with `CqlTemplate`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=update] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=update] +---- +====== + +The following example shows how to perform an `DELETE` operation with `CqlTemplate`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=delete] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=delete] +---- +====== + +[[cassandra.cql-template.examples.other]] +=== Other `CqlTemplate` operations + +You can use the `execute(..)` method to execute any arbitrary CQL. +As a result, the method is often used for DDL statements. +It is heavily overloaded with variants that take callback interfaces, bind variable arrays, and so on. + +The following example shows how to create and drop a table by using different API objects that are all passed to the `execute()` methods: + +==== +[source,java] +---- +include::example$CqlTemplateExamples.java[tags=other] +---- +==== + +[[cassandra.connections]] +== Controlling Cassandra Connections + +Applications connect to Apache Cassandra by using `CqlSession` objects. +A Cassandra `CqlSession` keeps track of multiple connections to the individual nodes and is designed to be a thread-safe, long-lived object. +Usually, you can use a single `CqlSession` for the whole application. + +Spring acquires a Cassandra `CqlSession` through a `SessionFactory`. `SessionFactory` is part of Spring Data for Apache Cassandra and is a generalized connection factory. +It lets the container or framework hide connection handling and routing issues from the application code. + +The following example shows how to configure a default `SessionFactory`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +CqlSession session = … // get a Cassandra Session + +CqlTemplate template = new CqlTemplate(); + +template.setSessionFactory(new DefaultSessionFactory(session)); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + +CqlSession session = … // get a Cassandra Session + +ReactiveCqlTemplate template = new ReactiveCqlTemplate(new DefaultBridgedReactiveSession(session)); +---- +====== + +`CqlTemplate` and other Template API implementations obtain a `CqlSession` for each operation. +Due to their long-lived nature, sessions are not closed after invoking the desired operation. +Responsibility for proper resource disposal lies with the container or framework that uses the session. + +You can find various `SessionFactory` implementations within the `org.springframework.data.cassandra.core.cql.session` +package. + +[[exception-translation]] +== Exception Translation + +The Spring Framework provides exception translation for a wide variety of database and mapping technologies. +This has traditionally been for JDBC and JPA. +Spring Data for Apache Cassandra extends this feature to Apache Cassandra by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface. + +The motivation behind mapping to Spring's {springDocsUrl}html/dao.html#dao-exceptions[consistent data access exception hierarchy] +is to let you write portable and descriptive exception handling code without resorting to coding against and handling specific Cassandra exceptions. +All of Spring's data access exceptions are inherited from the +`DataAccessException` class, so you can be sure that you can catch all database-related exceptions within a single try-catch block. + +`ReactiveCqlTemplate` and `ReactiveCassandraTemplate` propagate exceptions as early as possible. +Exceptions that occur during the processing of the reactive sequence are emitted as error signals. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/events.adoc b/src/main/antora/modules/ROOT/pages/cassandra/events.adoc new file mode 100644 index 000000000..aef888686 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/events.adoc @@ -0,0 +1,68 @@ +[[cassandra.mapping-usage.events]] += Lifecycle Events + +The Cassandra mapping framework has several built-in `org.springframework.context.ApplicationEvent` events that your application can respond to by registering special beans in the `ApplicationContext`. +Being based on Spring's application context event infrastructure lets other products, such as Spring Integration, easily receive these events as they are a well known eventing mechanism in Spring-based applications. + +To intercept an object before it goes into the database, you can register a subclass of `org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener` that overrides the `onBeforeSave(…)` method. +When the event is dispatched, your listener is called and passed the domain object (which is a Java entity). +Entity lifecycle events can be costly and you may notice a change in the performance profile when loading large result sets. +You can disable lifecycle events on the link:https://docs.spring.io/spring-data/cassandra/docs/{version}/api/org/springframework/data/cassandra/core/CassandraTemplate.html#setEntityLifecycleEventsEnabled(boolean)[Template API]. +The following example uses the `onBeforeSave` method: + +==== +[source,java] +---- +include::example$mapping/BeforeSaveListener.java[tags=class] +---- +==== + +Declaring these beans in your Spring `ApplicationContext` will cause them to be invoked whenever the event is dispatched. + +The `AbstractCassandraEventListener` has the following callback methods: + +* `onBeforeSave`: Called in `CassandraTemplate.insert(…)` and `.update(…)` operations before inserting or updating a row in the database. +* `onAfterSave`: Called in `CassandraTemplate…insert(…)` and `.update(…)` operations after inserting or updating a row in the database. +* `onBeforeDelete`: Called in `CassandraTemplate.delete(…)` operations before deleting row from the database. +* `onAfterDelete`: Called in `CassandraTemplate.delete(…)` operations after deleting row from the database. +* `onAfterLoad`: Called in the `CassandraTemplate.select(…)`, `.slice(…)`, and `.stream(…)` methods after each row is retrieved from the database. +* `onAfterConvert`: Called in the `CassandraTemplate.select(…)`, `.slice(…)`, and `.stream(…)` methods after converting a row retrieved from the database to a POJO. + +NOTE: Lifecycle events are emitted only for root-level types. +Complex types used as properties within an aggregate root are not subject to event publication. + +include::{commons}@data-commons::page$entity-callbacks.adoc[leveloffset=+1] + +[[cassandra.entity-callbacks]] +=== Store specific EntityCallbacks + +Spring Data for Apache Cassandra uses the `EntityCallback` API for its auditing support and reacts on the following callbacks. + +.Supported Entity Callbacks +[%header,cols="4"] +|=== +| Callback +| Method +| Description +| Order + +| `ReactiveBeforeConvertCallback` +`BeforeConvertCallback` +| `onBeforeConvert(T entity, CqlIdentifier tableName)` +| Invoked before a domain object is converted to `com.datastax.driver.core.Statement`. +| `Ordered.LOWEST_PRECEDENCE` + +| `ReactiveAuditingEntityCallback` +`AuditingEntityCallback` +| `onBeforeConvert(Object entity, CqlIdentifier tableName)` +| Marks an auditable entity _created_ or _modified_ +| 100 + +| `ReactiveBeforeSaveCallback` +`BeforeSaveCallback` +| `onBeforeSave(T entity, CqlIdentifier tableName, Statement statement)` +| Invoked before a domain object is saved. + +Can modify the target, to be persisted, `com.datastax.driver.core.Statement` containing all mapped entity information. +| `Ordered.LOWEST_PRECEDENCE` + +|=== diff --git a/src/main/antora/modules/ROOT/pages/cassandra/getting-started.adoc b/src/main/antora/modules/ROOT/pages/cassandra/getting-started.adoc new file mode 100644 index 000000000..c1ae35ec3 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/getting-started.adoc @@ -0,0 +1,103 @@ +[[cassandra.getting-started]] += Getting Started + +Spring Data for Apache Cassandra requires Apache Cassandra 2.1 or later and Datastax Java Driver 4.0 or later. +An easy way to quickly set up and bootstrap a working environment is to create a Spring-based project in https://spring.io/tools[Spring Tools] or use https://start.spring.io/[Spring Initializer]. + +[[cassandra.examples-repo]] +== Examples Repository + +To get a feel for how the library works, you can download and play around with +https://github.com/spring-projects/spring-data-examples[several examples]. + +[[cassandra.hello-world]] +== Hello World + +First, you need to set up a running Apache Cassandra server. +See the +https://cassandra.apache.org/doc/latest/getting_started/index.html[Apache Cassandra Quick Start Guide] +for an explanation on how to start Apache Cassandra. +Once installed, starting Cassandra is typically a matter of executing the following command: `CASSANDRA_HOME/bin/cassandra -f`. + +To create a Spring project in STS, go to File -> New -> Spring Template Project -> Simple Spring Utility Project and press Yes when prompted. +Then enter a project and a package name, such as `org.spring.data.cassandra.example`. + +Then you can add the following dependency declaration to your pom.xml file's `dependencies` section. + +==== +[source,xml,subs="verbatim,attributes"] +---- + + + + org.springframework.data + spring-data-cassandra + {version} + + + +---- +==== + +Also, you should change the version of Spring in the pom.xml file to be as follows: + +==== +[source,xml,subs="verbatim,attributes"] +---- +{springVersion} +---- +==== + +If using a milestone release instead of a GA release, you also need to add the location of the Spring Milestone repository for Maven to your pom.xml file so that it is at the same level of your `` element, as follows: + +[source,xml] +---- + + + spring-milestone + Spring Maven MILESTONE Repository + https://repo.spring.io/milestone + + +---- + +The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here]. + +You can also browse all Spring repositories https://repo.spring.io/webapp/#/home[here]. + +Now you can create a simple Java application that stores and reads a domain object to and from Cassandra. + +To do so, first create a simple domain object class to persist, as the following example shows: + +==== +[source,java] +---- +include::example$Person.java[tags=file] +---- +==== + +Next, create the main application to run, as the following example shows: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CassandraApplication.java[tags=file] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCassandraApplication.java[tags=file] +---- +====== + +Even in this simple example, there are a few notable things to point out: + +* You can create an instance of `CassandraTemplate` (or `ReactiveCassandraTemplate` for reactive usage) with a Cassandra `CqlSession`. +* You must annotate your POJO as a Cassandra `@Table` entity and also annotate the `@PrimaryKey`. +Optionally, you can override these mapping names to match your Cassandra database table and column names. +* You can either use raw CQL or the DataStax `QueryBuilder` API to construct your queries. diff --git a/src/main/asciidoc/reference/introduction.adoc b/src/main/antora/modules/ROOT/pages/cassandra/introduction.adoc similarity index 69% rename from src/main/asciidoc/reference/introduction.adoc rename to src/main/antora/modules/ROOT/pages/cassandra/introduction.adoc index ff9c596cf..603c7251a 100644 --- a/src/main/asciidoc/reference/introduction.adoc +++ b/src/main/antora/modules/ROOT/pages/cassandra/introduction.adoc @@ -3,11 +3,9 @@ This part of the reference documentation explains the core functionality offered by Spring Data for Apache Cassandra. -<> introduces the Cassandra module feature set. - -<> explains reactive Cassandra specifics. - -<> introduces repository support for Cassandra. +* xref:cassandra.adoc[Cassandra Support] introduces the Cassandra module feature set. +* xref:cassandra/reactive-cassandra.adoc[Reactive Cassandra Support] explains reactive Cassandra specifics. +* xref:repositories.adoc[Cassandra Repositories] introduces repository support for Cassandra. [[cassandra.modules]] == Spring CQL and Spring Data for Apache Cassandra Modules @@ -70,13 +68,13 @@ Spring's support for Apache Cassandra comes in different flavors. Once you start using one of these approaches, you can still mix and match to include a feature from a different approach. The following approaches work well: -* <> and <> are the classic Spring CQL approach and the most popular. +* xref:cassandra/cql-template.adoc[`CqlTemplate`] and xref:cassandra/reactive-cassandra.adoc#cassandra.reactive.cql-template[`ReactiveCqlTemplate`] are the classic Spring CQL approach and the most popular. This is the "`lowest-level`" approach. Note that components like `CassandraTemplate` use `CqlTemplate` under-the-hood. -* <> wraps a `CqlTemplate` to provide query result-to-object mapping and the use of `SELECT`, `INSERT`, `UPDATE`, and `DELETE` methods instead of writing CQL statements. +* xref:cassandra/template.adoc[`CassandraTemplate`] wraps a `CqlTemplate` to provide query result-to-object mapping and the use of `SELECT`, `INSERT`, `UPDATE`, and `DELETE` methods instead of writing CQL statements. This approach provides better documentation and ease of use. -* <> wraps a `ReactiveCqlTemplate` to provide query result-to-object mapping and the use of `SELECT`, `INSERT`, `UPDATE`, and `DELETE` methods instead of writing CQL statements. +* xref:cassandra/reactive-cassandra.adoc#cassandra.reactive.template[`ReactiveCassandraTemplate`] wraps a `ReactiveCqlTemplate` to provide query result-to-object mapping and the use of `SELECT`, `INSERT`, `UPDATE`, and `DELETE` methods instead of writing CQL statements. This approach provides better documentation and ease of use. * Repository Abstraction lets you create repository declarations in your data access layer. The goal of Spring Data's repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/prepared-statements.adoc b/src/main/antora/modules/ROOT/pages/cassandra/prepared-statements.adoc new file mode 100644 index 000000000..29832ba67 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/prepared-statements.adoc @@ -0,0 +1,104 @@ +[[cassandra.template.prepared-statements]] += Prepared Statements + +CQL statements that are executed multiple times can be prepared and stored in a `PreparedStatement` object to improve query performance. +Both, the driver and Cassandra maintain a mapping of `PreparedStatement` queries to their metadata. +You can use prepared statements through the following abstractions: + +* xref:cassandra/cql-template.adoc[`CqlTemplate`, `AsyncCqlTemplate`, or `ReactiveCqlTemplate`] through the choice of API +* xref:cassandra/template.adoc[`CassandraTemplate`, `AsyncCassandraTemplate`, or `ReactiveCassandraTemplate`] by enabling prepared statements +* xref:repositories.adoc[Cassandra repositories] as they are built on top of the Template API + +[[cassandra.template.prepared-statements.cql]] +== Using `CqlTemplate` + +The `CqlTemplate` class (and its asynchronous and reactive variants) offers various methods accepting static CQL, `Statement` objects and `PreparedStatementCreator`. +Methods accepting static CQL without additional arguments typically run the CQL statement as-is without further processing. +Methods accepting static CQL in combination with an arguments array (such as `execute(String cql, Object... args)` and `queryForRows(String cql, Object... args)`) use prepared statements. +Internally, these methods create a `PreparedStatementCreator` and `PreparedStatementBinder` objects to prepare the statement and later on to bind values to the statement to run it. +Spring Data Cassandra generally uses index-based parameter bindings for prepared statements. + +Since Cassandra Driver version 4, prepared statements are cached on the driver level which removes the need to keep track of prepared statements in the application. + +The following example shows how to issue a query with a parametrized prepared statement: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=lastName] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=lastName] +---- +====== + +In cases where you require more control over statement preparation and parameter binding (for example, using named binding parameters), you can fully control prepared statement creation and parameter binding by calling query methods with `PreparedStatementCreator` and `PreparedStatementBinder` arguments: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CqlTemplateExamples.java[tags=preparedStatement] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCqlTemplateExamples.java[tags=preparedStatement] +---- +====== + +Spring Data Cassandra ships with classes supporting that pattern in the `cql` package: + +* `SimplePreparedStatementCreator` - utility class to create a prepared statement. +* `ArgumentPreparedStatementBinder` - utility class to bind arguments to a prepared statement. + +[[cassandra.template.prepared-statements.cassandra-template]] +=== Using `CassandraTemplate` + +The `CassandraTemplate` class is built on top of `CqlTemplate` to provide a higher level of abstraction. +The use of prepared statements can be controlled directly on `CassandraTemplate` (and its asynchronous and reactive variants) by calling `setUsePreparedStatements(false)` respective `setUsePreparedStatements(true)`. +Note that the use of prepared statements by `CassandraTemplate` is enabled by default. + +The following example shows the use of methods that generate and that accept CQL: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +include::example$CassandraTemplateExamples.java[tags=preparedStatement] +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +include::example$ReactiveCassandraTemplateExamples.java[tags=preparedStatement] +---- +====== + +Calling entity-bound methods such as `select(Query, Class)` or `update(Query, Update, Class)` build CQL statements themselves to perform the intended operations. +Some `CassandraTemplate` methods (such as `select(Statement, Class)`) also accepts CQL `Statement` objects as part of their API. + +It's possible to participate in prepared statements when calling methods accepting a `Statement` with a `SimpleStatement` object. +The template API extracts the query string and parameters (positional and named parameters) and uses these to prepare, bind, and run the statement. +Non-``SimpleStatement`` objects cannot be used with prepared statements. + +[[cassandra.template.prepared-statements.caching]] +== Caching Prepared Statements + +Since Cassandra driver 4.0, prepared statements are cached by the `CqlSession` cache so it is okay to prepare the same string twice. +Previous versions required caching of prepared statements outside of the driver. +See also the https://docs.datastax.com/en/developer/java-driver/latest/manual/core/statements/prepared/[Driver documentation on Prepared Statements] for further reference. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/reactive-cassandra.adoc b/src/main/antora/modules/ROOT/pages/cassandra/reactive-cassandra.adoc new file mode 100644 index 000000000..60e44c339 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/reactive-cassandra.adoc @@ -0,0 +1,36 @@ +[[cassandra.reactive]] += Reactive Infrastructure + +The reactive Cassandra support contains a wide range of features: + +* Spring configuration support using Java-based `@Configuration` classes. +* `ReactiveCqlTemplate` helper class that increases productivity by properly handling common Cassandra data access operations. +* `ReactiveCassandraTemplate` helper class that increases productivity by using `ReactiveCassandraOperations` in a reactive manner. +It includes integrated object mapping between tables and POJOs. +* Exception translation into Spring's portable {springDocsUrl}data-access.html#dao-exceptions[Data Access Exception Hierarchy]. +* Feature rich object mapping integrated with Spring's {springDocsUrl}core.html#core-convert[Conversion Service]. +* Java-based Query, Criteria, and Update DSLs. +* Automatic implementation of `Repository` interfaces, including support for custom finder methods. + +For most data-oriented tasks, you can use the `ReactiveCassandraTemplate` or the repository support, which use the rich object mapping functionality. `ReactiveCqlTemplate` is commonly used to increment counters or perform ad-hoc CRUD operations. `ReactiveCqlTemplate` also provides callback methods that make it easy to get low-level API objects, such as `com.datastax.oss.driver.api.core.CqlSession`, which let you communicate directly with Cassandra. +Spring Data for Apache Cassandra uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are immediately familiar and so that you can map your existing knowledge onto the Spring APIs. + +Reactive usage is broken up into two phases: Composition and Execution. + +Calling repository methods lets you compose a reactive sequence by obtaining `Publisher` instances and applying operators. +No I/O happens until you subscribe. +Passing the reactive sequence to a reactive execution infrastructure, such as {springDocsUrl}web.html#web-reactive[Spring WebFlux] +or https://vertx.io/docs/vertx-reactive-streams/java/[Vert.x]), subscribes to the publisher and initiate the actual execution. +See https://projectreactor.io/docs/core/release/reference/#reactive.subscribe[the Project reactor documentation] for more detail. + +[[cassandra.reactive.repositories.libraries]] +== Reactive Composition Libraries + +The reactive space offers various reactive composition libraries. +The most common libraries are +https://github.com/ReactiveX/RxJava[RxJava] and https://projectreactor.io/[Project Reactor]. + +Spring Data for Apache Cassandra is built on top of the https://github.com/datastax/java-driver[DataStax Cassandra Driver]. +The driver is not reactive but the asynchronous capabilities allow us to adopt and expose the `Publisher` APIs to provide maximum interoperability by relying on the https://www.reactive-streams.org/[Reactive Streams] initiative. +Static APIs, such as `ReactiveCassandraOperations`, are provided by using Project Reactor's `Flux` and `Mono` types. +Project Reactor offers various adapters to convert reactive wrapper types (`Flux` to `Observable` and back), but conversion can easily clutter your code. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/repositories/cdi-integration.adoc b/src/main/antora/modules/ROOT/pages/cassandra/repositories/cdi-integration.adoc new file mode 100644 index 000000000..0d1112c9e --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/repositories/cdi-integration.adoc @@ -0,0 +1,25 @@ +[[cassandra.repositories.misc.cdi-integration]] += CDI Integration + +Instances of the repository interfaces are usually created by a container, and the Spring container is the most natural choice when working with Spring Data. +Spring Data for Apache Cassandra ships with a custom CDI extension that allows using the repository abstraction in CDI environments. +The extension is part of the JAR.To activate it, drop the Spring Data for Apache Cassandra JAR into your classpath. +You can now set up the infrastructure by implementing a CDI Producer for the +`CassandraTemplate`, as the following examlpe shows: + +==== +[source,java] +---- +include::example$CassandraTemplateProducer.java[tags=class] +---- +==== + +The Spring Data for Apache Cassandra CDI extension picks up `CassandraOperations` as a CDI bean and creates a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container. +Thus, obtaining an instance of a Spring Data repository is a matter of declaring an injected property, as the following example shows: + +==== +[source,java] +---- +include::example$RepositoryClient.java[tags=class] +---- +==== diff --git a/src/main/antora/modules/ROOT/pages/cassandra/repositories/query-methods.adoc b/src/main/antora/modules/ROOT/pages/cassandra/repositories/query-methods.adoc new file mode 100644 index 000000000..bc6e2f841 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/repositories/query-methods.adoc @@ -0,0 +1,240 @@ +[[cassandra.repositories.queries]] += Cassandra-specific Query Methods + +NOTE: This chapter explains Cassandra-specific query methods. +This documentation uses imperative types. +By using reactive return types, the same semantics apply to reactive repositories as well. + +Most of the data access operations you usually trigger on a repository result in a query being executed against the Apache Cassandra database. +Defining such a query is a matter of declaring a method on the repository interface. +The following example shows a number of such method declarations: + +.PersonRepository with query methods + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +interface PersonRepository extends CrudRepository { + + List findByLastname(String lastname); <1> + + Slice findByFirstname(String firstname, Pageable pageRequest); <2> + + Window findByFirstname(String firstname, CassandraScrollPosition pos, Limit limit); <3> + + List findByFirstname(String firstname, QueryOptions opts); <4> + + List findByFirstname(String firstname, Sort sort); <5> + + List findByFirstname(String firstname, Limit limit); <6> + + Person findByShippingAddress(Address address); <7> + + Person findFirstByShippingAddress(Address address); <8> + + Stream findAllBy(); <9> + + @AllowFiltering + List findAllByAge(int age); <10> +} +---- +<1> The method shows a query for all people with the given `lastname`. +The query is derived from parsing the method name for constraints, which can be concatenated with `And`. +Thus, the method name results in a query expression of `SELECT * FROM person WHERE lastname = 'lastname'`. +<2> Applies pagination to a query. +You can equip your method signature with a `Pageable` parameter and let the method return a `Slice` instance, and we automatically page the query accordingly. +<3> Applies scrolling to a query. +Scrolling wraps Cassandra's `PagingState` into `CassandraScrollPosition` and allows dynamic limiting. +You can also use `findTop…` for a static limit. +<4> Passing a `QueryOptions` object applies the query options to the resulting query before its execution. +<5> Applies dynamic sorting to a query. +You can add a `Sort` parameter to your method signature, and Spring Data automatically applies ordering to the query. +<6> Applies dynamic result limiting to a query. +Query results can be limited using `SELECT … LIMIT`. +<7> Shows that you can query based on properties that are not a primitive type by using `Converter` instances registered in `CustomConversions`. +Throws `IncorrectResultSizeDataAccessException` if more than one match is found. +<8> Uses the `First` keyword to restrict the query to only the first result. +Unlike the preceding method, this method does not throw an exception if more than one match is found. +<9> Uses a Java 8 `Stream` to read and convert individual elements while iterating the stream. +<10> Shows a query method annotated with `@AllowFiltering`, to allow server-side filtering. + +Reactive:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +---- +interface ReactivePersonRepository extends ReactiveSortingRepository { + + Flux findByFirstname(String firstname); <1> + + Flux findByFirstname(Publisher firstname); <2> + + Mono findByFirstnameAndLastname(String firstname, String lastname); <3> + + Mono findFirstByFirstname(String firstname); <4> + + @AllowFiltering + Flux findByAge(int age); <5> +} +---- +<1> A query for all people with the given `firstname`. +The query is derived by parsing the method name for constraints, which can be concatenated with `And` and `Or`. +Thus, the method name results in a query expression of `SELECT * FROM person WHERE firstname = :firstname`. +<2> A query for all people with the given `firstname` once the `firstname` is emitted from the given `Publisher`. +<3> Find a single entity for the given criteria. +Completes with `IncorrectResultSizeDataAccessException` on non-unique results. +<4> Unlike the preceding query, the first entity is always emitted even if the query yields more result rows. +<5> A query method annotated with `@AllowFiltering`, which allows server-side filtering. +====== + +NOTE: Querying non-primary key properties requires secondary indexes. + +The following table shows short examples of the keywords that you can use in query methods: + +[cols="1,2,3",options="header"] +.Supported keywords for query methods +|=== +| Keyword +| Sample +| Logical result + +| `After` +| `findByBirthdateAfter(Date date)` +| `birthdate > date` + +| `GreaterThan` +| `findByAgeGreaterThan(int age)` +| `age > age` + +| `GreaterThanEqual` +| `findByAgeGreaterThanEqual(int age)` +| `age >= age` + +| `Before` +| `findByBirthdateBefore(Date date)` +| `birthdate < date` + +| `LessThan` +| `findByAgeLessThan(int age)` +| `age < age` + +| `LessThanEqual` +| `findByAgeLessThanEqual(int age)` +| `age <= age` + +| `Between` +| `findByAgeBetween(int from, int to)` and `findByAgeBetween(Range range)` +| ``age > from AND age < to`` and +lower / upper bounds (`>` / `>=` & `<` / `<=`) according to `Range` + +| `In` +| `findByAgeIn(Collection ages)` +| `age IN (ages...)` + +| `Like`, `StartingWith`, `EndingWith` +| `findByFirstnameLike(String name)` +| `firstname LIKE (name as like expression)` + +| `Containing` on String +| `findByFirstnameContaining(String name)` +| `firstname LIKE (name as like expression)` + +| `Containing` on Collection +| `findByAddressesContaining(Address address)` +| `addresses CONTAINING address` + +| `(No keyword)` +| `findByFirstname(String name)` +| `firstname = name` + +| `IsTrue`, `True` +| `findByActiveIsTrue()` +| `active = true` + +| `IsFalse`, `False` +| `findByActiveIsFalse()` +| `active = false` + +|=== + +[[cassandra.repositories.queries.delete]] +== Repository Delete Queries + +The keywords in the preceding table can be used in conjunction with `delete…By` to create queries that delete matching documents. + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +interface PersonRepository extends Repository { + + void deleteWithoutResultByLastname(String lastname); + + boolean deleteByLastname(String lastname); +} +---- + +Reactive:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +---- +interface PersonRepository extends Repository { + + Mono deleteWithoutResultByLastname(String lastname); + + Mono deleteByLastname(String lastname); +} +---- +====== + +Delete queries return whether the query was applied or terminate without returning a value using `void`. + +[[cassandra.repositories.queries.options]] +=== Query Options + +You can specify query options for query methods by passing a `QueryOptions` object. +The options apply to the query before the actual query execution. +`QueryOptions` is treated as a non-query parameter and is not considered to be a query parameter value. +Query options apply to derived and string `@Query` repository methods. + +To statically set the consistency level, use the `@Consistency` annotation on query methods. +The declared consistency level is applied to the query each time it is executed. +The following example sets the consistency level to `ConsistencyLevel.LOCAL_ONE`: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +interface PersonRepository extends CrudRepository { + + @Consistency(ConsistencyLevel.LOCAL_ONE) + List findByLastname(String lastname); + + List findByFirstname(String firstname, QueryOptions options); +} +---- + +Reactive:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +---- +interface PersonRepository extends ReactiveCrudRepository { + + @Consistency(ConsistencyLevel.LOCAL_ONE) + Flux findByLastname(String lastname); + + Flux findByFirstname(String firstname, QueryOptions options); +} +---- +====== + +The DataStax Cassandra documentation includes https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/dml/dmlConfigConsistency.html[a good discussion of the available consistency levels]. + +NOTE: You can control fetch size, consistency level, and retry policy defaults by configuring the following parameters on the CQL API instances: `CqlTemplate`, `AsyncCqlTemplate`, and `ReactiveCqlTemplate`. +Defaults apply if the particular query option is not set. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/repositories/repositories.adoc b/src/main/antora/modules/ROOT/pages/cassandra/repositories/repositories.adoc new file mode 100644 index 000000000..fed9ca83a --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/repositories/repositories.adoc @@ -0,0 +1,246 @@ +[[cassandra.repositories]] += Cassandra Repositories + +To access domain entities stored in Apache Cassandra, you can use Spring Data's sophisticated repository support, which significantly eases implementing DAOs. +To do so, create an interface for your repository, as the following example shows: + +.Sample Person entity +==== +[source,java] +---- +@Table +public class Person { + + @Id + private String id; + private String firstname; + private String lastname; + + // … getters and setters omitted +} +---- +==== + +Note that the entity has a property named `id` of type `String`. +The default conversion mechanism used in `MappingCassandraConverter` (which backs the repository support) regards properties named `id` as being the row ID. + +The following example shows a repository definition to persist `Person` entities: + +.Basic repository interface to persist `Person` entities +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +interface PersonRepository extends CrudRepository { + + // additional custom finder methods go here +} +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +interface PersonRepository extends ReactiveCrudRepository { + + // additional custom finder methods go here +} +---- +====== + +Right now, the interface in the preceding example serves only typing purposes, but we add additional methods to it later. + +Next, in your Spring configuration, add the following (if you use Java for configuration): + +If you want to use Java configuration, use the `@EnableCassandraRepositories` respective `@EnableReactiveCassandraRepositories` annotation. +The annotation carries the same attributes as the namespace element. +If no base package is configured, the infrastructure scans the package of the annotated configuration class. +The following example show how to the different configuration approaches: + +.Configuration for repositories +[tabs] +====== +Imperative Java Configuration:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +@Configuration +@EnableCassandraRepositories +class ApplicationConfig extends AbstractCassandraConfiguration { + + @Override + protected String getKeyspaceName() { + return "keyspace"; + } + + public String[] getEntityBasePackages() { + return new String[] { "com.oreilly.springdata.cassandra" }; + } +} +---- + +XML:: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] +---- + + + + + + + + + + + + + + +---- + +Reactive Java Configuration:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="third"] +---- +@Configuration +@EnableReactiveCassandraRepositories +class ApplicationConfig extends AbstractReactiveCassandraConfiguration { + + @Override + protected String getKeyspaceName() { + return "keyspace"; + } + + public String[] getEntityBasePackages() { + return new String[] { "com.oreilly.springdata.cassandra" }; + } +} +---- +====== + +The `cassandra:repositories` namespace element causes the base packages to be scanned for interfaces that extend `CrudRepository` and create Spring beans for each one found. +By default, the repositories are wired with a `CassandraTemplate` Spring bean called `cassandraTemplate`, so you only need to configure +`cassandra-template-ref` explicitly if you deviate from this convention. + +Because our domain repository extends `CrudRepository` respective `ReactiveCrudRepository`, it provides you with basic CRUD operations. +Working with the repository instance is a matter of injecting the repository as a dependency into a client, as the following example does by autowiring `PersonRepository`: + +.Basic access to Person entities +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +@ExtendWith(SpringExtension.class) +class PersonRepositoryTests { + + @Autowired PersonRepository repository; + + @Test + void readsPersonTableCorrectly() { + + List persons = repository.findAll(); + assertThat(persons.isEmpty()).isFalse(); + } +} +---- + +Reactive:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +---- +public class PersonRepositoryTests { + + @Autowired ReactivePersonRepository repository; + + @Test + public void sortsElementsCorrectly() { + Flux people = repository.findAll(Sort.by(new Order(ASC, "lastname"))); + } +} +---- +====== + +Cassandra repositories support paging and sorting for paginated and sorted access to the entities. +Cassandra paging requires a paging state to forward-only navigate through pages. +A `Slice` keeps track of the current paging state and allows for creation of a `Pageable` to request the next page. +The following example shows how to set up paging access to `Person` entities: + +.Paging access to `Person` entities +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +@ExtendWith(SpringExtension.class) +class PersonRepositoryTests { + + @Autowired PersonRepository repository; + + @Test + void readsPagesCorrectly() { + + Slice firstBatch = repository.findAll(CassandraPageRequest.first(10)); + + assertThat(firstBatch).hasSize(10); + + Slice nextBatch = repository.findAll(firstBatch.nextPageable()); + + // … + } +} +---- + +Reactive:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="secondary"] +---- +@ExtendWith(SpringExtension.class) +class PersonRepositoryTests { + + @Autowired PersonRepository repository; + + @Test + void readsPagesCorrectly() { + + Mono> firstBatch = repository.findAll(CassandraPageRequest.first(10)); + + Mono> nextBatch = firstBatch.flatMap(it -> repository.findAll(it.nextPageable())); + + // … + } +}} +---- +====== + +NOTE: Cassandra repositories do not extend `PagingAndSortingRepository`, because classic paging patterns using limit/offset are not applicable to Cassandra. + +The preceding example creates an application context with Spring's unit test support, which performs annotation-based dependency injection into the test class. +Inside the test cases (the test methods), we use the repository to query the data store. +We invoke the repository query method that requests all `Person` instances. + +[[cassandra.repositories.reactive]] +== Reactive Repositories + +Spring Data's repository abstraction is a dynamic API that is mostly defined by you and your requirements as you declare query methods. +Reactive Cassandra repositories can be implemented by using either RxJava or Project Reactor wrapper types by extending from one of the library-specific repository interfaces: + +* `ReactiveCrudRepository` +* `ReactiveSortingRepository` +* `RxJava3CrudRepository` +* `RxJava3SortingRepository` + +Spring Data converts reactive wrapper types behind the scenes so that you can stick to your favorite composition library. diff --git a/src/main/antora/modules/ROOT/pages/cassandra/schema-management.adoc b/src/main/antora/modules/ROOT/pages/cassandra/schema-management.adoc new file mode 100644 index 000000000..ec16bdd3f --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/schema-management.adoc @@ -0,0 +1,253 @@ +[[cassandra.schema-management]] += Schema Management + +Apache Cassandra is a data store that requires a schema definition prior to any data interaction. +Spring Data for Apache Cassandra can support you with schema creation. + +[[keyspaces-and-lifecycle-scripts]] +== Keyspaces and Lifecycle Scripts + +The first thing to start with is a Cassandra keyspace. +A keyspace is a logical grouping of tables that share the same replication factor and replication strategy. +Keyspace management is located in the `CqlSession` configuration, which has the `KeyspaceSpecification` and startup and shutdown CQL script execution. + +Declaring a keyspace with a specification allows creating and dropping of the Keyspace. +It derives CQL from the specification so that you need not write CQL yourself. +The following example specifies a Cassandra keyspace by using XML: + +.Specifying a Cassandra keyspace +==== +.Java +[source,java,role="primary"] +---- +include::example$CreateKeyspaceConfiguration.java[tags=class] +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + + + + + + + + + + +---- +==== + +NOTE: Keyspace creation allows rapid bootstrapping without the need of external keyspace management. +This can be useful for certain scenarios but should be used with care. +Dropping a keyspace on application shutdown removes the keyspace and all data from the tables in the keyspace. + +[[cassandra.schema-management.initializing]] +== Initializing a `SessionFactory` + +The `org.springframework.data.cassandra.core.cql.session.init` package provides support for initializing an existing `SessionFactory`. +You may sometimes need to initialize a keyspace that runs on a server somewhere. + +[[cassandra.schema-management.initializing.config]] +=== Initializing a Keyspace + +You can provide arbitrary CQL that is executed on `CqlSession` initialization and shutdown in the configured keyspace, as the following Java configuration example shows: + +==== +.Java +[source,java,role="primary"] +---- +include::example$KeyspacePopulatorConfiguration.java[tags=class] +---- + +.XML +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] +---- + + + + +---- +==== + +The preceding example runs the two specified scripts against the keyspace. +The first script creates a schema, and the second populates tables with a test data set. +The script locations can also be patterns with wildcards in the usual Ant style used for resources in Spring (for example, `classpath{asterisk}:/com/foo/{asterisk}{asterisk}/cql/{asterisk}-data.cql`). +If you use a pattern, the scripts are run in the lexical order of their URL or filename. + +The default behavior of the keyspace initializer is to unconditionally run the provided scripts. +This may not always be what you want -- for instance, if you run the scripts against a keyspace that already has test data in it. +The likelihood of accidentally deleting data is reduced by following the common pattern (shown earlier) of creating the tables first and then inserting the data. +The first step fails if the tables already exist. + +However, to gain more control over the creation and deletion of existing data, the XML namespace provides a few additional options. +The first is a flag to switch the initialization on and off. +You can set this according to the environment (such as pulling a boolean value from system properties or from an environment bean). +The following example gets a value from a system property: + +==== +[source,xml,indent=0,subs="verbatim,quotes"] +---- + <1> + + +---- +<1> Get the value for `enabled` from a system property called `INITIALIZE_KEYSPACE`. +==== + +The second option to control what happens with existing data is to be more tolerant of failures. +To this end, you can control the ability of the initializer to ignore certain errors in the CQL it executes from the scripts, as the following example shows: + +==== +.Java +[source,java,role="primary"] +---- +include::example$KeyspacePopulatorFailureConfiguration.java[tags=class] +---- + +.XML +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] +---- + + + +---- +==== + +In the preceding example, we are saying that we expect that, sometimes, the scripts are run against an empty keyspace, and there are some `DROP` statements in the scripts that would, therefore, fail. +So failed CQL `DROP` statements will be ignored, but other failures will cause an exception. +This is useful if you don't want tu use support `DROP ... IF EXISTS` (or similar) but you want to unconditionally remove all test data before re-creating it. +In that case the first script is usually a set of `DROP` statements, followed by a set of `CREATE` statements. + +The `ignore-failures` option can be set to `NONE` (the default), `DROPS` (ignore failed drops), or `ALL` (ignore all failures). + +Each statement should be separated by `;` or a new line if the `;` character is not present at all in the script. +You can control that globally or script by script, as the following example shows: + +==== +.Java +[source,java,role="primary"] +---- +include::example$SessionFactoryInitializerConfiguration.java[tags=class] +---- + +.XML +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] +---- + + + + + +---- +==== + + +In this example, the two `test-data` scripts use `@@` as statement separator and only the `db-schema.cql` uses `;`. +This configuration specifies that the default separator is `@@` and overrides that default for the `db-schema` script. + +If you need more control than you get from the XML namespace, you can use the `SessionFactoryInitializer` directly and define it as a component in your application. + +[[cassandra.schema-management.initializing.component]] +==== Initialization of Other Components that Depend on the Keyspace + +A large class of applications (those that do not use the database until after the Spring context has started) can use the database initializer with no further complications. +If your application is not one of those, you might need to read the rest of this section. + +The database initializer depends on a `SessionFactory` instance and runs the scripts provided in its initialization callback (analogous to an `init-method` in an XML bean definition, a `@PostConstruct` method in a component, or the `afterPropertiesSet()` method in a component that implements `InitializingBean`). +If other beans depend on the same data source and use the session factory in an initialization callback, there might be a problem because the data has not yet been initialized. +A common example of this is a cache that initializes eagerly and loads data from the database on application startup. + +To get around this issue, you have two options: change your cache initialization strategy to a later phase or ensure that the keyspace initializer is initialized first. + +Changing your cache initialization strategy might be easy if the application is in your control and not otherwise. +Some suggestions for how to implement this include: + +* Make the cache initialize lazily on first usage, which improves application startup time. +* Have your cache or a separate component that initializes the cache implement `Lifecycle` or `SmartLifecycle`. +When the application context starts, you can automatically start a `SmartLifecycle` by setting its `autoStartup` flag, and you can manually start a `Lifecycle` by calling `ConfigurableApplicationContext.start()` on the enclosing context. +* Use a Spring `ApplicationEvent` or similar custom observer mechanism to trigger the cache initialization. `ContextRefreshedEvent` is always published by the context when it is ready for use (after all beans have been initialized), so that is often a useful hook (this is how the `SmartLifecycle` works by default). + +Ensuring that the keyspace initializer is initialized first can also be easy. +Some suggestions on how to implement this include: + +* Rely on the default behavior of the Spring `BeanFactory`, which is that beans are initialized in registration order. +You can easily arrange that by adopting the common practice of a set of `` elements in XML configuration that order your application modules and ensuring that the database and database initialization are listed first. +* Separate the `SessionFactory` and the business components that use it and control their startup order by putting them in separate `ApplicationContext` instances (for example, the parent context contains the `SessionFactory`, and the child context contains the business components). +This structure is common in Spring web applications but can be more generally applied. +* Use the Schema management for xref:cassandra/schema-management.adoc#cassandra.schema-management.tables[Tables and User-defined Types] to initialize the keyspace using Spring Data Cassandra's built-in schema generator. + +[[cassandra.schema-management.tables]] +== Tables and User-defined Types + +Spring Data for Apache Cassandra approaches data access with mapped entity classes that fit your data model. +You can use these entity classes to create Cassandra table specifications and user type definitions. + +Schema creation is tied to `CqlSession` initialization by `SchemaAction`. +The following actions are supported: + +* `SchemaAction.NONE`: No tables or types are created or dropped. +This is the default setting. +* `SchemaAction.CREATE`: Create tables, indexes, and user-defined types from entities annotated with `@Table` and types annotated with `@UserDefinedType`. +Existing tables or types cause an error if you tried to create the type. +* `SchemaAction.CREATE_IF_NOT_EXISTS`: Like `SchemaAction.CREATE` but with `IF NOT EXISTS` applied. +Existing tables or types do not cause any errors but may remain stale. +* `SchemaAction.RECREATE`: Drops and recreates existing tables and types that are known to be used. +Tables and types that are not configured in the application are not dropped. +* `SchemaAction.RECREATE_DROP_UNUSED`: Drops all tables and types and recreates only known tables and types. + +NOTE: `SchemaAction.RECREATE` and `SchemaAction.RECREATE_DROP_UNUSED` drop your tables and lose all data. +`RECREATE_DROP_UNUSED` also drops tables and types that are not known to the application. + +[[enabling-tables-and-user-defined-types-for-schema-management]] +=== Enabling Tables and User-Defined Types for Schema Management + +xref:object-mapping.adoc#mapping.usage[Metadata-based Mapping] explains object mapping with conventions and annotations. +To prevent unwanted classes from being created as a table or a type, schema management is only active for entities annotated with `@Table` and user-defined types annotated with `@UserDefinedType`. +Entities are discovered by scanning the classpath. +Entity scanning requires one or more base packages. +Tuple-typed columns that use `TupleValue` do not provide any typing details. +Consequently, you must annotate such column properties with `@CassandraType(type = TUPLE, typeArguments = …)` +to specify the desired column type. + +The following example shows how to specify entity base packages in XML configuration: + +.Specifying entity base packages +==== +.Java +[source,java,role="primary"] +---- +include::example$EntityBasePackagesConfiguration.java[tags=class] +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + +---- +==== + diff --git a/src/main/antora/modules/ROOT/pages/cassandra/template.adoc b/src/main/antora/modules/ROOT/pages/cassandra/template.adoc new file mode 100644 index 000000000..65d4c8e1f --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/template.adoc @@ -0,0 +1,567 @@ +[[cassandra.template]] += Persisting Entities + +The `CassandraTemplate` class (and its reactive variant `ReactiveCassandraTemplate`), located in the `org.springframework.data.cassandra` package, is the central class in Spring's Cassandra support and provides a rich feature set to interact with the database. +The template offers convenience operations to create, update, delete, and query Cassandra, and provides a mapping between your domain objects and rows in Cassandra tables. + +NOTE: Once configured, a template instance is thread-safe and can be reused across multiple instances. + +The mapping between rows in Cassandra and application domain classes is done by delegating to an implementation of the `CassandraConverter` interface. +Spring provides a default implementation, `MappingCassandraConverter`, but you can also write your own custom converter. +See the section on +xref:object-mapping.adoc[Cassandra conversion] for more detailed information. + +The `CassandraTemplate` class implements the `CassandraOperations` interface and its reactive variant `ReactiveCassandraTemplate` implements `ReactiveCassandraOperations`. +In as much as possible, the methods on `[Reactive]CassandraOperations` are named after methods available in Cassandra to make the API familiar to developers who are already familiar with Cassandra. + +For example, you can find methods such as `select`, `insert`, `delete`, and `update`. +The design goal was to make it as easy as possible to transition between the use of the base Cassandra driver and `[Reactive]CassandraOperations`. +A major difference between the two APIs is that `CassandraOperations` can be passed domain objects instead of CQL and query objects. + +NOTE: The preferred way to reference operations on a `[Reactive]CassandraTemplate` instance is through the +`[Reactive]CassandraOperations` interface. + +The default converter implementation used by `[Reactive]CassandraTemplate` is `MappingCassandraConverter`. +While `MappingCassandraConverter` can use additional metadata to specify the mapping of objects to rows, it can also convert objects that contain no additional metadata by using some conventions for the mapping of fields and table names. +These conventions, as well as the use of mapping annotations, are explained in the xref:object-mapping.adoc["`Mapping`" chapter]. + +Another central feature of `[Reactive]CassandraTemplate` is exception translation of exceptions thrown in the Cassandra Java driver into Spring's portable Data Access Exception hierarchy. +See the section on +xref:cassandra/cql-template.adoc#exception-translation[exception translation] for more information. + +NOTE: The Template API has different execution model flavors. +The basic `CassandraTemplate` uses a blocking (imperative-synchronous) execution model. +You can use `AsyncCassandraTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or `ReactiveCassandraTemplate` for reactive execution. + +[[cassandra.template.instantiating]] +== Instantiating `CassandraTemplate` + +`CassandraTemplate` should always be configured as a Spring bean, although we show an example earlier where you can instantiate it directly. +However, because we are assuming the context of making a Spring module, we assume the presence of the Spring container. + +There are two ways to get a `CassandraTemplate`, depending on how you load you Spring `ApplicationContext`: + +* xref:cassandra/template.adoc#cassandra-template-autowiring[Autowiring] +* xref:cassandra/template.adoc#cassandra-template-bean-lookup-applicationcontext[Bean Lookup with `ApplicationContext`] + +[float] +[[cassandra-template-autowiring]] +=== Autowiring + +You can autowire a `[Reactive]CassandraOperations` into your project, as the following example shows: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +@Autowired +private CassandraOperations cassandraOperations; +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +@Autowired +private ReactiveCassandraOperations reactiveCassandraOperations; +---- +====== + +As with all Spring autowiring, this assumes there is only one bean of type `[Reactive]CassandraOperations` in the `ApplicationContext`. +If you have multiple `[Reactive]CassandraTemplate` beans (which is the case if you work with multiple keyspaces in the same project), then you can use the `@Qualifier` annotation to designate the bean you want to autowire. + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +@Autowired +@Qualifier("keyspaceOneTemplateBeanId") +private CassandraOperations cassandraOperations; +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +@Autowired +@Qualifier("keyspaceOneTemplateBeanId") +private ReactiveCassandraOperations reactiveCassandraOperations; +---- +====== + +[float] +[[cassandra-template-bean-lookup-applicationcontext]] +=== Bean Lookup with `ApplicationContext` + +You can also look up the `[Reactive]CassandraTemplate` bean from the `ApplicationContext`, as shown in the following example: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +CassandraOperations cassandraOperations = applicationContext.getBean("cassandraTemplate", CassandraOperations.class); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +ReactiveCassandraOperations cassandraOperations = applicationContext.getBean("ReactiveCassandraOperations", ReactiveCassandraOperations.class); +---- +====== + +[[cassandra.template.query]] +== Querying Rows + +You can express your queries by using the `Query` and `Criteria` classes, which have method names that reflect the native Cassandra predicate operator names, such as `lt`, `lte`, `is`, and others. + +The `Query` and `Criteria` classes follow a fluent API style so that you can easily chain together multiple method criteria and queries while having easy-to-understand code. +Static imports are used in Java when creating `Query` +and `Criteria` instances to improve readability. + +[[cassandra.template.query.table]] +=== Querying Rows in a Table + +In earlier sections, we saw how to retrieve a single object by using the `selectOneById` method on `[Reactive]CassandraTemplate`. +Doing so returns a single domain object. +We can also query for a collection of rows to be returned as a list of domain objects. +Assuming we have a number of `Person` objects with name and age values stored as rows in a table and that each person has an account balance, we can now run a query by using the following code: + +.Querying for rows using `[Reactive]CassandraTemplate` +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import static org.springframework.data.cassandra.core.query.Query.query; + +… + +List result = cassandraTemplate.select(query(where("age").is(50)) + .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import static org.springframework.data.cassandra.core.query.Query.query; + +… + +Flux result = reactiveCassandraTemplate.select(query(where("age").is(50)) + .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class); +---- +====== + +The `select`, `selectOne`, and `stream` methods take a `Query` object as a parameter. +This object defines the criteria and options used to perform the query. +The criteria is specified by using a `Criteria` object that has a static factory method named `where` that instantiates a new `Criteria` object. +We recommend using a static import for `org.springframework.data.cassandra.core.query.Criteria.where` and `Query.query`, to make the query more readable. + +This query should return a list of `Person` objects that meet the specified criteria. +The `Criteria` class has the following methods that correspond to the operators provided in Apache Cassandra: + +[[cassandra.template.query.criteria]] +==== Methods for the Criteria class + +* `CriteriaDefinition` *gt* `(Object value)`: Creates a criterion by using the `>` operator. +* `CriteriaDefinition` *gte* `(Object value)`: Creates a criterion by using the `>=` operator. +* `CriteriaDefinition` *in* `(Object... values)`: Creates a criterion by using the `IN` operator for a varargs argument. +* `CriteriaDefinition` *in* `(Collection collection)`: Creates a criterion by using the `IN` operator using a collection. +* `CriteriaDefinition` *is* `(Object value)`: Creates a criterion by using field matching (`column = value`). +* `CriteriaDefinition` *lt* `(Object value)`: Creates a criterion by using the `<` operator. +* `CriteriaDefinition` *lte* `(Object value)`: Creates a criterion by using the `<=` operator. +* `CriteriaDefinition` *like* `(Object value)`: Creates a criterion by using the `LIKE` operator. +* `CriteriaDefinition` *contains* `(Object value)`: Creates a criterion by using the `CONTAINS` operator. +* `CriteriaDefinition` *containsKey* `(Object key)`: Creates a criterion by using the `CONTAINS KEY` operator. + +`Criteria` is immutable once created. + +[[cassandra.template.query.query-class]] +=== Methods for the Query class + +The `Query` class has some additional methods that you can use to provide options for the query: + +* `Query` *by* `(CriteriaDefinition... criteria)`: Used to create a `Query` object. +* `Query` *and* `(CriteriaDefinition criteria)`: Used to add additional criteria to the query. +* `Query` *columns* `(Columns columns)`: Used to define columns to be included in the query results. +* `Query` *limit* `(Limit limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting). +* `Query` *limit* `(long limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting). +* `Query` *pageRequest* `(Pageable pageRequest)`: Used to associate `Sort`, `PagingState`, and `fetchSize` with the query (used for paging). +* `Query` *pagingState* `(ByteBuffer pagingState)`: Used to associate a `ByteBuffer` with the query (used for paging). +* `Query` *queryOptions* `(QueryOptions queryOptions)`: Used to associate `QueryOptions` with the query. +* `Query` *sort* `(Sort sort)`: Used to provide a sort definition for the results. +* `Query` *withAllowFiltering* `()`: Used to render `ALLOW FILTERING` queries. + +`Query` is immutable once created. +Invoking methods creates new immutable (intermediate) `Query` objects. + +[[cassandra.template.query.rows]] +=== Methods for Querying for Rows + +The `Query` class has the following methods that return rows: + +* `List` *select* `(Query query, Class entityClass)`: Query for a list of objects of type `T` from the table. +* `T` *selectOne* `(Query query, Class entityClass)`: Query for a single object of type `T` from the table. +* `Slice` *slice* `(Query query, Class entityClass)`: Starts or continues paging by querying for a `Slice` of objects of type `T` from the table. +* `Stream` *stream* `(Query query, Class entityClass)`: Query for a stream of objects of type `T` from the table. +* `List` *select* `(String cql, Class entityClass)`: Ad-hoc query for a list of objects of type `T` from the table by providing a CQL statement. +* `T` *selectOne* `(String cql, Class entityClass)`: Ad-hoc query for a single object of type `T` from the table by providing a CQL statement. +* `Stream` *stream* `(String cql, Class entityClass)`: Ad-hoc query for a stream of objects of type `T` from the table by providing a CQL statement. + +The query methods must specify the target type `T` that is returned. + +[[cassandra.template.query.fluent-template-api]] +=== Fluent Template API + +The `[Reactive]CassandraOperations` interface is one of the central components when it comes to more low-level interaction with Apache Cassandra. +It offers a wide range of methods. +You can find multiple overloads for every method. +Most of them cover optional (nullable) parts of the API. + +`FluentCassandraOperations` and its reactive variant `ReactiveFluentCassandraOperations` provide a more narrow interface for common methods of `[Reactive]CassandraOperations` +providing a more readable, fluent API. +The entry points (`query(…)`, `insert(…)`, `update(…)`, and `delete(…)`) follow a natural naming scheme based on the operation to execute. +Moving on from the entry point, the API is designed to offer only context-dependent methods that guide the developer towards a terminating method that invokes the actual `[Reactive]CassandraOperations`. +The following example shows the fluent API: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +List all = ops.query(SWCharacter.class) + .inTable("star_wars") <1> + .all(); +---- +<1> Skip this step if `SWCharacter` defines the table name with `@Table` or if using the class name as the table name is not a problem + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +Flux all = ops.query(SWCharacter.class) + .inTable("star_wars") <1> + .all(); +---- +<1> Skip this step if `SWCharacter` defines the table name with `@Table` or if using the class name as the table name is not a problem +====== + +If a table in Cassandra holds entities of different types, such as a `Jedi` within a Table of `SWCharacters`, you can use different types to map the query result. +You can use `as(Class targetType)` to map results to a different target type, while `query(Class entityType)` still applies to the query and table name. +The following example uses the `query` and `as` methods: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +List all = ops.query(SWCharacter.class) <1> + .as(Jedi.class) <2> + .matching(query(where("jedi").is(true))) + .all(); +---- +<1> The query fields are mapped against the `SWCharacter` type. +<2> Resulting rows are mapped into `Jedi`. + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +Flux all = ops.query(SWCharacter.class) <1> + .as(Jedi.class) <2> + .matching(query(where("jedi").is(true))) + .all(); +---- +<1> The query fields are mapped against the `SWCharacter` type. +<2> Resulting rows are mapped into `Jedi`. +====== + +TIP: You can directly apply xref:repositories/projections.adoc[] to resulting documents by providing only the `interface` type through `as(Class)`. + +The terminating methods (`first()`, `one()`, `all()`, and `stream()`) handle switching between retrieving a single entity and retrieving multiple entities as `List` or `Stream` and similar operations. + +WARNING: The new fluent template API methods (that is, `query(..)`, `insert(..)`, `update(..)`, and `delete(..)`) use effectively thread-safe supporting objects to compose the CQL statement. +However, it comes at the added cost of additional young-gen JVM heap overhead, since the design is based on final fields for the various CQL statement components and construction on mutation. +You should be careful when possibly inserting or deleting a large number of objects (such as inside of a loop, for instance). + +[[cassandra-template.save-update-remove]] +== Saving, Updating, and Removing Rows + +`[Reactive]CassandraTemplate` provides a simple way for you to save, update, and delete your domain objects and map those objects to tables managed in Cassandra. + +[[cassandra.template.type-mapping]] +=== Type Mapping + +Spring Data for Apache Cassandra relies on the DataStax Java driver's `CodecRegistry` to ensure type support. +As types are added or changed, the Spring Data for Apache Cassandra module continues to function without requiring changes. +See https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cql_data_types_c.html[CQL data types] +and "`xref:object-mapping.adoc#mapping-conversion[Data Mapping and Type Conversion]`" for the current type mapping matrix. + +[[cassandra.template.insert-update]] +=== Methods for Inserting and Updating rows + +`[Reactive]CassandraTemplate` has several convenient methods for saving and inserting your objects. +To have more fine-grained control over the conversion process, you can register Spring `Converter` instances with the `MappingCassandraConverter` +(for example, `Converter`). + +NOTE: The difference between insert and update operations is that `INSERT` operations do not insert `null` values. + +The simple case of using the `INSERT` operation is to save a POJO. +In this case, the table name is determined by the simple class name (not the fully qualified class name). +The table to store the object can be overridden by using mapping metadata. + +When inserting or updating, the `id` property must be set. +Apache Cassandra has no means to generate an ID. + +The following example uses the save operation and retrieves its contents: + +.Inserting and retrieving objects by using the `[Reactive]CassandraTemplate` +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import static org.springframework.data.cassandra.core.query.Query.query; +… + +Person bob = new Person("Bob", 33); +cassandraTemplate.insert(bob); + +Person queriedBob = cassandraTemplate.selectOneById(query(where("age").is(33)), Person.class); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import static org.springframework.data.cassandra.core.query.Query.query; +… + +Person bob = new Person("Bob", 33); +cassandraTemplate.insert(bob); + +Mono queriedBob = reactiveCassandraTemplate.selectOneById(query(where("age").is(33)), Person.class); +---- +====== + +You can use the following operations to insert and save: + +* `void` *insert* `(Object objectToSave)`: Inserts the object in an Apache Cassandra table. +* `WriteResult` *insert* `(Object objectToSave, InsertOptions options)`: Inserts the object in an Apache Cassandra table and applies `InsertOptions`. + +You can use the following update operations: + +* `void` *update* `(Object objectToSave)`: Updates the object in an Apache Cassandra table. +* `WriteResult` *update* `(Object objectToSave, UpdateOptions options)`: Updates the object in an Apache Cassandra table and applies `UpdateOptions`. + +You can also use the old fashioned way and write your own CQL statements, as the following example shows: + +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')"; + +cassandraTemplate().getCqlOperations().execute(cql); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')"; + +Mono applied = reactiveCassandraTemplate.getReactiveCqlOperations().execute(cql); +---- +====== + +You can also configure additional options such as TTL, consistency level, and lightweight transactions when using `InsertOptions` and `UpdateOptions`. + +[[cassandra.template.insert-update.table]] +==== Which Table Are My Rows Inserted into? + +You can manage the table name that is used for operating on the tables in two ways. +The default table name is the simple class name changed to start with a lower-case letter. +So, an instance of the `com.example.Person` class would be stored in the `person` table. +The second way is to specify a table name in the `@Table` annotation. + +[[cassandra.template.batch]] +==== Inserting, Updating, and Deleting Individual Objects in a Batch + +The Cassandra protocol supports inserting a collection of rows in one operation by using a batch. + +The following methods in the `[Reactive]CassandraTemplate` interface support this functionality: + +* `batchOps`: Creates a new `[Reactive]CassandraBatchOperations` to populate the batch. + +`[Reactive]CassandraBatchOperations` + +* `insert`: Takes a single object, an array (var-args), or an `Iterable` of objects to insert. +* `update`: Takes a single object, an array (var-args), or an `Iterable` of objects to update. +* `delete`: Takes a single object, an array (var-args), or an `Iterable` of objects to delete. +* `withTimestamp`: Applies a TTL to the batch. +* `execute`: Executes the batch. + +[[cassandra.template.update]] +=== Updating Rows in a Table + +For updates, you can select to update a number of rows. + +The following example shows updating a single account object by adding a one-time $50.00 bonus to the balance with the `+` assignment: + +.Updating rows using `[Reactive]CasandraTemplate` +[tabs] +====== +Imperative:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.data.cassandra.core.query.Update; + +… + +boolean applied = cassandraTemplate.update(Query.query(where("id").is("foo")), + Update.create().increment("balance", 50.00), Account.class); +---- + +Reactive:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- +import static org.springframework.data.cassandra.core.query.Criteria.where; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.data.cassandra.core.query.Update; + +… + +Mono wasApplied = reactiveCassandraTemplate.update(Query.query(where("id").is("foo")), + Update.create().increment("balance", 50.00), Account.class); +---- +====== + +In addition to the `Query` discussed earlier, we provide the update definition by using an `Update` object. +The `Update` class has methods that match the update assignments available for Apache Cassandra. + +Most methods return the `Update` object to provide a fluent API for code styling purposes. + +[[cassandra.template.update.methods]] +==== Methods for Executing Updates for Rows + +The update method can update rows, as follows: + +* `boolean` *update* `(Query query, Update update, Class entityClass)`: Updates a selection of objects in the Apache Cassandra table. + +[[cassandra.template.update.update]] +==== Methods for the Update class + +The `Update` class can be used with a little 'syntax sugar', as its methods are meant to be chained together. +Also, you can kick-start the creation of a new `Update` instance with the static method `public static Update update(String key, Object value)` and by using static imports. + +The `Update` class has the following methods: + +* `AddToBuilder` *addTo* `(String columnName)` `AddToBuilder` entry-point: +** Update `prepend(Object value)`: Prepends a collection value to the existing collection by using the `+` update assignment. + ** Update `prependAll(Object... values)`: Prepends all collection values to the existing collection by using the `+` update assignment. +** Update `append(Object value)`: Appends a collection value to the existing collection by using the `+` update assignment. + ** Update `append(Object... values)`: Appends all collection values to the existing collection by using the `+` update assignment. +** Update `entry(Object key, Object value)`: Adds a map entry by using the `+` update assignment. + ** Update `addAll(Map map)`: Adds all map entries to the map by using the `+` update assignment. +* `Update` *remove* `(String columnName, Object value)`: Removes the value from the collection by using the `-` update assignment. +* `Update` *clear* `(String columnName)`: Clears the collection. +* `Update` *increment* `(String columnName, Number delta)`: Updates by using the `+` update assignment. +* `Update` *decrement* `(String columnName, Number delta)`: Updates by using the `-` update assignment. +* `Update` *set* `(String columnName, Object value)`: Updates by using the `=` update assignment. +* `SetBuilder` *set* `(String columnName)` `SetBuilder` entry-point: +** Update `atIndex(int index).to(Object value)`: Sets a collection at the given index to a value using the `=` update assignment. +** Update `atKey(String object).to(Object value)`: Sets a map entry at the given key to a value the `=` update assignment. + +The following listing shows a few update examples: + +==== +[source] +---- +// UPDATE … SET key = 'Spring Data'; +Update.update("key", "Spring Data") + +// UPDATE … SET key[5] = 'Spring Data'; +Update.empty().set("key").atIndex(5).to("Spring Data"); + +// UPDATE … SET key = key + ['Spring', 'DATA']; +Update.empty().addTo("key").appendAll("Spring", "Data"); +---- +==== + +Note that `Update` is immutable once created. +Invoking methods creates new immutable (intermediate) `Update` objects. + +[[cassandra.template.delete]] +=== Methods for Removing Rows + +You can use the following overloaded methods to remove an object from the database: + +* `boolean` *delete* `(Query query, Class entityClass)`: Deletes the objects selected by `Query`. +* `T` *delete* `(T entity)`: Deletes the given object. +* `T` *delete* `(T entity, QueryOptions queryOptions)`: Deletes the given object applying `QueryOptions`. +* `boolean` *deleteById* `(Object id, Class entityClass)`: Deletes the object using the given Id. + +[[cassandra.template.optimistic-locking]] +=== Optimistic Locking + +The `@Version` annotation provides syntax similar to that of JPA in the context of Cassandra and makes sure updates are only applied to rows with a matching version. +Optimistic Locking leverages Cassandra's lightweight transactions to conditionally insert, update and delete rows. +Therefore, `INSERT` statements are executed with the `IF NOT EXISTS` condition. +For updates and deletes, the actual value of the version property is added to the `UPDATE` condition in such a way that the modification does not have any effect if another operation altered the row in the meantime. +In that case, an `OptimisticLockingFailureException` is thrown. +The following example shows these features: + +==== +[source,java] +---- +@Table +class Person { + + @Id String id; + String firstname; + String lastname; + @Version Long version; +} + +Person daenerys = template.insert(new Person("Daenerys")); <1> + +Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); <2> + +daenerys.setLastname("Targaryen"); +template.save(daenerys); <3> + +template.save(tmp); // throws OptimisticLockingFailureException <4> +---- + +<1> Intially insert document. `version` is set to `0`. +<2> Load the just inserted document. `version` is still `0`. +<3> Update the document with `version = 0`. +Set the `lastname` and bump `version` to `1`. +<4> Try to update the previously loaded document that still has `version = 0`. +The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`. +==== + +NOTE: Optimistic Locking is only supported with single-entity operations and not for batch operations. diff --git a/src/main/antora/modules/ROOT/pages/commons/upgrade.adoc b/src/main/antora/modules/ROOT/pages/commons/upgrade.adoc new file mode 100644 index 000000000..51a9189aa --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/commons/upgrade.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$upgrade.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/index.adoc b/src/main/antora/modules/ROOT/pages/index.adoc new file mode 100644 index 000000000..c91843cdd --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/index.adoc @@ -0,0 +1,22 @@ +[[spring-data-cassandra-reference-documentation]] += Spring Data Cassandra +:revnumber: {version} +:revdate: {localdate} +:feature-scroll: true + +_Spring Data for Apache Cassandra provides repository support for the Apache Cassandra database. +It eases development of applications with a consistent programming model that need to access Cassandra data sources._ + +[horizontal] +xref:cassandra.adoc[Cassandra] :: Apache Cassandra support and connectivity +xref:repositories.adoc[Repositories] :: Apache Cassandra Repositories +xref:observability.adoc[Observability] :: Observability Integration +xref:kotlin.adoc[Kotlin] :: Kotlin support +xref:migration-guides.adoc[Migration] :: Migration Guides +https://github.com/spring-projects/spring-data-commons/wiki[Wiki] :: What's New, Upgrade Notes, Supported Versions, additional cross-version information. + +David Webb, Matthew Adams, John Blum, Mark Paluch, Jay Bryant + +(C) 2008-2023 VMware, Inc. + +Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. diff --git a/src/main/antora/modules/ROOT/pages/kotlin.adoc b/src/main/antora/modules/ROOT/pages/kotlin.adoc new file mode 100644 index 000000000..4f01678d8 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/kotlin.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$kotlin.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/kotlin/coroutines.adoc b/src/main/antora/modules/ROOT/pages/kotlin/coroutines.adoc new file mode 100644 index 000000000..8f578961c --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/kotlin/coroutines.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$kotlin/coroutines.adoc[] diff --git a/src/main/asciidoc/reference/kotlin.adoc b/src/main/antora/modules/ROOT/pages/kotlin/extensions.adoc similarity index 82% rename from src/main/asciidoc/reference/kotlin.adoc rename to src/main/antora/modules/ROOT/pages/kotlin/extensions.adoc index e4e0f86e0..7ee0009e8 100644 --- a/src/main/asciidoc/reference/kotlin.adoc +++ b/src/main/antora/modules/ROOT/pages/kotlin/extensions.adoc @@ -1,6 +1,4 @@ -include::../{spring-data-commons-docs}/kotlin.adoc[] - -include::../{spring-data-commons-docs}/kotlin-extensions.adoc[leveloffset=+1] +include::{commons}@data-commons::page$kotlin/extensions.adoc[] To retrieve a list of `SWCharacter` objects in Java, you would normally write the following: @@ -24,5 +22,3 @@ Spring Data for Apache Cassandra provides the following extensions: * Reified generics support for `CassandraOperations` (including async and reactive variants), `CqlOperations` (including async and reactive variants)`FluentCassandraOperations`, `ReactiveFluentCassandraOperations`, `Criteria`, and `Query`. * <> extensions for `ReactiveFluentCassandraOperations`. - -include::../{spring-data-commons-docs}/kotlin-coroutines.adoc[leveloffset=+1] diff --git a/src/main/antora/modules/ROOT/pages/kotlin/null-safety.adoc b/src/main/antora/modules/ROOT/pages/kotlin/null-safety.adoc new file mode 100644 index 000000000..6967ddb3f --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/kotlin/null-safety.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$kotlin/null-safety.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc b/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc new file mode 100644 index 000000000..ba2301bd7 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/kotlin/object-mapping.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$kotlin/object-mapping.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/kotlin/requirements.adoc b/src/main/antora/modules/ROOT/pages/kotlin/requirements.adoc new file mode 100644 index 000000000..bb209ab6a --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/kotlin/requirements.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$kotlin/requirements.adoc[] diff --git a/src/main/asciidoc/reference/migration-guide-1.5-to-2.0.adoc b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-1.5-to-2.0.adoc similarity index 92% rename from src/main/asciidoc/reference/migration-guide-1.5-to-2.0.adoc rename to src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-1.5-to-2.0.adoc index 985d8ae7c..f176b4d06 100644 --- a/src/main/asciidoc/reference/migration-guide-1.5-to-2.0.adoc +++ b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-1.5-to-2.0.adoc @@ -1,5 +1,5 @@ [[cassandra.migration.1.x-to-2.x]] -= Migration Guide from Spring Data Cassandra 1.x to 2.x += Migration Guide from 1.x to 2.x Spring Data for Apache Cassandra 2.0 introduces a set of breaking changes when upgrading from earlier versions: @@ -14,6 +14,7 @@ into dedicated interfaces and templates. * Refactored `QueryOptions` to be immutable objects. * Refactored `CassandraPersistentProperty` to single-column. +[[deprecations]] == Deprecations * Deprecated `QueryOptionsBuilder.readTimeout(long, TimeUnit)` in favor of `QueryOptionsBuilder.readTimeout(Duration)`. @@ -25,6 +26,7 @@ into dedicated interfaces and templates. * Deprecated constructors of `QueryOptions` in favor of their builders. * Deprecated `TypedIdCassandraRepository` in favor of `CassandraRepository` +[[merged-spring-cql-and-spring-data-cassandra-modules]] == Merged Spring CQL and Spring Data Cassandra Modules Spring CQL and Spring Data Cassandra are now merged into a single module. @@ -57,6 +59,7 @@ With the merge, we merged all CQL packages into Spring Data Cassandra: * Moved `o.s.d.c.mapping` to `o.s.d.c.core.mapping` (affects mapping annotations). * Moved `MapId` from `o.s.d.c.repository` to `o.s.d.c.core.mapping`. +[[revised-cqltemplate/cassandratemplate]] == Revised `CqlTemplate`/`CassandraTemplate` We split `CqlTemplate` and `CassandraTemplate` in three ways: @@ -70,6 +73,7 @@ versus `execute(…)`) and the reduced method set is aligned with Spring Framewo * Asynchronous methods are re-implemented on `AsyncCqlTemplate` and `AsyncCassandraTemplate` by using `ListenableFuture`. We removed `Cancellable` and the various async callback listeners. `ListenableFuture` is a flexible approach and allows transition into a `CompletableFuture`. +[[removed-cassandraoperations-selectbysimpleids]] == Removed `CassandraOperations.selectBySimpleIds()` The method was removed because it did not support complex IDs. @@ -82,6 +86,7 @@ cassandraTemplate.select(Query.query(Criteria.where("id").in(…)), Person.class ---- ==== +[[better-names-for-cassandrarepository]] == Better names for `CassandraRepository` We renamed `CassandraRepository` and `TypedIdCassandraRepository` to align Spring Data Cassandra naming with other Spring Data modules: @@ -90,6 +95,7 @@ We renamed `CassandraRepository` and `TypedIdCassandraRepository` to align Sprin * Renamed `TypedIdCassandraRepository` to `CassandraRepository` * Introduced `TypedIdCassandraRepository`, extending `CassandraRepository` as a deprecated type to ease migration +[[removed-sd-cassandra-consistencylevel-and-retrypolicy-types-in-favor-of-datastax-consistencylevel-and-retrypolicy-types]] == Removed SD Cassandra `ConsistencyLevel` and `RetryPolicy` types in favor of DataStax `ConsistencyLevel` and `RetryPolicy` types Spring Data Cassandra `ConsistencyLevel` and `RetryPolicy` have been removed. @@ -98,12 +104,14 @@ Please use the types provided by the DataStax driver. The Spring Data Cassandra types restricted usage of available features provided in and allowed by the Cassandra native driver. As a result, the Spring Data Cassandra's types required an update each time newer functionality was introduced by the driver. +[[refactored-cql-specifications-to-value-objects-and-configurators]] == Refactored CQL Specifications to Value Objects and Configurators As much as possible, CQL specification types are now value types (such as `FieldSpecification`, `AlterColumnSpecification`), and objects are constructed by static factory methods. This allows immutability for simple value objects. Configurator objects (such as `AlterTableSpecification`) that operate on mandatory properties (such as a table name or keyspace name) are initially constructed through a a static factory method and allow further configuration until the desired state is created. +[[refactored-queryoptions-to-be-immutable-objects]] == Refactored `QueryOptions` to be Immutable Objects `QueryOptions` and `WriteOptions` are now immutable and can be created through builders. @@ -122,6 +130,7 @@ QueryOptions queryOptions = QueryOptions.builder() .build(); ---- +[[refactored-cassandrapersistentproperty-to-single-column]] == Refactored `CassandraPersistentProperty` to Single-column This change affects You only if you operate directly on the mapping model. diff --git a/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-2.2-to-3.0.adoc similarity index 96% rename from src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc rename to src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-2.2-to-3.0.adoc index 3a3e535dd..89731ee97 100644 --- a/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc +++ b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-2.2-to-3.0.adoc @@ -1,13 +1,15 @@ [[cassandra.migration.2.x-to-3.x]] -= Migration Guide from Spring Data Cassandra 2.x to 3.x += Migration Guide from 2.x to 3.x Spring Data for Apache Cassandra 3.0 introduces a set of breaking changes when upgrading from earlier versions. +[[review-dependencies]] == Review dependencies Upgrading to Spring Data Cassandra requires an upgrade to the DataStax Driver version 4. Upgrading to the new driver comes with transitive dependency changes, most notably, Google Guava is bundled and shaded by the driver. Check out the https://docs.datastax.com/en/developer/java-driver/4.3/upgrade_guide/[DataStax Java Driver for Apache Cassandra 4 Upgrade Guide] for details on the Driver-related changes. +[[adapt-configuration]] == Adapt Configuration DataStax Java Driver 4 merges `Cluster` and `Session` objects into a single `CqlSession` object, therefore, all `Cluster`-related API was removed. @@ -19,6 +21,7 @@ If you're using XML-based configuration, make sure to migrate all configuration To reflect the change in configuration builders, `ClusterBuilderConfigurer` was renamed to `SessionBuilderConfigurer` accepting now `CqlSessionBuilder` instead of the `Cluster.Builder`. Make sure to also provide the local data center in your configuration as it is required to properly configure load balancing. +[[connectivity]] === Connectivity The configuration elements for `Cluster` (`cassandra:cluster`) and `Session` (`cassandra:session`) were merged into a single `CqlSession` (`cassandra:session`) element that configures both, the keyspace and endpoints. @@ -56,6 +59,7 @@ With the upgrade, schema support was moved to a new namespace element: `cassandr NOTE: Spring Data Cassandra 3.0 no longer registers default Mapping Context, Context and Template API beans when using XML namespace configuration. The defaulting should be applied on application or Spring Boot level. +[[template-api]] == Template API Spring Data for Apache Cassandra encapsulates most of the changes that come with the driver upgrade as the Template API and repository support if your application mainly interacts with mapped entities or primitive Java types. @@ -94,6 +98,7 @@ Typical cases include: * Calls to `CqlTemplate.queryForResultSet(…)` * Calling methods that accept `Statement` +[[changes-in-asynccqltemplate]] === Changes in `AsyncCqlTemplate` DataStax driver 4 has changed the result type of queries that are run asynchronously. @@ -106,6 +111,7 @@ Result set extraction requires a new interface for DataStax' `AsyncResultSet`. `AsyncCqlTemplate` now uses `AsyncResultSetExtractor` in places where it used previously `ResultSetExtractor`. Note that `AsyncResultSetExtractor.extractData(…)` returns a `Future` instead of a scalar object so a migration of code comes with the possibility to use fully non-blocking code in the extractor. +[[data-model-migrations]] == Data model migrations Your data model may require updates if you use the following features: @@ -115,23 +121,27 @@ Your data model may require updates if you use the following features: * Properties using `java.lang.Date` * Properties using `UDTValue` or `TupleValue` +[[cassandratype]] === `@CassandraType` DataStax driver 4 no longer ships with a `Name` enumeration to describe the Cassandra type. We decided to re-introduce the enumeration with `CassandraType.Name`. Make sure to update your imports to use the newly introduced replacement type. +[[force-quote]] === Force Quote This flag is now deprecated, and we recommend not to use it any longer. Spring Data for Apache Cassandra internally uses the driver's `CqlIdentifier` that ensures quoting where it's required. +[[property-types]] === Property Types DataStax driver 4 no longer uses `java.lang.Date`. Please upgrade your data model to use `java.time.LocalDateTime`. Please also migrate raw UDT and tuple types to the new driver types `UdtValue` respective `TupleValue`. +[[other-changes]] == Other changes * Driver's `ConsistencyLevel` constant class was removed and reintroduced as `DefaultConsistencyLevel`. `@Consistency` was adapted to `DefaultConsistencyLevel`. @@ -148,6 +158,7 @@ Previously it returned just `ReactiveSession`. * Data type resolution was moved into `ColumnTypeResolver` so all `DataType`-related methods were moved from `CassandraPersistentEntity`/`CassandraPersistentProperty` into `ColumnTypeResolver` (affected methods are `MappingContext.getDataType(…)`, `CassandraPersistentProperty.getDataType()`, `CassandraPersistentEntity.getUserType()`, and `CassandraPersistentEntity.getTupleType()`). * Schema creation was moved from `MappingContext` to `SchemaFactory` (affected methods are `CassandraMappingContext.getCreateTableSpecificationFor(…)`, `CassandraMappingContext.getCreateIndexSpecificationsFor(…)`, and `CassandraMappingContext.getCreateUserTypeSpecificationFor(…)`). +[[deprecations]] == Deprecations * `CassandraCqlSessionFactoryBean`, use `CqlSessionFactoryBean` instead. @@ -164,8 +175,10 @@ Previously it returned just `ReactiveSession`. * Schema creation via `CqlSessionFactoryBean` (`cassandra:session`) is deprecated. Keyspace creation via `CqlSessionFactoryBean` (`cassandra:session`) is not affected. +[[removals]] == Removals +[[removal.configuration-api]] === Configuration API * `PoolingOptionsFactoryBean` @@ -178,6 +191,7 @@ Keyspace creation via `CqlSessionFactoryBean` (`cassandra:session`) is not affec * `AbstractClusterConfiguration` * `ClusterBuilderConfigurer` (use `SessionBuilderConfigurer` instead +[[utilities]] === Utilities * `GuavaListenableFutureAdapter` @@ -186,6 +200,7 @@ Use the builder in conjunction of execution profiles as replacement. * `CassandraAccessor.setRetryPolicy(…)` and `ReactiveCqlTemplate.setRetryPolicy(…)` methods. Use execution profiles as replacement. +[[removal.namespace-support]] === Namespace support * `cql` namespace (`http://www.springframework.org/schema/cql`, use `http://www.springframework.org/schema/data/cassandra` instead) @@ -194,8 +209,10 @@ Use execution profiles as replacement. * Removed implicit bean registrations Mapping Context, Context and Template API beans. These must be declared explicitly. +[[additions]] == Additions +[[add.configuration-api]] === Configuration API * `CqlSessionFactoryBean` @@ -203,6 +220,7 @@ These must be declared explicitly. * `SessionFactoryFactoryBean` including schema creation via `KeyspacePopulator` * `KeyspacePopulator` and `SessionFactoryInitializer` to initialize a keyspace +[[add.namespace-support]] === Namespace support * `cassandra:cluster` (endpoint properties merged to `cassandra:session`) diff --git a/src/main/asciidoc/reference/migration-guide-3.0-to-4.0.adoc b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-3.0-to-4.0.adoc similarity index 91% rename from src/main/asciidoc/reference/migration-guide-3.0-to-4.0.adoc rename to src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-3.0-to-4.0.adoc index c5ca0e199..3f72dea48 100644 --- a/src/main/asciidoc/reference/migration-guide-3.0-to-4.0.adoc +++ b/src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-3.0-to-4.0.adoc @@ -1,8 +1,9 @@ [[cassandra.migration.3.x-to-4.x]] -= Migration Guide from Spring Data Cassandra 3.x to 4.x += Migration Guide from 3.x to 4.x Spring Data for Apache Cassandra 4.0 introduces a set of breaking changes when upgrading from earlier versions. +[[asynchronous-template-api]] == Asynchronous Template API With the deprecation of `ListenableFuture`, `AsyncCqlOperations` and `AsyncCassandraOperations` and their dependant classes were migrated to `CompletableFuture`. diff --git a/src/main/antora/modules/ROOT/pages/migration-guides.adoc b/src/main/antora/modules/ROOT/pages/migration-guides.adoc new file mode 100644 index 000000000..7cb9de974 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/migration-guides.adoc @@ -0,0 +1,8 @@ +[[cassandra.migration]] += Migration Guides +:page-section-summary-toc: 1 + +This section contains version-specific migration guides explaining how to upgrade between two versions. + + + diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/antora/modules/ROOT/pages/object-mapping.adoc similarity index 83% rename from src/main/asciidoc/reference/mapping.adoc rename to src/main/antora/modules/ROOT/pages/object-mapping.adoc index 26ebccc42..5fb43b6f1 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/antora/modules/ROOT/pages/object-mapping.adoc @@ -9,7 +9,7 @@ The `MappingCassandraConverter` also lets you map domain objects to tables witho In this chapter, we describe the features of the `MappingCassandraConverter`, how to use conventions for mapping domain objects to tables, and how to override those conventions with annotation-based mapping metadata. -include::../{spring-data-commons-docs}/object-mapping.adoc[leveloffset=+1] +include::{commons}@data-commons::page$object-mapping.adoc[leveloffset=+1] [[mapping-conversion]] == Data Mapping and Type Conversion @@ -19,7 +19,7 @@ This section explains how types are mapped to and from an Apache Cassandra repre Spring Data for Apache Cassandra supports several types that are provided by Apache Cassandra. In addition to these types, Spring Data for Apache Cassandra provides a set of built-in converters to map additional types. You can provide your own custom converters to adjust type conversion. -See "`<>`" for further details. +See "`xref:cassandra/converters.adoc[Overriding Default Mapping with Custom Converters]`" for further details. The following table maps Spring Data types to Cassandra types: [cols="3,2",options="header"] @@ -150,7 +150,7 @@ The following example shows how to configure a `NamingStrategy`: ==== [source,java] ---- -include::../{example-root}/NamingStrategyConfiguration.java[tags=method] +include::example$NamingStrategyConfiguration.java[tags=method] ---- ==== @@ -167,7 +167,7 @@ The following example configuration class sets up Cassandra mapping support: ==== [source,java] ---- -include::../{example-root}/SchemaConfiguration.java[tags=class] +include::example$SchemaConfiguration.java[tags=class] ---- ==== @@ -308,7 +308,7 @@ The following example shows a class with a flat composite primary key: ==== [source,java] ---- -include::../{example-root}/LoginEvent.java[tags=class] +include::example$LoginEvent.java[tags=class] ---- ==== @@ -325,7 +325,7 @@ The following example shows a composite primary key class: ==== [source,java] ---- -include::../{example-root}/LoginEventKey.java[tags=class] +include::example$LoginEventKey.java[tags=class] ---- ==== @@ -465,7 +465,7 @@ The following example shows a more complex mapping: ==== [source,java] ---- -include::../{example-root}/mapping/Person.java[tags=class] +include::example$mapping/Person.java[tags=class] ---- ==== @@ -475,12 +475,12 @@ The following example shows how to map a UDT `Address`: ==== [source,java] ---- -include::../{example-root}/mapping/Address.java[tags=class] +include::example$mapping/Address.java[tags=class] ---- ==== NOTE: Working with User-Defined Types requires a `UserTypeResolver` that is configured with the mapping context. -See the <> for how to configure a `UserTypeResolver`. +See the xref:cassandra/configuration.adoc[configuration chapter] for how to configure a `UserTypeResolver`. The following example shows how map a tuple: @@ -488,7 +488,7 @@ The following example shows how map a tuple: ==== [source,java] ---- -include::../{example-root}/mapping/Coordinates.java[tags=class] +include::example$mapping/Coordinates.java[tags=class] ---- ==== @@ -509,7 +509,7 @@ The following example shows a number of ways to create an index: ==== [source,java] ---- -include::../{example-root}/mapping/PersonWithIndexes.java[tags=class] +include::example$mapping/PersonWithIndexes.java[tags=class] ---- ==== @@ -520,47 +520,10 @@ The `@Indexed` annotation can be applied to single properties of embedded entiti CAUTION: Index creation on session initialization may have a severe performance impact on application startup. -include::./converters.adoc[] - -include::../{spring-data-commons-docs}/is-new-state-detection.adoc[leveloffset=+1] +[[cassandra.entity-persistence.state-detection-strategies]] +include::{commons}@data-commons::page$is-new-state-detection.adoc[leveloffset=+1] NOTE: Cassandra provides no means to generate identifiers upon inserting data. As consequence, entities must be associated with identifier values. Spring Data defaults to identifier inspection to determine whether an entity is new. -If you want to use <> make sure to either use <> or implement `Persistable` for proper entity state detection. - -[[cassandra.mapping-usage.events]] -== Lifecycle Events - -The Cassandra mapping framework has several built-in `org.springframework.context.ApplicationEvent` events that your application can respond to by registering special beans in the `ApplicationContext`. -Being based on Spring's application context event infrastructure lets other products, such as Spring Integration, easily receive these events as they are a well known eventing mechanism in Spring-based applications. - -To intercept an object before it goes into the database, you can register a subclass of `org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener` that overrides the `onBeforeSave(…)` method. -When the event is dispatched, your listener is called and passed the domain object (which is a Java entity). -Entity lifecycle events can be costly and you may notice a change in the performance profile when loading large result sets. -You can disable lifecycle events on the link:https://docs.spring.io/spring-data/cassandra/docs/{version}/api/org/springframework/data/cassandra/core/CassandraTemplate.html#setEntityLifecycleEventsEnabled(boolean)[Template API]. -The following example uses the `onBeforeSave` method: - -==== -[source,java] ----- -include::../{example-root}/mapping/BeforeSaveListener.java[tags=class] ----- -==== - -Declaring these beans in your Spring `ApplicationContext` will cause them to be invoked whenever the event is dispatched. - -The `AbstractCassandraEventListener` has the following callback methods: - -* `onBeforeSave`: Called in `CassandraTemplate.insert(…)` and `.update(…)` operations before inserting or updating a row in the database. -* `onAfterSave`: Called in `CassandraTemplate…insert(…)` and `.update(…)` operations after inserting or updating a row in the database. -* `onBeforeDelete`: Called in `CassandraTemplate.delete(…)` operations before deleting row from the database. -* `onAfterDelete`: Called in `CassandraTemplate.delete(…)` operations after deleting row from the database. -* `onAfterLoad`: Called in the `CassandraTemplate.select(…)`, `.slice(…)`, and `.stream(…)` methods after each row is retrieved from the database. -* `onAfterConvert`: Called in the `CassandraTemplate.select(…)`, `.slice(…)`, and `.stream(…)` methods after converting a row retrieved from the database to a POJO. - -NOTE: Lifecycle events are emitted only for root-level types. -Complex types used as properties within an aggregate root are not subject to event publication. - -include::../{spring-data-commons-docs}/entity-callbacks.adoc[leveloffset=+1] -include::./cassandra-entity-callbacks.adoc[leveloffset=+2] +If you want to use xref:cassandra/auditing.adoc[auditing] make sure to either use xref:cassandra/template.adoc#cassandra.template.optimistic-locking[Optimistic Locking] or implement `Persistable` for proper entity state detection. diff --git a/src/main/asciidoc/reference/observability.adoc b/src/main/antora/modules/ROOT/pages/observability.adoc similarity index 90% rename from src/main/asciidoc/reference/observability.adoc rename to src/main/antora/modules/ROOT/pages/observability.adoc index 10e127d12..2ea651da6 100644 --- a/src/main/asciidoc/reference/observability.adoc +++ b/src/main/antora/modules/ROOT/pages/observability.adoc @@ -1,5 +1,5 @@ [[cassandra.observability]] -== Observability += Observability Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency. Spring Data Cassandra ships with a Micrometer instrumentation through the Cassandra driver to collect observations during Cassandra interaction. @@ -32,10 +32,10 @@ Also, registers `ObservationRequestTracker.INSTANCE` with the `CqlSessionBuilder <2> Wraps a CQL session object to observe reactive Cassandra statement execution. ==== -include::../observability/_conventions.adoc[] - -include::../observability/_metrics.adoc[] - -include::../observability/_spans.adoc[] - See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#cassandra[OpenTelemetry Semantic Conventions] for further reference. + +include::observability/conventions.adoc[leveloffset=+1] + +include::observability/metrics.adoc[leveloffset=+1] + +include::observability/spans.adoc[leveloffset=+1] diff --git a/src/main/asciidoc/observability/_conventions.adoc b/src/main/antora/modules/ROOT/pages/observability/conventions.adoc similarity index 92% rename from src/main/asciidoc/observability/_conventions.adoc rename to src/main/antora/modules/ROOT/pages/observability/conventions.adoc index 205edf269..7cbd02a61 100644 --- a/src/main/asciidoc/observability/_conventions.adoc +++ b/src/main/antora/modules/ROOT/pages/observability/conventions.adoc @@ -1,5 +1,5 @@ [[observability-conventions]] -=== Observability - Conventions += Conventions Below you can find a list of all `GlobalObservabilityConventions` and `ObservabilityConventions` declared by this project. diff --git a/src/main/asciidoc/observability/_metrics.adoc b/src/main/antora/modules/ROOT/pages/observability/metrics.adoc similarity index 95% rename from src/main/asciidoc/observability/_metrics.adoc rename to src/main/antora/modules/ROOT/pages/observability/metrics.adoc index 9efd4a9d1..2c3d29808 100644 --- a/src/main/asciidoc/observability/_metrics.adoc +++ b/src/main/antora/modules/ROOT/pages/observability/metrics.adoc @@ -1,10 +1,10 @@ [[observability-metrics]] -=== Observability - Metrics += Metrics Below you can find a list of all metrics declared by this project. [[observability-metrics-cassandra-query-observation]] -==== Cassandra Query Observation +== Cassandra Query Observation ____ Create an `io.micrometer.observation.Observation` for Cassandra-based queries. diff --git a/src/main/asciidoc/observability/_spans.adoc b/src/main/antora/modules/ROOT/pages/observability/spans.adoc similarity index 95% rename from src/main/asciidoc/observability/_spans.adoc rename to src/main/antora/modules/ROOT/pages/observability/spans.adoc index 18f990455..a149243f7 100644 --- a/src/main/asciidoc/observability/_spans.adoc +++ b/src/main/antora/modules/ROOT/pages/observability/spans.adoc @@ -1,10 +1,10 @@ [[observability-spans]] -=== Observability - Spans += Spans Below you can find a list of all spans declared by this project. [[observability-spans-cassandra-query-observation]] -==== Cassandra Query Observation Span +== Cassandra Query Observation Span > Create an `io.micrometer.observation.Observation` for Cassandra-based queries. diff --git a/src/main/asciidoc/preface.adoc b/src/main/antora/modules/ROOT/pages/preface.adoc similarity index 99% rename from src/main/asciidoc/preface.adoc rename to src/main/antora/modules/ROOT/pages/preface.adoc index 8720c3c5d..42b4f89b8 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/antora/modules/ROOT/pages/preface.adoc @@ -62,6 +62,7 @@ Spring Data for Apache Cassandra 2.x binaries require JDK level 8.0 and later an It requires https://cassandra.apache.org/[Cassandra] 2.0 or later and Datastax driver 4.x. +[[additional-help-resources]] == Additional Help Resources Learning a new framework is not always straight forward. diff --git a/src/main/antora/modules/ROOT/pages/repositories.adoc b/src/main/antora/modules/ROOT/pages/repositories.adoc new file mode 100644 index 000000000..ba73cea7b --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories.adoc @@ -0,0 +1,8 @@ +[[cassandra.repositories]] += Repositories +:page-section-summary-toc: 1 + +This chapter explains the basic foundations of Spring Data repositories and Cassandra specifics. +Before continuing to the Cassandra specifics, make sure you have a sound understanding of the basic concepts. + +The goal of the Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores. diff --git a/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc b/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc new file mode 100644 index 000000000..4ae3ce676 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/core-concepts.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/core-domain-events.adoc b/src/main/antora/modules/ROOT/pages/repositories/core-domain-events.adoc new file mode 100644 index 000000000..f84313e9d --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/core-domain-events.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/core-domain-events.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc b/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc new file mode 100644 index 000000000..a7c2ff8d3 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/core-extensions.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/create-instances.adoc b/src/main/antora/modules/ROOT/pages/repositories/create-instances.adoc new file mode 100644 index 000000000..2ae01801b --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/create-instances.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/create-instances.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc b/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc new file mode 100644 index 000000000..c7615191a --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/custom-implementations.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/definition.adoc b/src/main/antora/modules/ROOT/pages/repositories/definition.adoc new file mode 100644 index 000000000..bd65a8af8 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/definition.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/definition.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/null-handling.adoc b/src/main/antora/modules/ROOT/pages/repositories/null-handling.adoc new file mode 100644 index 000000000..081bac9f6 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/null-handling.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/null-handling.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/projections.adoc b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc new file mode 100644 index 000000000..875f61abe --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/projections.adoc @@ -0,0 +1,4 @@ +[[cassandra.projections]] += Projections + +include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1] diff --git a/src/main/antora/modules/ROOT/pages/repositories/query-keywords-reference.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-keywords-reference.adoc new file mode 100644 index 000000000..e495eddc6 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/query-keywords-reference.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/query-keywords-reference.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc new file mode 100644 index 000000000..dfe481495 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/query-methods-details.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/repositories/query-return-types-reference.adoc b/src/main/antora/modules/ROOT/pages/repositories/query-return-types-reference.adoc new file mode 100644 index 000000000..a73c3201d --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/repositories/query-return-types-reference.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$repositories/query-return-types-reference.adoc[] diff --git a/src/main/antora/resources/antora-resources/antora.yml b/src/main/antora/resources/antora-resources/antora.yml new file mode 100644 index 000000000..c9339559f --- /dev/null +++ b/src/main/antora/resources/antora-resources/antora.yml @@ -0,0 +1,20 @@ +version: ${antora-component.version} +prerelease: ${antora-component.prerelease} + +asciidoc: + attributes: + version: ${project.version} + springversionshort: ${spring.short} + springversion: ${spring} + attribute-missing: 'warn' + commons: ${springdata.commons.docs} + include-xml-namespaces: false + spring-data-commons-docs-url: https://docs.spring.io/spring-data-commons/reference + spring-data-commons-javadoc-base: https://docs.spring.io/spring-data/commons/docs/${springdata.commons}/api/ + springdocsurl: https://docs.spring.io/spring-framework/reference/{springversionshort} + springjavadocurl: https://docs.spring.io/spring-framework/docs/${spring}/javadoc-api + spring-framework-docs: '{springdocsurl}' + spring-framework-javadoc: '{springjavadocurl}' + springhateoasversion: ${spring-hateoas} + releasetrainversion: ${releasetrain} + store: Cassandra diff --git a/src/main/asciidoc/images/epub-cover.png b/src/main/asciidoc/images/epub-cover.png deleted file mode 100644 index 17f5293a933abfcbc99698f5ae5650fedd9634a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52562 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 WL1d><=QlG~BX1a7CtkIS`hNh&@f-aB diff --git a/src/main/asciidoc/images/epub-cover.svg b/src/main/asciidoc/images/epub-cover.svg deleted file mode 100644 index 71366fd22..000000000 --- a/src/main/asciidoc/images/epub-cover.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Spring Data Cassandra - Reference Guide - David Webb, Matthew Adams, - John Blum, Mark Paluch, - Jay Bryant - diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc deleted file mode 100644 index bd52bb0fc..000000000 --- a/src/main/asciidoc/index.adoc +++ /dev/null @@ -1,44 +0,0 @@ -= Spring Data for Apache Cassandra - Reference Documentation -David Webb, Matthew Adams, John Blum, Mark Paluch, Jay Bryant -:revnumber: {version} -:revdate: {localdate} -ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]] -:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc -:example-root: ../../../spring-data-cassandra/src/test/java/org/springframework/data/cassandra/example -:example-resources: ../../../spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/example -:tabsize: 2 - -(C) 2008-2023 The original author(s). - -NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. - -include::preface.adoc[] - -include::{spring-data-commons-docs}/dependencies.adoc[leveloffset=+1] - -include::{spring-data-commons-docs}/repositories.adoc[leveloffset=+1] - -[[reference]] -= Reference Documentation - -include::reference/introduction.adoc[leveloffset=+1] -include::reference/upgrade.adoc[leveloffset=+1] -include::reference/cassandra.adoc[leveloffset=+1] -include::reference/observability.adoc[leveloffset=+1] -include::reference/reactive-cassandra.adoc[leveloffset=+1] -include::reference/cassandra-repositories.adoc[leveloffset=+1] -include::reference/reactive-cassandra-repositories.adoc[leveloffset=+1] -include::{spring-data-commons-docs}/auditing.adoc[leveloffset=+1] -include::reference/cassandra-auditing.adoc[leveloffset=+1] -include::reference/mapping.adoc[leveloffset=+1] -include::reference/kotlin.adoc[leveloffset=+1] - -[[appendix]] -= Appendix - -:numbered!: -include::{spring-data-commons-docs}/repository-namespace-reference.adoc[leveloffset=+1] -include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[leveloffset=+1] -include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[leveloffset=+1] -include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[leveloffset=+1] -include::reference/migration-guides.adoc[leveloffset=+1] diff --git a/src/main/asciidoc/reference/cassandra-entity-callbacks.adoc b/src/main/asciidoc/reference/cassandra-entity-callbacks.adoc deleted file mode 100644 index df1037874..000000000 --- a/src/main/asciidoc/reference/cassandra-entity-callbacks.adoc +++ /dev/null @@ -1,30 +0,0 @@ -[[cassandra.entity-callbacks]] -= Store specific EntityCallbacks - -Spring Data for Apache Cassandra uses the `EntityCallback` API for its auditing support and reacts on the following callbacks. - -.Supported Entity Callbacks -[%header,cols="4"] -|=== -| Callback -| Method -| Description -| Order - -| Reactive/BeforeConvertCallback -| `onBeforeConvert(T entity, CqlIdentifier tableName)` -| Invoked before a domain object is converted to `com.datastax.driver.core.Statement`. -| `Ordered.LOWEST_PRECEDENCE` - -| Reactive/AuditingEntityCallback -| `onBeforeConvert(Object entity, CqlIdentifier tableName)` -| Marks an auditable entity _created_ or _modified_ -| 100 - -| Reactive/BeforeSaveCallback -| `onBeforeSave(T entity, CqlIdentifier tableName, Statement statement)` -| Invoked before a domain object is saved. + -Can modify the target, to be persisted, `com.datastax.driver.core.Statement` containing all mapped entity information. -| `Ordered.LOWEST_PRECEDENCE` - -|=== diff --git a/src/main/asciidoc/reference/cassandra-repositories.adoc b/src/main/asciidoc/reference/cassandra-repositories.adoc deleted file mode 100644 index ce3e2ea80..000000000 --- a/src/main/asciidoc/reference/cassandra-repositories.adoc +++ /dev/null @@ -1,368 +0,0 @@ -[[cassandra.repositories]] -= Cassandra Repositories - -This chapter covers the details of the Spring Data Repository support for Apache Cassandra. -Cassandra's repository support builds on the core repository support explained in "`<>`". -Cassandra repositories use `CassandraTemplate` and its wired `CqlTemplate` as infrastructure beans. -You should understand the basic concepts explained there before proceeding. - -[[cassandra-repo-usage]] -== Usage - -To access domain entities stored in Apache Cassandra, you can use Spring Data's sophisticated repository support, which significantly eases implementing DAOs. -To do so, create an interface for your repository, as the following example shows: - -.Sample Person entity -==== -[source,java] ----- -@Table -public class Person { - - @Id - private String id; - private String firstname; - private String lastname; - - // … getters and setters omitted -} ----- -==== - -Note that the entity has a property named `id` of type `String`. -The default serialization mechanism used in `CassandraTemplate` (which backs the repository support) regards properties named `id` as being the row ID. - -The following example shows a repository definition to persist `Person` entities: - -.Basic repository interface to persist `Person` entities -==== -[source] ----- -public interface PersonRepository extends CrudRepository { - - // additional custom finder methods go here -} ----- -==== - -Right now, the interface in the preceding example serves only typing purposes, but we add additional methods to it later. - -Next, in your Spring configuration, add the following (if you use Java for configuration): - -If you want to use Java configuration, use the `@EnableCassandraRepositories` annotation. -The annotation carries the same attributes as the namespace element. -If no base package is configured, the infrastructure scans the package of the annotated configuration class. -The following example shows how to use the `@EnableCassandraRepositories` annotation: - -.Configuration for repositories -==== -.Java -[source,java,role="primary"] ----- -@Configuration -@EnableCassandraRepositories -class ApplicationConfig extends AbstractCassandraConfiguration { - - @Override - protected String getKeyspaceName() { - return "keyspace"; - } - - public String[] getEntityBasePackages() { - return new String[] { "com.oreilly.springdata.cassandra" }; - } -} ----- - -.XML -[source,xml,role="secondary"] ----- - - - - - - - - - - - - - - ----- -==== - -The `cassandra:repositories` namespace element causes the base packages to be scanned for interfaces that extend `CrudRepository` and create Spring beans for each one found. -By default, the repositories are wired with a `CassandraTemplate` Spring bean called `cassandraTemplate`, so you only need to configure -`cassandra-template-ref` explicitly if you deviate from this convention. - -Because our domain repository extends `CrudRepository`, it provides you with basic CRUD operations. -Working with the repository instance is a matter of injecting the repository as a dependency into a client, as the following example does by autowiring `PersonRepository`: - -.Basic access to Person entities -==== -[source,java] ----- -@ExtendWith(SpringExtension.class) -class PersonRepositoryTests { - - @Autowired PersonRepository repository; - - @Test - void readsPersonTableCorrectly() { - - List persons = repository.findAll(); - assertThat(persons.isEmpty()).isFalse(); - } -} ----- -==== - -Cassandra repositories support paging and sorting for paginated and sorted access to the entities. -Cassandra paging requires a paging state to forward-only navigate through pages. -A `Slice` keeps track of the current paging state and allows for creation of a `Pageable` to request the next page. -The following example shows how to set up paging access to `Person` entities: - -.Paging access to `Person` entities -==== -[source,java] ----- -@ExtendWith(SpringExtension.class) -class PersonRepositoryTests { - - @Autowired PersonRepository repository; - - @Test - void readsPagesCorrectly() { - - Slice firstBatch = repository.findAll(CassandraPageRequest.first(10)); - - assertThat(firstBatch).hasSize(10); - - Slice nextBatch = repository.findAll(firstBatch.nextPageable()); - - // … - } -} ----- -==== - -NOTE: Cassandra repositories do not extend `PagingAndSortingRepository`, because classic paging patterns using limit/offset are not applicable to Cassandra. - -The preceding example creates an application context with Spring's unit test support, which performs annotation-based dependency injection into the test class. -Inside the test cases (the test methods), we use the repository to query the data store. -We invoke the repository query method that requests all `Person` instances. - -[[cassandra.repositories.queries]] -== Query Methods - -Most of the data access operations you usually trigger on a repository result in a query being executed against the Apache Cassandra database. -Defining such a query is a matter of declaring a method on the repository interface. -The following example shows a number of such method declarations: - -.PersonRepository with query methods -==== -[source,java] ----- -interface PersonRepository extends CrudRepository { - - List findByLastname(String lastname); <1> - - Slice findByFirstname(String firstname, Pageable pageRequest); <2> - - Window findByFirstname(String firstname, CassandraScrollPosition pos, Limit limit); <3> - - List findByFirstname(String firstname, QueryOptions opts); <4> - - List findByFirstname(String firstname, Sort sort); <5> - - List findByFirstname(String firstname, Limit limit); <6> - - Person findByShippingAddress(Address address); <7> - - Person findFirstByShippingAddress(Address address); <8> - - Stream findAllBy(); <9> - - @AllowFiltering - List findAllByAge(int age); <10> -} ----- - -<1> The method shows a query for all people with the given `lastname`. -The query is derived from parsing the method name for constraints, which can be concatenated with `And`. -Thus, the method name results in a query expression of `SELECT * FROM person WHERE lastname = 'lastname'`. -<2> Applies pagination to a query. -You can equip your method signature with a `Pageable` parameter and let the method return a `Slice` instance, and we automatically page the query accordingly. -<3> Applies scrolling to a query. -Scrolling wraps Cassandra's `PagingState` into `CassandraScrollPosition` and allows dynamic limiting. -You can also use `findTop…` for a static limit. -<4> Passing a `QueryOptions` object applies the query options to the resulting query before its execution. -<5> Applies dynamic sorting to a query. -You can add a `Sort` parameter to your method signature, and Spring Data automatically applies ordering to the query. -<6> Applies dynamic result limiting to a query. -Query results can be limited using `SELECT … LIMIT`. -<7> Shows that you can query based on properties that are not a primitive type by using `Converter` instances registered in `CustomConversions`. -Throws `IncorrectResultSizeDataAccessException` if more than one match is found. -<8> Uses the `First` keyword to restrict the query to only the first result. -Unlike the preceding method, this method does not throw an exception if more than one match is found. -<9> Uses a Java 8 `Stream` to read and convert individual elements while iterating the stream. -<10> Shows a query method annotated with `@AllowFiltering`, to allow server-side filtering. -==== - -NOTE: Querying non-primary key properties requires secondary indexes. - -The following table shows short examples of the keywords that you can use in query methods: - -[cols="1,2,3",options="header"] -.Supported keywords for query methods -|=== -| Keyword -| Sample -| Logical result - -| `After` -| `findByBirthdateAfter(Date date)` -| `birthdate > date` - -| `GreaterThan` -| `findByAgeGreaterThan(int age)` -| `age > age` - -| `GreaterThanEqual` -| `findByAgeGreaterThanEqual(int age)` -| `age >= age` - -| `Before` -| `findByBirthdateBefore(Date date)` -| `birthdate < date` - -| `LessThan` -| `findByAgeLessThan(int age)` -| `age < age` - -| `LessThanEqual` -| `findByAgeLessThanEqual(int age)` -| `age <= age` - -| `Between` -| `findByAgeBetween(int from, int to)` and `findByAgeBetween(Range range)` -| ``age > from AND age < to`` and -lower / upper bounds (`>` / `>=` & `<` / `<=`) according to `Range` - -| `In` -| `findByAgeIn(Collection ages)` -| `age IN (ages...)` - -| `Like`, `StartingWith`, `EndingWith` -| `findByFirstnameLike(String name)` -| `firstname LIKE (name as like expression)` - -| `Containing` on String -| `findByFirstnameContaining(String name)` -| `firstname LIKE (name as like expression)` - -| `Containing` on Collection -| `findByAddressesContaining(Address address)` -| `addresses CONTAINING address` - -| `(No keyword)` -| `findByFirstname(String name)` -| `firstname = name` - -| `IsTrue`, `True` -| `findByActiveIsTrue()` -| `active = true` - -| `IsFalse`, `False` -| `findByActiveIsFalse()` -| `active = false` - -|=== - -[[cassandra.repositories.queries.delete]] -== Repository Delete Queries - -The keywords in the preceding table can be used in conjunction with `delete…By` to create queries that delete matching documents. - -==== -[source,java] ----- -interface PersonRepository extends Repository { - - void deleteWithoutResultByLastname(String lastname); - - boolean deleteByLastname(String lastname); -} ----- -==== - -Delete queries return whether the query was applied or terminate without returning a value using `void`. - -include::../{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+2] - -[[cassandra.repositories.queries.options]] -=== Query Options - -You can specify query options for query methods by passing a `QueryOptions` object. -The options apply to the query before the actual query execution. -`QueryOptions` is treated as a non-query parameter and is not considered to be a query parameter value. -Query options apply to derived and string `@Query` repository methods. - -To statically set the consistency level, use the `@Consistency` annotation on query methods. -The declared consistency level is applied to the query each time it is executed. -The following example sets the consistency level to `ConsistencyLevel.LOCAL_ONE`: - -==== -[source,java] ----- -interface PersonRepository extends CrudRepository { - - @Consistency(ConsistencyLevel.LOCAL_ONE) - List findByLastname(String lastname); - - List findByFirstname(String firstname, QueryOptions options); -} ----- -==== - -The DataStax Cassandra documentation includes https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/dml/dmlConfigConsistency.html[a good discussion of the available consistency levels]. - -NOTE: You can control fetch size, consistency level, and retry policy defaults by configuring the following parameters on the CQL API instances: `CqlTemplate`, `AsyncCqlTemplate`, and `ReactiveCqlTemplate`. -Defaults apply if the particular query option is not set. - -[[cassandra.repositories.misc.cdi-integration]] -=== CDI Integration - -Instances of the repository interfaces are usually created by a container, and the Spring container is the most natural choice when working with Spring Data. -Spring Data for Apache Cassandra ships with a custom CDI extension that allows using the repository abstraction in CDI environments. -The extension is part of the JAR.To activate it, drop the Spring Data for Apache Cassandra JAR into your classpath. -You can now set up the infrastructure by implementing a CDI Producer for the -`CassandraTemplate`, as the following examlpe shows: - -==== -[source,java] ----- -include::../{example-root}/CassandraTemplateProducer.java[tags=class] ----- -==== - -The Spring Data for Apache Cassandra CDI extension picks up `CassandraOperations` as a CDI bean and creates a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container. -Thus, obtaining an instance of a Spring Data repository is a matter of declaring an injected property, as the following example shows: - -==== -[source,java] ----- -include::../{example-root}/RepositoryClient.java[tags=class] ----- -==== diff --git a/src/main/asciidoc/reference/cassandra.adoc b/src/main/asciidoc/reference/cassandra.adoc deleted file mode 100644 index e08f08444..000000000 --- a/src/main/asciidoc/reference/cassandra.adoc +++ /dev/null @@ -1,1212 +0,0 @@ -[[cassandra.core]] -= Cassandra Support - -Spring Data support for Apache Cassandra contains a wide range of features: - -* Spring configuration support with Java-based `@Configuration` classes or the XML namespace. -* The `CqlTemplate` helper class that increases productivity by properly handling common Cassandra data access operations. -* The `CassandraTemplate` helper class that provides object mapping between CQL Tables and POJOs. -* Exception translation into Spring's portable {springDocsUrl}data-access.html#dao-exceptions[Data Access Exception Hierarchy]. -* Feature rich object mapping integrated with _Spring's_ {springDocsUrl}core.html#core-convert[Conversion Service]. -* Annotation-based mapping metadata that is extensible to support other metadata formats. -* Java-based query, criteria, and update DSLs. -* Automatic implementation of `Repository` interfaces including support for custom finder methods. - -For most data-oriented tasks, you can use the `CassandraTemplate` or the `Repository` support, both of which use the rich object-mapping functionality. `CqlTemplate` is commonly used to increment counters or perform ad-hoc CRUD operations. `CqlTemplate` also provides callback methods that make it easy to get low-level API objects, such as `com.datastax.oss.driver.api.core.CqlSession`, which lets you communicate directly with Cassandra. -Spring Data for Apache Cassandra uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are familiar and so that you can map your existing knowledge onto the Spring APIs. - -[[cassandra.getting-started]] -== Getting Started - -Spring Data for Apache Cassandra requires Apache Cassandra 2.1 or later and Datastax Java Driver 4.0 or later. -An easy way to quickly set up and bootstrap a working environment is to create a Spring-based project in https://spring.io/tools[Spring Tools] or use https://start.spring.io/[Spring Initializer]. - -First, you need to set up a running Apache Cassandra server. -See the -https://cassandra.apache.org/doc/latest/getting_started/index.html[Apache Cassandra Quick Start Guide] -for an explanation on how to start Apache Cassandra. -Once installed, starting Cassandra is typically a matter of executing the following command: `CASSANDRA_HOME/bin/cassandra -f`. - -To create a Spring project in STS, go to File -> New -> Spring Template Project -> Simple Spring Utility Project and press Yes when prompted. -Then enter a project and a package name, such as `org.spring.data.cassandra.example`. - -Then you can add the following dependency declaration to your pom.xml file's `dependencies` section. - -==== -[source,xml,subs="verbatim,attributes"] ----- - - - - org.springframework.data - spring-data-cassandra - {version} - - - ----- -==== - -Also, you should change the version of Spring in the pom.xml file to be as follows: - -==== -[source,xml,subs="verbatim,attributes"] ----- -{springVersion} ----- -==== - -If using a milestone release instead of a GA release, you also need to add the location of the Spring Milestone repository for Maven to your pom.xml file so that it is at the same level of your `` element, as follows: - -[source,xml] ----- - - - spring-milestone - Spring Maven MILESTONE Repository - https://repo.spring.io/milestone - - ----- - -The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here]. - -You can also browse all Spring repositories https://repo.spring.io/webapp/#/home[here]. - -Now you can create a simple Java application that stores and reads a domain object to and from Cassandra. - -To do so, first create a simple domain object class to persist, as the following example shows: - -==== -[source,java] ----- -include::../{example-root}/Person.java[tags=file] ----- -==== - -Next, create the main application to run, as the following example shows: - -==== -[source,java] ----- -include::../{example-root}/CassandraApplication.java[tags=file] ----- -==== - -Even in this simple example, there are a few notable things to point out: - -* You can create an instance of `CassandraTemplate` with a Cassandra `CqlSession`. -* You must annotate your POJO as a Cassandra `@Table` entity and also annotate the `@PrimaryKey`. -Optionally, you can override these mapping names to match your Cassandra database table and column names. -* You can either use raw CQL or the DataStax `QueryBuilder` API to construct your queries. - -[[cassandra.examples-repo]] -== Examples Repository - -To get a feel for how the library works, you can download and play around with -https://github.com/spring-projects/spring-data-examples[several examples]. -. - -[[cassandra.connectors]] -== Connecting to Cassandra with Spring - -One of the first tasks when using Apache Cassandra with Spring is to create a `com.datastax.oss.driver.api.core.CqlSession` object by using the Spring IoC container. -You can do so either by using Java-based bean metadata or by using XML-based bean metadata. -These are discussed in the following sections. - -NOTE: For those not familiar with how to configure the Spring container using Java-based bean metadata instead of XML-based metadata, see the high-level introduction in the reference docs -https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/new-in-3.0.html#new-java-configuration[here] -as well as the detailed documentation {springDocsUrl}core.html#beans-java-instantiating-container[here]. - -[[cassandra.cassandra-java-config]] -=== Registering a Session Instance by Using Java-based Metadata - -The following example shows how to use Java-based bean metadata to register an instance of a `com.datastax.oss.driver.api.core.CqlSession`: - -.Registering a `com.datastax.oss.driver.api.core.CqlSession` object by using Java-based bean metadata -==== -[source,java] ----- -include::../{example-root}/AppConfig.java[tags=class] ----- -==== - -This approach lets you use the standard `com.datastax.oss.driver.api.core.CqlSession` API that you may already know. - -An alternative is to register an instance of `com.datastax.oss.driver.api.core.CqlSession` with the container by using Spring's `CqlSessionFactoryBean`. -As compared to instantiating a `com.datastax.oss.driver.api.core.CqlSession` instance directly, the `FactoryBean` approach has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates Cassandra exceptions to exceptions in Spring's portable `DataAccessException` hierarchy. -This hierarchy and the use of -`@Repository` is described in {springDocsUrl}data-access.html[Spring's DAO support features]. - -The following example shows Java-based factory class usage: - -.Registering a com.datastax.oss.driver.api.core.CqlSession object by using Spring's `CqlSessionFactoryBean`: -==== -[source,java] ----- -include::../{example-root}/FactoryBeanAppConfig.java[tags=class] ----- -==== - -Using `CassandraTemplate` with object mapping and repository support requires a `CassandraTemplate`, -`CassandraMappingContext`, `CassandraConverter`, and enabling repository support. - -The following example shows how to register components to configure object mapping and repository support: - -.Registering components to configure object mapping and repository support -==== -[source,java] ----- -include::../{example-root}/CassandraConfig.java[tags=class] ----- -==== - -Creating configuration classes that register Spring Data for Apache Cassandra components can be an exhausting challenge, so Spring Data for Apache Cassandra comes with a pre-built configuration support class. -Classes that extend from -`AbstractCassandraConfiguration` register beans for Spring Data for Apache Cassandra use. -`AbstractCassandraConfiguration` lets you provide various configuration options, such as initial entities, default query options, pooling options, socket options, and many more. `AbstractCassandraConfiguration` also supports you with schema generation based on initial entities, if any are provided. -Extending from -`AbstractCassandraConfiguration` requires you to at least provide the keyspace name by implementing the `getKeyspaceName` method. -The following example shows how to register beans by using `AbstractCassandraConfiguration`: - -.Registering Spring Data for Apache Cassandra beans by using `AbstractCassandraConfiguration` -==== -[source,java] ----- -include::../{example-root}/CassandraConfiguration.java[tags=class] ----- -==== - -[[cassandra-connectors.xmlconfig]] -=== XML Configuration - -This section describes how to configure Spring Data Cassandra with XML. - -[[cassandra-connectors.xmlconfig.ext_properties]] -==== Externalizing Connection Properties - -To externalize connection properties, you should first create a properties file that contains the information needed to connect to Cassandra. `contactpoints` and `keyspace` are the required fields. - -The following example shows our properties file, called `cassandra.properties`: - -==== -[source] ----- -cassandra.contactpoints=10.1.55.80:9042,10.1.55.81:9042 -cassandra.keyspace=showcase ----- -==== - -In the next two examples, we use Spring to load these properties into the Spring context. - -==== Registering a Session Instance by using XML-based Metadata - -While you can use Spring's traditional `` XML namespace to register an instance of -`com.datastax.oss.driver.api.core.CqlSession` with the container, the XML can be quite verbose, because it is general purpose. -XML namespaces are a better alternative to configuring commonly used objects, such as the `CqlSession` instance. -The `cassandra` namespace let you create a `CqlSession` instance. - -The following example shows how to configure the `cassandra` namespace: - -.XML schema to configure Cassandra by using the `cassandra` namespace -==== -[source,xml] ----- - - - - - - - - - - - - ----- -==== - -The XML configuration elements for more advanced Cassandra configuration are shown below. -These elements all use default bean names to keep the configuration code clean and readable. - -While the preceding example shows how easy it is to configure Spring to connect to Cassandra, there are many other options. -Basically, any option available with the DataStax Java Driver is also available in the Spring Data for Apache Cassandra configuration. -This includes but is not limited to authentication, load-balancing policies, retry policies, and pooling options. -All of the Spring Data for Apache Cassandra method names and XML elements are named exactly (or as close as possible) like the configuration options on the driver so that mapping any existing driver configuration should be straight forward. -The following example shows how to configure Spring Data components by using XML - -.Configuring Spring Data components by using XML -==== -[source,xml] ----- - - - - - - - - - - - - - - - - - - - - - ----- -==== - -[[cassandra.schema-management]] -== Schema Management - -Apache Cassandra is a data store that requires a schema definition prior to any data interaction. -Spring Data for Apache Cassandra can support you with schema creation. - -=== Keyspaces and Lifecycle Scripts - -The first thing to start with is a Cassandra keyspace. -A keyspace is a logical grouping of tables that share the same replication factor and replication strategy. -Keyspace management is located in the `CqlSession` configuration, which has the `KeyspaceSpecification` and startup and shutdown CQL script execution. - -Declaring a keyspace with a specification allows creating and dropping of the Keyspace. -It derives CQL from the specification so that you need not write CQL yourself. -The following example specifies a Cassadra keyspace by using XML: - -.Specifying a Cassandra keyspace -==== -.Java -[source,java,role="primary"] ----- -include::../{example-root}/CreateKeyspaceConfiguration.java[tags=class] ----- - -.XML -[source,xml,role="secondary"] ----- - - - - - - - - - - - - - - ----- -==== - -NOTE: Keyspace creation allows rapid bootstrapping without the need of external keyspace management. -This can be useful for certain scenarios but should be used with care. -Dropping a keyspace on application shutdown removes the keyspace and all data from the tables in the keyspace. - -[[cassandra.schema-management.initializing]] -=== Initializing a `SessionFactory` - -The `org.springframework.data.cassandra.core.cql.session.init` package provides support for initializing an existing `SessionFactory`. -You may sometimes need to initialize a keyspace that runs on a server somewhere. - -[[cassandra.schema-management.initializing.config]] -==== Initializing a Keyspace - -You can provide arbitrary CQL that is executed on `CqlSession` initialization and shutdown in the configured keyspace, as the following Java configuration example shows: - -==== -.Java -[source,java,role="primary"] ----- -include::../{example-root}/KeyspacePopulatorConfiguration.java[tags=class] ----- - -.XML -[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ----- - - - - ----- -==== - -The preceding example runs the two specified scripts against the keyspace. -The first script creates a schema, and the second populates tables with a test data set. -The script locations can also be patterns with wildcards in the usual Ant style used for resources in Spring (for example, `classpath{asterisk}:/com/foo/{asterisk}{asterisk}/cql/{asterisk}-data.cql`). -If you use a pattern, the scripts are run in the lexical order of their URL or filename. - -The default behavior of the keyspace initializer is to unconditionally run the provided scripts. -This may not always be what you want -- for instance, if you run the scripts against a keyspace that already has test data in it. -The likelihood of accidentally deleting data is reduced by following the common pattern (shown earlier) of creating the tables first and then inserting the data. -The first step fails if the tables already exist. - -However, to gain more control over the creation and deletion of existing data, the XML namespace provides a few additional options. -The first is a flag to switch the initialization on and off. -You can set this according to the environment (such as pulling a boolean value from system properties or from an environment bean). -The following example gets a value from a system property: - -==== -[source,xml,indent=0,subs="verbatim,quotes"] ----- - <1> - - ----- -<1> Get the value for `enabled` from a system property called `INITIALIZE_KEYSPACE`. -==== - -The second option to control what happens with existing data is to be more tolerant of failures. -To this end, you can control the ability of the initializer to ignore certain errors in the CQL it executes from the scripts, as the following example shows: - -==== -.Java -[source,java,role="primary"] ----- -include::../{example-root}/KeyspacePopulatorFailureConfiguration.java[tags=class] ----- - -.XML -[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ----- - - - ----- -==== - -In the preceding example, we are saying that we expect that, sometimes, the scripts are run against an empty keyspace, and there are some `DROP` statements in the scripts that would, therefore, fail. -So failed CQL `DROP` statements will be ignored, but other failures will cause an exception. -This is useful if you don't want tu use support `DROP ... IF EXISTS` (or similar) but you want to unconditionally remove all test data before re-creating it. -In that case the first script is usually a set of `DROP` statements, followed by a set of `CREATE` statements. - -The `ignore-failures` option can be set to `NONE` (the default), `DROPS` (ignore failed drops), or `ALL` (ignore all failures). - -Each statement should be separated by `;` or a new line if the `;` character is not present at all in the script. -You can control that globally or script by script, as the following example shows: - -==== -.Java -[source,java,role="primary"] ----- -include::../{example-root}/SessionFactoryInitializerConfiguration.java[tags=class] ----- - -.XML -[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] ----- - - - - - ----- -==== - - -In this example, the two `test-data` scripts use `@@` as statement separator and only the `db-schema.cql` uses `;`. -This configuration specifies that the default separator is `@@` and overrides that default for the `db-schema` script. - -If you need more control than you get from the XML namespace, you can use the `SessionFactoryInitializer` directly and define it as a component in your application. - -[[cassandra.schema-management.initializing.component]] -===== Initialization of Other Components that Depend on the Keyspace - -A large class of applications (those that do not use the database until after the Spring context has started) can use the database initializer with no further complications. -If your application is not one of those, you might need to read the rest of this section. - -The database initializer depends on a `SessionFactory` instance and runs the scripts provided in its initialization callback (analogous to an `init-method` in an XML bean definition, a `@PostConstruct` method in a component, or the `afterPropertiesSet()` method in a component that implements `InitializingBean`). -If other beans depend on the same data source and use the session factory in an initialization callback, there might be a problem because the data has not yet been initialized. -A common example of this is a cache that initializes eagerly and loads data from the database on application startup. - -To get around this issue, you have two options: change your cache initialization strategy to a later phase or ensure that the keyspace initializer is initialized first. - -Changing your cache initialization strategy might be easy if the application is in your control and not otherwise. -Some suggestions for how to implement this include: - -* Make the cache initialize lazily on first usage, which improves application startup time. -* Have your cache or a separate component that initializes the cache implement `Lifecycle` or `SmartLifecycle`. -When the application context starts, you can automatically start a `SmartLifecycle` by setting its `autoStartup` flag, and you can manually start a `Lifecycle` by calling `ConfigurableApplicationContext.start()` on the enclosing context. -* Use a Spring `ApplicationEvent` or similar custom observer mechanism to trigger the cache initialization. `ContextRefreshedEvent` is always published by the context when it is ready for use (after all beans have been initialized), so that is often a useful hook (this is how the `SmartLifecycle` works by default). - -Ensuring that the keyspace initializer is initialized first can also be easy. -Some suggestions on how to implement this include: - -* Rely on the default behavior of the Spring `BeanFactory`, which is that beans are initialized in registration order. -You can easily arrange that by adopting the common practice of a set of `` elements in XML configuration that order your application modules and ensuring that the database and database initialization are listed first. -* Separate the `SessionFactory` and the business components that use it and control their startup order by putting them in separate `ApplicationContext` instances (for example, the parent context contains the `SessionFactory`, and the child context contains the business components). -This structure is common in Spring web applications but can be more generally applied. -* Use the Schema management for <> to initialize the keyspace using Spring Data Cassandra's built-in schema generator. - -[[cassandra.schema-management.tables]] -=== Tables and User-defined Types - -Spring Data for Apache Cassandra approaches data access with mapped entity classes that fit your data model. -You can use these entity classes to create Cassandra table specifications and user type definitions. - -Schema creation is tied to `CqlSession` initialization by `SchemaAction`. -The following actions are supported: - -* `SchemaAction.NONE`: No tables or types are created or dropped. -This is the default setting. -* `SchemaAction.CREATE`: Create tables, indexes, and user-defined types from entities annotated with `@Table` and types annotated with `@UserDefinedType`. -Existing tables or types cause an error if you tried to create the type. -* `SchemaAction.CREATE_IF_NOT_EXISTS`: Like `SchemaAction.CREATE` but with `IF NOT EXISTS` applied. -Existing tables or types do not cause any errors but may remain stale. -* `SchemaAction.RECREATE`: Drops and recreates existing tables and types that are known to be used. -Tables and types that are not configured in the application are not dropped. -* `SchemaAction.RECREATE_DROP_UNUSED`: Drops all tables and types and recreates only known tables and types. - -NOTE: `SchemaAction.RECREATE` and `SchemaAction.RECREATE_DROP_UNUSED` drop your tables and lose all data. -`RECREATE_DROP_UNUSED` also drops tables and types that are not known to the application. - -==== Enabling Tables and User-Defined Types for Schema Management - -<> explains object mapping with conventions and annotations. -To prevent unwanted classes from being created as a table or a type, schema management is only active for entities annotated with `@Table` and user-defined types annotated with `@UserDefinedType`. -Entities are discovered by scanning the classpath. -Entity scanning requires one or more base packages. -Tuple-typed columns that use `TupleValue` do not provide any typing details. -Consequently, you must annotate such column properties with `@CassandraType(type = TUPLE, typeArguments = …)` -to specify the desired column type. - -The following example shows how to specify entity base packages in XML configuration: - -.Specifying entity base packages -==== -.Java -[source,java,role="primary"] ----- -include::../{example-root}/EntityBasePackagesConfiguration.java[tags=class] ----- - -.XML -[source,xml,role="secondary"] ----- - - - - - ----- -==== - -[[cassandra.cql-template]] -== `CqlTemplate` - -The `CqlTemplate` class is the central class in the core CQL package. -It handles the creation and release of resources. -It performs the basic tasks of the core CQL workflow, such as statement creation and execution, and leaves application code to provide CQL and extract results. -The `CqlTemplate` class executes CQL queries and update statements, performs iteration over `ResultSet` instances and extraction of returned parameter values. -It also catches CQL exceptions and translates them to the generic, more informative, exception hierarchy defined in the `org.springframework.dao` package. - -When you use the `CqlTemplate` for your code, you need only implement callback interfaces, which have a clearly defined contract. -Given a `Connection`, the `PreparedStatementCreator` callback interface creates a <> with the provided CQL and any necessary parameter arguments. -The `RowCallbackHandler` interface extracts values from each row of a `ResultSet`. - -The `CqlTemplate` can be used within a DAO implementation through direct instantiation with a `SessionFactory` reference or be configured in the Spring container and given to DAOs as a bean reference. `CqlTemplate` is a foundational building block for <>. - -All CQL issued by this class is logged at the `DEBUG` level under the category corresponding to the fully-qualified class name of the template instance (typically `CqlTemplate`, but it may be different if you use a custom subclass of the `CqlTemplate` class). - -You can control fetch size, consistency level, and retry policy defaults by configuring these parameters on the CQL API instances: `CqlTemplate`, `AsyncCqlTemplate`, and `ReactiveCqlTemplate`. -Defaults apply if the particular query option is not set. - -NOTE: `CqlTemplate` comes in different execution model flavors. -The basic `CqlTemplate` uses a blocking execution model. -You can use `AsyncCqlTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or -<> for reactive execution. - -[[cassandracql-template.examples]] -=== Examples of `CqlTemplate` Class Usage - -This section provides some examples of the `CqlTemplate` class in action. -These examples are not an exhaustive list of all of the functionality exposed by the `CqlTemplate`. -See the https://docs.spring.io/spring-data/cassandra/docs/{version}/api/[Javadoc] for that. - -[[cassandra.cql-template.examples.query]] -==== Querying (SELECT) with `CqlTemplate` - -The following query gets the number of rows in a table: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=rowCount] ----- -==== - -The following query uses a bind variable: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=countOfActorsNamedJoe] ----- -==== - -The following example queries for a `String`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=lastName] ----- -==== - -The following example queries and populates a single domain object: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=rowMapper] ----- -==== - -The following example queries and populates multiple domain objects: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=listOfRowMapper] ----- -==== - -If the last two snippets of code actually existed in the same application, it would make sense to remove the duplication present in the two `RowMapper` anonymous inner classes and extract them out into a single class (typically a `static` nested class) that can then be referenced by DAO methods. - -For example, it might be better to write the last code snippet as follows: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=findAllActors] ----- -==== - -[[cassandra.cql-template.examples.update]] -==== `INSERT`, `UPDATE`, and `DELETE` with `CqlTemplate` - -You can use the `execute(…)` method to perform `INSERT`, `UPDATE`, and `DELETE` operations. -Parameter values are usually provided as variable arguments or, alternatively, as an object array. - -The following example shows how to perform an `INSERT` operation with `CqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=insert] ----- -==== - -The following example shows how to perform an `UPDATE` operation with `CqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=update] ----- -==== - -The following example shows how to perform an `DELETE` operation with `CqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=delete] ----- -==== - -[[cassandra.cql-template.examples.other]] -==== Other `CqlTemplate` operations - -You can use the `execute(..)` method to execute any arbitrary CQL. As a result, the method is often used for DDL statements. -It is heavily overloaded with variants that take callback interfaces, bind variable arrays, and so on. - -The following example shows how to create and drop a table by using different API objects that are all passed to the `execute()` methods: - -==== -[source,java] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=other] ----- -==== - -[[cassandra.exception]] -include::exception-translation.adoc[] - -[[cassandra.connections]] -== Controlling Cassandra Connections - -Applications connect to Apache Cassandra by using `CqlSession` objects. -A Cassandra `CqlSession` keeps track of multiple connections to the individual nodes and is designed to be a thread-safe, long-lived object. -Usually, you can use a single `CqlSession` for the whole application. - -Spring acquires a Cassandra `CqlSession` through a `SessionFactory`. `SessionFactory` is part of Spring Data for Apache Cassandra and is a generalized connection factory. -It lets the container or framework hide connection handling and routing issues from the application code. - -The following example shows how to configure a default `SessionFactory`: - -[source,java] ----- -Session session = … // get a Cassandra Session - -CqlTemplate template = new CqlTemplate(); - -template.setSessionFactory(new DefaultSessionFactory(session)); ----- - -`CqlTemplate` and other Template API implementations obtain a `CqlSession` for each operation. -Due to their long-lived nature, sessions are not closed after invoking the desired operation. -Responsibility for proper resource disposal lies with the container or framework that uses the session. - -You can find various `SessionFactory` implementations within the `org.springframework.data.cassandra.core.cql.session` -package. - -[[cassandra.template]] -== Introduction to `CassandraTemplate` - -The `CassandraTemplate` class, located in the `org.springframework.data.cassandra` package, is the central class in Spring's Cassandra support and provides a rich feature set to interact with the database. -The template offers convenience operations to create, update, delete, and query Cassandra, and provides a mapping between your domain objects and rows in Cassandra tables. - -NOTE: Once configured, `CassandraTemplate` is thread-safe and can be reused across multiple instances. - -The mapping between rows in Cassandra and application domain classes is done by delegating to an implementation of the `CassandraConverter` interface. -Spring provides a default implementation, `MappingCassandraConverter`, but you can also write your own custom converter. -See the section on -<> for more detailed information. - -The `CassandraTemplate` class implements the `CassandraOperations` interface. -In as much as possible, the methods on `CassandraOperations` are named after methods available in Cassandra to make the API familiar to developers who are already familiar with Cassandra. - -For example, you can find methods such as `select`, `insert`, `delete`, and `update`. -The design goal was to make it as easy as possible to transition between the use of the base Cassandra driver and `CassandraOperations`. -A major difference between the two APIs is that `CassandraOperations` can be passed domain objects instead of CQL and query objects. - -NOTE: The preferred way to reference operations on a `CassandraTemplate` instance is through the -`CassandraOperations` interface. - -The default converter implementation used by `CassandraTemplate` is `MappingCassandraConverter`. -While `MappingCassandraConverter` can use additional metadata to specify the mapping of objects to rows, it can also convert objects that contain no additional metadata by using some conventions for the mapping of fields and table names. -These conventions, as well as the use of mapping annotations, are explained in the <>. - -Another central feature of `CassandraTemplate` is exception translation of exceptions thrown in the Cassandra Java driver into Spring's portable Data Access Exception hierarchy. -See the section on -<> for more information. - -NOTE: The Template API has different execution model flavors. -The basic `CassandraTemplate` uses a blocking (imperative-synchronous) execution model. -You can use `AsyncCassandraTemplate` for asynchronous execution and synchronization with `ListenableFuture` instances or <> for reactive execution. - -[[cassandra.template.instantiating]] -=== Instantiating CassandraTemplate - -`CassandraTemplate` should always be configured as a Spring bean, although we show an example earlier where you can instantiate it directly. -However, because we are assuming the context of making a Spring module, we assume the presence of the Spring container. - -There are two ways to get a `CassandraTemplate`, depending on how you load you Spring `ApplicationContext`: - -* <> -* <> - -[float] -[[cassandra-template-autowiring]] -==== Autowiring - -You can autowire a `CassandraOperations` into your project, as the following example shows: - -==== -[source,java] ----- -@Autowired -private CassandraOperations cassandraOperations; ----- -==== - -As with all Spring autowiring, this assumes there is only one bean of type `CassandraOperations` in the `ApplicationContext`. -If you have multiple `CassandraTemplate` beans (which is the case if you work with multiple keyspaces in the same project), then you can use the `@Qualifier` annotation to designate the bean you want to autowire. - -==== -[source,java] ----- -@Autowired -@Qualifier("keyspaceOneTemplateBeanId") -private CassandraOperations cassandraOperations; ----- -==== - -[float] -[[cassandra-template-bean-lookup-applicationcontext]] -==== Bean Lookup with `ApplicationContext` - -You can also look up the `CassandraTemplate` bean from the `ApplicationContext`, as shown in the following example: - -==== -[source,java] ----- -CassandraOperations cassandraOperations = applicationContext.getBean("cassandraTemplate", CassandraOperations.class); ----- -==== - -[[cassandra-template.save-update-remove]] -== Saving, Updating, and Removing Rows - -`CassandraTemplate` provides a simple way for you to save, update, and delete your domain objects and map those objects to tables managed in Cassandra. - -[[cassandra.template.type-mapping]] -=== Type Mapping - -Spring Data for Apache Cassandra relies on the DataStax Java driver's `CodecRegistry` to ensure type support. -As types are added or changed, the Spring Data for Apache Cassandra module continues to function without requiring changes. -See https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cql_data_types_c.html[CQL data types] -and "`<>`" for the current type mapping matrix. - -[[cassandra.template.insert-update]] -=== Methods for Inserting and Updating rows - -`CassandraTemplate` has several convenient methods for saving and inserting your objects. -To have more fine-grained control over the conversion process, you can register Spring `Converter` instances with the `MappingCassandraConverter` -(for example, `Converter`). - -NOTE: The difference between insert and update operations is that `INSERT` operations do not insert `null` values. - -The simple case of using the `INSERT` operation is to save a POJO. In this case, the table name is determined by the simple class name (not the fully qualified class name). -The table to store the object can be overridden by using mapping metadata. - -When inserting or updating, the `id` property must be set. -Apache Cassandra has no means to generate an ID. - -The following example uses the save operation and retrieves its contents: - -.Inserting and retrieving objects by using the `CassandraTemplate` -==== -[source,java] ----- -import static org.springframework.data.cassandra.core.query.Criteria.where; -import static org.springframework.data.cassandra.core.query.Query.query; -… - -Person bob = new Person("Bob", 33); -cassandraTemplate.insert(bob); - -Person queriedBob = cassandraTemplate.selectOneById(query(where("age").is(33)), Person.class); ----- -==== - -You can use the following operations to insert and save: - -* `void` *insert* `(Object objectToSave)`: Inserts the object in an Apache Cassandra table. -* `WriteResult` *insert* `(Object objectToSave, InsertOptions options)`: Inserts the object in an Apache Cassandra table and applies `InsertOptions`. - -You can use the following update operations: - -* `void` *update* `(Object objectToSave)`: Updates the object in an Apache Cassandra table. -* `WriteResult` *update* `(Object objectToSave, UpdateOptions options)`: Updates the object in an Apache Cassandra table and applies `UpdateOptions`. - -You can also use the old fashioned way and write your own CQL statements, as the following example shows: - -[source,java] ----- -String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')"; - -cassandraTemplate().getCqlOperations().execute(cql); ----- - -You can also configure additional options such as TTL, consistency level, and lightweight transactions when using `InsertOptions` and `UpdateOptions`. - -[[cassandra.template.insert-update.table]] -==== Which Table Are My Rows Inserted into? - -You can manage the table name that is used for operating on the tables in two ways. -The default table name is the simple class name changed to start with a lower-case letter. -So, an instance of the `com.example.Person` class would be stored in the `person` table. -The second way is to specify a table name in the `@Table` annotation. - -[[cassandra.template.batch]] -==== Inserting, Updating, and Deleting Individual Objects in a Batch - -The Cassandra protocol supports inserting a collection of rows in one operation by using a batch. - -The following methods in the `CassandraTemplate` interface support this functionality: - -* `batchOps`: Creates a new `CassandraBatchOperations` to populate the batch. - -`CassandraBatchOperations` - -* `insert`: Takes a single object, an array (var-args), or an `Iterable` of objects to insert. -* `update`: Takes a single object, an array (var-args), or an `Iterable` of objects to update. -* `delete`: Takes a single object, an array (var-args), or an `Iterable` of objects to delete. -* `withTimestamp`: Applies a TTL to the batch. -* `execute`: Executes the batch. - -[[cassandra.template.update]] -=== Updating Rows in a Table - -For updates, you can select to update a number of rows. - -The following example shows updating a single account object by adding a one-time $50.00 bonus to the balance with the `+` assignment: - -.Updating rows using `CasandraTemplate` -==== -[source,java] ----- -import static org.springframework.data.cassandra.core.query.Criteria.where; -import org.springframework.data.cassandra.core.query.Query; -import org.springframework.data.cassandra.core.query.Update; - -… - -boolean applied = cassandraTemplate.update(Query.query(where("id").is("foo")), - Update.create().increment("balance", 50.00), Account.class); ----- -==== - -In addition to the `Query` discussed earlier, we provide the update definition by using an `Update` object. -The `Update` class has methods that match the update assignments available for Apache Cassandra. - -Most methods return the `Update` object to provide a fluent API for code styling purposes. - -[[cassandra.template.update.methods]] -==== Methods for Executing Updates for Rows - -The update method can update rows, as follows: - -* `boolean` *update* `(Query query, Update update, Class entityClass)`: Updates a selection of objects in the Apache Cassandra table. - -[[cassandra.template.update.update]] -==== Methods for the Update class - -The `Update` class can be used with a little 'syntax sugar', as its methods are meant to be chained together. -Also, you can kick-start the creation of a new `Update` instance with the static method `public static Update update(String key, Object value)` and by using static imports. - -The `Update` class has the following methods: - -* `AddToBuilder` *addTo* `(String columnName)` `AddToBuilder` entry-point: -** Update `prepend(Object value)`: Prepends a collection value to the existing collection by using the `+` update assignment. - ** Update `prependAll(Object... values)`: Prepends all collection values to the existing collection by using the `+` update assignment. -** Update `append(Object value)`: Appends a collection value to the existing collection by using the `+` update assignment. - ** Update `append(Object... values)`: Appends all collection values to the existing collection by using the `+` update assignment. -** Update `entry(Object key, Object value)`: Adds a map entry by using the `+` update assignment. - ** Update `addAll(Map map)`: Adds all map entries to the map by using the `+` update assignment. -* `Update` *remove* `(String columnName, Object value)`: Removes the value from the collection by using the `-` update assignment. -* `Update` *clear* `(String columnName)`: Clears the collection. -* `Update` *increment* `(String columnName, Number delta)`: Updates by using the `+` update assignment. -* `Update` *decrement* `(String columnName, Number delta)`: Updates by using the `-` update assignment. -* `Update` *set* `(String columnName, Object value)`: Updates by using the `=` update assignment. -* `SetBuilder` *set* `(String columnName)` `SetBuilder` entry-point: -** Update `atIndex(int index).to(Object value)`: Sets a collection at the given index to a value using the `=` update assignment. -** Update `atKey(String object).to(Object value)`: Sets a map entry at the given key to a value the `=` update assignment. - -The following listing shows a few update examples: - -==== -[source] ----- -// UPDATE … SET key = 'Spring Data'; -Update.update("key", "Spring Data") - -// UPDATE … SET key[5] = 'Spring Data'; -Update.empty().set("key").atIndex(5).to("Spring Data"); - -// UPDATE … SET key = key + ['Spring', 'DATA']; -Update.empty().addTo("key").appendAll("Spring", "Data"); ----- -==== - -Note that `Update` is immutable once created. -Invoking methods creates new immutable (intermediate) `Update` objects. - -[[cassandra.template.delete]] -=== Methods for Removing Rows - -You can use the following overloaded methods to remove an object from the database: - -* `boolean` *delete* `(Query query, Class entityClass)`: Deletes the objects selected by `Query`. -* `T` *delete* `(T entity)`: Deletes the given object. -* `T` *delete* `(T entity, QueryOptions queryOptions)`: Deletes the given object applying `QueryOptions`. -* `boolean` *deleteById* `(Object id, Class entityClass)`: Deletes the object using the given Id. - -[[cassandra.template.optimistic-locking]] -=== Optimistic Locking - -The `@Version` annotation provides syntax similar to that of JPA in the context of Cassandra and makes sure updates are only applied to rows with a matching version. -Optimistic Locking leverages Cassandra's lightweight transactions to conditionally insert, update and delete rows. -Therefore, `INSERT` statements are executed with the `IF NOT EXISTS` condition. -For updates and deletes, the actual value of the version property is added to the `UPDATE` condition in such a way that the modification does not have any effect if another operation altered the row in the meantime. -In that case, an `OptimisticLockingFailureException` is thrown. -The following example shows these features: - -==== -[source,java] ----- -@Table -class Person { - - @Id String id; - String firstname; - String lastname; - @Version Long version; -} - -Person daenerys = template.insert(new Person("Daenerys")); <1> - -Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); <2> - -daenerys.setLastname("Targaryen"); -template.save(daenerys); <3> - -template.save(tmp); // throws OptimisticLockingFailureException <4> ----- -<1> Intially insert document. `version` is set to `0`. -<2> Load the just inserted document. `version` is still `0`. -<3> Update the document with `version = 0`. -Set the `lastname` and bump `version` to `1`. -<4> Try to update the previously loaded document that still has `version = 0`. -The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`. -==== - -NOTE: Optimistic Locking is only supported with single-entity operations and not for batch operations. - -[[cassandra.template.query]] -== Querying Rows - -You can express your queries by using the `Query` and `Criteria` classes, which have method names that reflect the native Cassandra predicate operator names, such as `lt`, `lte`, `is`, and others. - -The `Query` and `Criteria` classes follow a fluent API style so that you can easily chain together multiple method criteria and queries while having easy-to-understand code. -Static imports are used in Java when creating `Query` -and `Criteria` instances to improve readability. - -[[cassandra.template.query.table]] -=== Querying Rows in a Table - -In earlier sections, we saw how to retrieve a single object by using the `selectOneById` method on `CassandraTemplate`. -Doing so returns a single domain object. -We can also query for a collection of rows to be returned as a list of domain objects. -Assuming we have a number of `Person` objects with name and age values stored as rows in a table and that each person has an account balance, we can now run a query by using the following code: - -.Querying for rows using `CassandraTemplate` -==== -[source,java] ----- -import static org.springframework.data.cassandra.core.query.Criteria.where; -import static org.springframework.data.cassandra.core.query.Query.query; - -… - -List result = cassandraTemplate.select(query(where("age").is(50)) - .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class); ----- -==== - -The `select`, `selectOne`, and `stream` methods take a `Query` object as a parameter. -This object defines the criteria and options used to perform the query. -The criteria is specified by using a `Criteria` object that has a static factory method named `where` that instantiates a new `Criteria` object. -We recommend using a static import for `org.springframework.data.cassandra.core.query.Criteria.where` and `Query.query`, to make the query more readable. - -This query should return a list of `Person` objects that meet the specified criteria. -The `Criteria` class has the following methods that correspond to the operators provided in Apache Cassandra: - -[[cassandra.template.query.criteria]] -==== Methods for the Criteria class - -* `CriteriaDefinition` *gt* `(Object value)`: Creates a criterion by using the `>` operator. -* `CriteriaDefinition` *gte* `(Object value)`: Creates a criterion by using the `>=` operator. -* `CriteriaDefinition` *in* `(Object... values)`: Creates a criterion by using the `IN` operator for a varargs argument. -* `CriteriaDefinition` *in* `(Collection collection)`: Creates a criterion by using the `IN` operator using a collection. -* `CriteriaDefinition` *is* `(Object value)`: Creates a criterion by using field matching (`column = value`). -* `CriteriaDefinition` *lt* `(Object value)`: Creates a criterion by using the `<` operator. -* `CriteriaDefinition` *lte* `(Object value)`: Creates a criterion by using the `<=` operator. -* `CriteriaDefinition` *like* `(Object value)`: Creates a criterion by using the `LIKE` operator. -* `CriteriaDefinition` *contains* `(Object value)`: Creates a criterion by using the `CONTAINS` operator. -* `CriteriaDefinition` *containsKey* `(Object key)`: Creates a criterion by using the `CONTAINS KEY` operator. - -`Criteria` is immutable once created. - -[[cassandra.template.query.query-class]] -==== Methods for the Query class - -The `Query` class has some additional methods that you can use to provide options for the query: - -* `Query` *by* `(CriteriaDefinition... criteria)`: Used to create a `Query` object. -* `Query` *and* `(CriteriaDefinition criteria)`: Used to add additional criteria to the query. -* `Query` *columns* `(Columns columns)`: Used to define columns to be included in the query results. -* `Query` *limit* `(Limit limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting). -* `Query` *limit* `(long limit)`: Used to limit the size of the returned results to the provided limit (used `SELECT` limiting). -* `Query` *pageRequest* `(Pageable pageRequest)`: Used to associate `Sort`, `PagingState`, and `fetchSize` with the query (used for paging). -* `Query` *pagingState* `(ByteBuffer pagingState)`: Used to associate a `ByteBuffer` with the query (used for paging). -* `Query` *queryOptions* `(QueryOptions queryOptions)`: Used to associate `QueryOptions` with the query. -* `Query` *sort* `(Sort sort)`: Used to provide a sort definition for the results. -* `Query` *withAllowFiltering* `()`: Used to render `ALLOW FILTERING` queries. - -`Query` is immutable once created. -Invoking methods creates new immutable (intermediate) `Query` objects. - -[[cassandra.template.query.rows]] -=== Methods for Querying for Rows - -The `Query` class has the following methods that return rows: - -* `List` *select* `(Query query, Class entityClass)`: Query for a list of objects of type `T` from the table. -* `T` *selectOne* `(Query query, Class entityClass)`: Query for a single object of type `T` from the table. -* `Slice` *slice* `(Query query, Class entityClass)`: Starts or continues paging by querying for a `Slice` of objects of type `T` from the table. -* `Stream` *stream* `(Query query, Class entityClass)`: Query for a stream of objects of type `T` from the table. -* `List` *select* `(String cql, Class entityClass)`: Ad-hoc query for a list of objects of type `T` from the table by providing a CQL statement. -* `T` *selectOne* `(String cql, Class entityClass)`: Ad-hoc query for a single object of type `T` from the table by providing a CQL statement. -* `Stream` *stream* `(String cql, Class entityClass)`: Ad-hoc query for a stream of objects of type `T` from the table by providing a CQL statement. - -The query methods must specify the target type `T` that is returned. - -[[cassandra.template.query.fluent-template-api]] -=== Fluent Template API - -The `CassandraOperations` interface is one of the central components when it comes to more low-level interaction with Apache Cassandra. -It offers a wide range of methods. -You can find multiple overloads for every method. -Most of them cover optional (nullable) parts of the API. - -`FluentCassandraOperations` provide a more narrow interface for common methods of `CassandraOperations` -providing a more readable, fluent API. The entry points (`query(…)`, `insert(…)`, `update(…)`, and `delete(…)`) follow a natural naming scheme based on the operation to execute. -Moving on from the entry point, the API is designed to offer only context-dependent methods that guide the developer towards a terminating method that invokes the actual `CassandraOperation`. -The following example shows the fluent API: - -==== -[source,java] ----- -List all = ops.query(SWCharacter.class) - .inTable("star_wars") <1> - .all(); ----- -<1> Skip this step if `SWCharacter` defines the table name with `@Table` or if using the class name as the table name is not a problem. -==== - -If a table in Cassandra holds entities of different types, such as a `Jedi` within a Table of `SWCharacters`, you can use different types to map the query result. -You can use `as(Class targetType)` to map results to a different target type, while `query(Class entityType)` still applies to the query and table name. -The following example uses the `query` and `as` methods: - -==== -[source,java] ----- -List all = ops.query(SWCharacter.class) <1> - .as(Jedi.class) <2> - .matching(query(where("jedi").is(true))) - .all(); ----- -<1> The query fields are mapped against the `SWCharacter` type. -<2> Resulting rows are mapped into `Jedi`. -==== - -TIP: You can directly apply <> to resulting documents by providing only the `interface` type through `as(Class)`. - -The terminating methods (`first()`, `one()`, `all()`, and `stream()`) handle switching between retrieving a single entity and retrieving multiple entities as `List` or `Stream` and similar operations. - -WARNING: The new fluent template API methods (that is, `query(..)`, `insert(..)`, `update(..)`, and `delete(..)`) use effectively thread-safe supporting objects to compose the CQL statement. -However, it comes at the added cost of additional young-gen JVM heap overhead, since the design is based on final fields for the various CQL statement components and construction on mutation. -You should be careful when possibly inserting or deleting a large number of objects (such as inside of a loop, for instance). - -[[cassandra.template.prepared-statements]] -== Prepared Statements - -CQL statements that are executed multiple times can be prepared and stored in a `PreparedStatement` object to improve query performance. -Both, the driver and Cassandra maintain a mapping of `PreparedStatement` queries to their metadata. -You can use prepared statements through the following abstractions: - -* `CqlTemplate` through the choice of API -* `CassandraTemplate` by enabling prepared statements -* Cassandra repositories as they are built on `CassandraTemplate` - -[[cassandra.template.prepared-statements.cql]] -=== Using `CqlTemplate` - -The `CqlTemplate` class (and its asynchronous and reactive variants) offers various methods accepting static CQL, `Statement` objects and `PreparedStatementCreator`. -Methods accepting static CQL without additional arguments typically run the CQL statement as-is without further processing. -Methods accepting static CQL in combination with an arguments array (such as `execute(String cql, Object... args)` and `queryForRows(String cql, Object... args)`) use prepared statements. -Internally, these methods create a `PreparedStatementCreator` and `PreparedStatementBinder` objects to prepare the statement and later on to bind values to the statement to run it. -Spring Data Cassandra generally uses index-based parameter bindings for prepared statements. - -Since Cassandra Driver version 4, prepared statements are cached on the driver level which removes the need to keep track of prepared statements in the application. - -The following example shows how to issue a query with a parametrized prepared statement: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=lastName] ----- -==== - -In cases where you require more control over statement preparation and parameter binding (for example, using named binding parameters), you can fully control prepared statement creation and parameter binding by calling query methods with `PreparedStatementCreator` and `PreparedStatementBinder` arguments: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CqlTemplateExamples.java[tags=preparedStatement] ----- -==== - -Spring Data Cassandra ships with classes supporting that pattern in the `cql` package: - -* `SimplePreparedStatementCreator` - utility class to create a prepared statement. -* `ArgumentPreparedStatementBinder` - utility class to bind arguments to a prepared statement. - -[[cassandra.template.prepared-statements.cassandra-template]] -=== Using `CassandraTemplate` - -The `CassandraTemplate` class is built on top of `CqlTemplate` to provide a higher level of abstraction. -The use of prepared statements can be controlled directly on `CassandraTemplate` (and its asynchronous and reactive variants) by calling `setUsePreparedStatements(false)` respective `setUsePreparedStatements(true)`. -Note that the use of prepared statements by `CassandraTemplate` is enabled by default. - -The following example shows the use of methods that generate and that accept CQL: - -==== -[source,java,indent=0] ----- -include::../{example-root}/CassandraTemplateExamples.java[tags=preparedStatement] ----- -==== - -Calling entity-bound methods such as `select(Query, Class)` or `update(Query, Update, Class)` build CQL statements themselves to perform the intended operations. -Some `CassandraTemplate` methods (such as `select(Statement, Class)`) also accepts CQL `Statement` objects as part of their API. - -It's possible to participate in prepared statements when calling methods accepting a `Statement` with a `SimpleStatement` object. -The template API extracts the query string and parameters (positional and named parameters) and uses these to prepare, bind, and run the statement. -Non-``SimpleStatement`` objects cannot be used with prepared statements. - -[[cassandra.template.prepared-statements.caching]] -=== Caching Prepared Statements - -Since Cassandra driver 4.0, prepared statements are cached by the `CqlSession` cache so it is okay to prepare the same string twice. -Previous versions required caching of prepared statements outside of the driver. -See also the https://docs.datastax.com/en/developer/java-driver/latest/manual/core/statements/prepared/[Driver documentation on Prepared Statements] for further reference. diff --git a/src/main/asciidoc/reference/exception-translation.adoc b/src/main/asciidoc/reference/exception-translation.adoc deleted file mode 100644 index b7922409e..000000000 --- a/src/main/asciidoc/reference/exception-translation.adoc +++ /dev/null @@ -1,10 +0,0 @@ -== Exception Translation - -The Spring Framework provides exception translation for a wide variety of database and mapping technologies. -This has traditionally been for JDBC and JPA. -Spring Data for Apache Cassandra extends this feature to Apache Cassandra by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface. - -The motivation behind mapping to Spring's {springDocsUrl}html/dao.html#dao-exceptions[consistent data access exception hierarchy] -is to let you write portable and descriptive exception handling code without resorting to coding against and handling specific Cassandra exceptions. -All of Spring's data access exceptions are inherited from the -`DataAccessException` class, so you can be sure that you can catch all database-related exceptions within a single try-catch block. diff --git a/src/main/asciidoc/reference/migration-guides.adoc b/src/main/asciidoc/reference/migration-guides.adoc deleted file mode 100644 index c7f13c632..000000000 --- a/src/main/asciidoc/reference/migration-guides.adoc +++ /dev/null @@ -1,8 +0,0 @@ -[[cassandra.migration]] -= Appendix E: Migration Guides - -include::migration-guide-1.5-to-2.0.adoc[leveloffset=+1] - -include::migration-guide-2.2-to-3.0.adoc[leveloffset=+1] - -include::migration-guide-3.0-to-4.0.adoc[leveloffset=+1] diff --git a/src/main/asciidoc/reference/reactive-cassandra-repositories.adoc b/src/main/asciidoc/reference/reactive-cassandra-repositories.adoc deleted file mode 100644 index 05b227315..000000000 --- a/src/main/asciidoc/reference/reactive-cassandra-repositories.adoc +++ /dev/null @@ -1,181 +0,0 @@ -[[cassandra.reactive.repositories]] -= Reactive Cassandra Repositories - -This chapter outlines the specialties handled by the reactive repository support for Apache Cassandra. -It builds on the core repository infrastructure explained in <>, so you should have a good understanding of the basic concepts explained there. - -Cassandra repositories use `ReactiveCassandraTemplate` and its wired `ReactiveCqlTemplate` as infrastructure beans. - -Reactive usage is broken up into two phases: Composition and Execution. - -Calling repository methods lets you compose a reactive sequence by obtaining `Publisher` instances and applying operators. -No I/O happens until you subscribe. -Passing the reactive sequence to a reactive execution infrastructure, such as {springDocsUrl}web.html#web-reactive[Spring WebFlux] -or https://vertx.io/docs/vertx-reactive-streams/java/[Vert.x]), subscribes to the publisher and initiate the actual execution. -See https://projectreactor.io/docs/core/release/reference/#reactive.subscribe[the Project reactor documentation] for more detail. - -[[cassandra.reactive.repositories.libraries]] -== Reactive Composition Libraries - -The reactive space offers various reactive composition libraries. -The most common libraries are -https://github.com/ReactiveX/RxJava[RxJava] and https://projectreactor.io/[Project Reactor]. - -Spring Data for Apache Cassandra is built on top of the https://github.com/datastax/java-driver[DataStax Cassandra Driver]. -The driver is not reactive but the asynchronous capabilities allow us to adopt and expose the `Publisher` APIs to provide maximum interoperability by relying on the https://www.reactive-streams.org/[Reactive Streams] initiative. -Static APIs, such as `ReactiveCassandraOperations`, are provided by using Project Reactor's `Flux` and `Mono` types. -Project Reactor offers various adapters to convert reactive wrapper types (`Flux` to `Observable` and back), but conversion can easily clutter your code. - -Spring Data's repository abstraction is a dynamic API that is mostly defined by you and your requirements as you declare query methods. -Reactive Cassandra repositories can be implemented by using either RxJava or Project Reactor wrapper types by extending from one of the library-specific repository interfaces: - -* `ReactiveCrudRepository` -* `ReactiveSortingRepository` -* `RxJava2CrudRepository` -* `RxJava2SortingRepository` - -Spring Data converts reactive wrapper types behind the scenes so that you can stick to your favorite composition library. - -[[cassandra.reactive.repositories.usage]] -== Usage - -To access domain entities stored in Apache Cassandra, you can use Spring Data's sophisticated repository support, which significantly eases implementing DAOs. -To do so, create an interface for your repository, as the following example shows: - -.Sample Person entity -==== -[source,java] ----- -@Table -public class Person { - - @Id - private String id; - private String firstname; - private String lastname; - - // … getters and setters omitted -} ----- -==== - -Note that the entity has a property named `id` of type `String`. -The default serialization mechanism used in `CassandraTemplate` (which backs the repository support) regards properties named `id` as being the row ID. - -The following example shows a repository definition to persist `Person` entities: - -.Basic repository interface to persist `Person` entities -==== -[source] ----- -public interface ReactivePersonRepository extends ReactiveSortingRepository { - - Flux findByFirstname(String firstname); <1> - - Flux findByFirstname(Publisher firstname); <2> - - Mono findByFirstnameAndLastname(String firstname, String lastname); <3> - - Mono findFirstByFirstname(String firstname); <4> - - @AllowFiltering - Flux findByAge(int age); <5> -} ----- -<1> A query for all people with the given `firstname`. -The query is derived by parsing the method name for constraints, which can be concatenated with `And` and `Or`. -Thus, the method name results in a query expression of `SELECT * FROM person WHERE firstname = :firstname`. -<2> A query for all people with the given `firstname` once the `firstname` is emitted from the given `Publisher`. -<3> Find a single entity for the given criteria. -Completes with `IncorrectResultSizeDataAccessException` on non-unique results. -<4> Unlike the preceding query, the first entity is always emitted even if the query yields more result rows. -<5> A query method annotated with `@AllowFiltering`, which allows server-side filtering. -==== - -For Java configuration, use the `@EnableReactiveCassandraRepositories` annotation. -The annotation carries the same attributes as the corresponding XML namespace element. -If no base package is configured, the infrastructure scans the package of the annotated configuration class. -The following example uses the `@EnableReactiveCassandraRepositories` annotation: - -.Java configuration for repositories -==== -[source,java] ----- -@Configuration -@EnableReactiveCassandraRepositories -class ApplicationConfig extends AbstractReactiveCassandraConfiguration { - - @Override - protected String getKeyspaceName() { - return "keyspace"; - } - - public String[] getEntityBasePackages() { - return new String[] { "com.oreilly.springdata.cassandra" }; - } -} ----- -==== - -Since our domain repository extends `ReactiveSortingRepository`, it provides you with CRUD operations as well as methods for sorted access to the entities. -Working with the repository instance is a matter of dependency injecting it into a client, as the following example shows: - -.Sorted access to Person entities -==== -[source,java] ----- -public class PersonRepositoryTests { - - @Autowired ReactivePersonRepository repository; - - @Test - public void sortsElementsCorrectly() { - Flux people = repository.findAll(Sort.by(new Order(ASC, "lastname"))); - } -} ----- -==== - -Cassandra repositories support paging and sorting for paginated and sorted access to the entities. -Cassandra paging requires a paging state to forward-only navigate through pages. -A `Slice` keeps track of the current paging state and allows for creation of a `Pageable` to request the next page. -The following example shows how to set up paging access to `Person` entities: - -.Paging access to `Person` entities -==== -[source,java] ----- -@ExtendWith(SpringExtension.class) -class PersonRepositoryTests { - - @Autowired PersonRepository repository; - - @Test - void readsPagesCorrectly() { - - Mono> firstBatch = repository.findAll(CassandraPageRequest.first(10)); - - Mono> nextBatch = firstBatch.flatMap(it -> repository.findAll(it.nextPageable())); - - // … - } -} ----- -==== - -The preceding example creates an application context with Spring's unit test support, which performs annotation-based dependency injection into the test class. -Inside the test cases (the test methods), we use the repository to query the data store. -We invoke the repository query method that requests all `Person` instances. - -[[cassandra.reactive.repositories.features]] -== Features - -Spring Data's Reactive Cassandra support comes with the same set of features as the support for <>. - -It supports the following features: - -* Query Methods that use <> -* <> - -NOTE: Query methods must return a reactive type. -Resolved types (`User` versus `Mono`) are not supported. diff --git a/src/main/asciidoc/reference/reactive-cassandra.adoc b/src/main/asciidoc/reference/reactive-cassandra.adoc deleted file mode 100644 index b8cda1a1b..000000000 --- a/src/main/asciidoc/reference/reactive-cassandra.adoc +++ /dev/null @@ -1,441 +0,0 @@ -[[cassandra.reactive]] -= Reactive Cassandra Support - -The reactive Cassandra support contains a wide range of features: - -* Spring configuration support using Java-based `@Configuration` classes. -* `ReactiveCqlTemplate` helper class that increases productivity by properly handling common Cassandra data access operations. -* `ReactiveCassandraTemplate` helper class that increases productivity by using `ReactiveCassandraOperations` in a reactive manner. -It includes integrated object mapping between tables and POJOs. -* Exception translation into Spring's portable {springDocsUrl}data-access.html#dao-exceptions[Data Access Exception Hierarchy]. -* Feature rich object mapping integrated with Spring's {springDocsUrl}core.html#core-convert[Conversion Service]. -* Java-based Query, Criteria, and Update DSLs. -* Automatic implementation of `Repository` interfaces, including support for custom finder methods. - -For most data-oriented tasks, you can use the `ReactiveCassandraTemplate` or the repository support, which use the rich object mapping functionality. `ReactiveCqlTemplate` is commonly used to increment counters or perform ad-hoc CRUD operations. `ReactiveCqlTemplate` also provides callback methods that make it easy to get low-level API objects, such as `com.datastax.oss.driver.api.core.CqlSession`, which let you communicate directly with Cassandra. -Spring Data for Apache Cassandra uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are immediately familiar and so that you can map your existing knowledge onto the Spring APIs. - -[[cassandra.reactive.getting-started]] -== Getting Started - -Spring Data for Apache Cassandra requires Apache Cassandra 2.1 or later and Datastax Java Driver 4.0 or later. -An easy way to quickly set up and bootstrap a working environment is to create a Spring-based project in https://spring.io/tools[Spring Tools] or use https://start.spring.io/[Spring Initializer]. - -First, you need to set up a running Apache Cassandra server. -See the -https://cassandra.apache.org/doc/latest/getting_started/index.html[Apache Cassandra Quick Start Guide] -for an explanation on how to start Apache Cassandra. -Once installed, starting Cassandra is typically a matter of running the following command: `CASSANDRA_HOME/bin/cassandra -f`. - -To create a Spring project in STS, go to File -> New -> Spring Template Project -> Simple Spring Utility Project and press Yes when prompted. -Then enter a project and a package name, such as `org.spring.data.cassandra.example`. - -Then you can add the following dependency declaration to your pom.xml file's `dependencies` section. - -==== -[source,xml,subs="verbatim,attributes"] ----- - - - - org.springframework.data - spring-data-cassandra - {version} - - - ----- -==== - -Also, you should change the version of Spring in the pom.xml file to be as follows: - -==== -[source,xml,subs="verbatim,attributes"] ----- -{springVersion} ----- -==== - -If using a milestone release instead of a GA release, you also need to add the location of the Spring Milestone repository for Maven to your pom.xml file so that it is at the same level of your `` element, as follows: - -[source,xml] ----- - - - spring-milestone - Spring Maven MILESTONE Repository - https://repo.spring.io/milestone - - ----- - -The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here]. - -You can also browse all Spring repositories https://repo.spring.io/webapp/#/home[here]. - -Now you can create a simple Java application that stores and reads a domain object to and from Cassandra. - -To do so, first create a simple domain object class to persist, as the following example shows: - -==== -[source,java] ----- -include::../{example-root}/Person.java[tags=file] ----- -==== - -Next, create the main application to run, as the following example shows: - -==== -[source,java] ----- -include::../{example-root}/ReactiveCassandraApplication.java[tags=file] ----- -==== - -Even in this simple example, there are a few notable things to point out: - -* A fully synchronous flow does not benefit from a reactive infrastructure, because a reactive programming model requires synchronization. -* You can create an instance of `ReactiveCassandraTemplate` with a Cassandra `CqlSession`. -* You must annotate your POJO as a Cassandra `@Table` and annotate the `@PrimaryKey`. -Optionally, you can override these mapping names to match your Cassandra database table and column names. -* You can either use raw CQL or the DataStax `QueryBuilder` API to construct your queries. - -[[cassandra.reactive.examples-repo]] -== Examples Repository - -A https://github.com/spring-projects/spring-data-examples[Github repository] contains several examples that you can download and play around with to get a feel for how the library works. - -[[cassandra.reactive.connectors]] -== Connecting to Cassandra with Spring - -One of the first tasks when using Apache Cassandra with Spring is to create a `com.datastax.oss.driver.api.core.CqlSession` object by using the Spring IoC container. -You can do so either by using Java-based bean metadata or by using XML-based bean metadata. -These are discussed in the following sections. - -NOTE: For those not familiar with how to configure the Spring container using Java-based bean metadata instead of XML-based metadata, see the high-level introduction in the reference docs -https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java[here] -as well as the detailed documentation {springDocsUrl}core.html#beans-java-instantiating-container[here]. - -[[reactive.cassandra.java-config]] -=== Registering a Session instance using Java-based metadata - -You can configure Reactive Cassandra support by using <>. -Reactive Cassandra support adapts a `CqlSession` to provide a reactive processing model on top of an asynchronous driver. - -A reactive `CqlSession` is configured similarly to an imperative `CqlSession`. -We provide supporting configuration classes that come with predefined defaults and require only environment-specific information to configure Spring Data for Apache Cassandra. -The base class for reactive support is `AbstractReactiveCassandraConfiguration`. -This configuration class extends the imperative `AbstractCassandraConfiguration`, so the reactive support also configures the imperative API support. -The following example shows how to register Apache Cassandra beans in a configuration class: -ReactiveAppCassandraConfiguration .Registering Spring Data for Apache Cassandra beans using `AbstractReactiveCassandraConfiguration` - -==== -[source,java] ----- -include::../{example-root}/ReactiveCassandraConfiguration.java[tags=class] ----- -==== - -The configuration class in the preceding example is schema-management-enabled to create CQL objects during startup. -See <> for further details. - -[[cassandra.reactive.cql-template]] -== `ReactiveCqlTemplate` - -The `ReactiveCqlTemplate` class is the central class in the core CQL package. -It handles the creation and release of resources. -It performs the basic tasks of the core CQL workflow, such as creating and running statements, leaving application code to provide CQL and extract results. -The `ReactiveCqlTemplate` class runs CQL queries and update statements and performs iteration over `ResultSet` instances and extraction of returned parameter values. -It also catches CQL exceptions and translates them into the generic, more informative, exception hierarchy defined in the `org.springframework.dao` package. - -When you use the `ReactiveCqlTemplate` in your code, you need only implement callback interfaces, which have a clearly defined contract. -Given a `Connection`, the `ReactivePreparedStatementCreator` callback interface creates a <> with the provided CQL and any necessary parameter arguments. -The `RowCallbackHandler` -interface extracts values from each row of a `ReactiveResultSet`. - -The `ReactiveCqlTemplate` can be used within a DAO implementation through direct instantiation with a `ReactiveSessionFactory` -reference or be configured in the Spring container and given to DAOs as a bean reference. `ReactiveCqlTemplate` is a foundational building block for <>. - -All CQL issued by this class is logged at the `DEBUG` level under the category corresponding to the fully-qualified class name of the template instance (typically `ReactiveCqlTemplate`, but it may be different if you use a custom subclass of the `ReactiveCqlTemplate` class). - -[[cassandra.reactive.cql-template.examples]] -=== Examples of `ReactiveCqlTemplate` Class Usage - -This section provides some examples of `ReactiveCqlTemplate` class usage. -These examples are not an exhaustive list of all of the functionality exposed by the `ReactiveCqlTemplate`. -See the attendant https://docs.spring.io/spring-data/cassandra/docs/{version}/api/org/springframework/data/cassandra/core/cql/ReactiveCqlTemplate.html[Javadocs] for that. - -[[cql-template.examples.query]] -==== Querying (SELECT) with `ReactiveCqlTemplate` - -The following query gets the number of rows in a relation: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=rowCount] ----- -==== - -The following query uses a bind variable: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=countOfActorsNamedJoe] ----- -==== - -The following example queries for a `String`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=lastName] ----- -==== - -The following example queries and populates a single domain object: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=rowMapper] ----- -==== - -The following example queries and populates a number of domain objects: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=listOfRowMapper] ----- -==== - -If the last two snippets of code actually existed in the same application, it would make sense to remove the duplication present in the two `RowMapper` anonymous inner classes and extract them into a single class (typically a `static` nested class) that can then be referenced by DAO methods as needed. - -For example, it might be better to write the last code snippet as follows: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=findAllActors] ----- -==== - -[[cassandra.reactive.cql-template.examples.update]] -==== `INSERT`, `UPDATE`, and `DELETE` with `ReactiveCqlTemplate` - -You can use the `execute(…)` method to perform `INSERT`, `UPDATE`, and `DELETE` operations. -Parameter values are usually provided as variable arguments or, alternatively, as an object array. - -The following example shows how to perform an `INSERT` operation with `ReactiveCqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=insert] ----- -==== - -The following example shows how to perform an `UPDATE` operation with `ReactiveCqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=update] ----- -==== - -The following example shows how to perform an `DELETE` operation with `ReactiveCqlTemplate`: - -==== -[source,java,indent=0] ----- -include::../{example-root}/ReactiveCqlTemplateExamples.java[tags=delete] ----- -==== - -[[cassandra.reactive.exception]] -include::exception-translation.adoc[] - -`ReactiveCqlTemplate` and `ReactiveCassandraTemplate` propagate exceptions as early as possible. -Exceptions that occur during the processing of the reactive sequence are emitted as error signals. - -[[cassandra.reactive.template]] -== Introduction to `ReactiveCassandraTemplate` - -The `ReactiveCassandraTemplate` class, located in the `org.springframework.data.cassandra` package, is the central class in Spring Data's Cassandra support. -It provides a rich feature set to interact with the database. -The template offers convenience data access operations to create, update, delete, and query Cassandra and provides a mapping between your domain objects and Cassandra table rows. - -NOTE: Once configured, `ReactiveCassandraTemplate` is thread-safe and can be reused across multiple instances. - -The mapping between rows in a Cassandra table and domain classes is done by delegating to an implementation of the `CassandraConverter` interface. -Spring provides a default implementation, `MappingCassandraConverter`, but you can also write your own custom converter. -See "`<>`" for more detailed information. - -The `ReactiveCassandraTemplate` class implements the `ReactiveCassandraOperations` interface. -As often as possible, the methods names `ReactiveCassandraOperations` match names in Cassandra to make the API familiar to developers who are familiar with Cassandra. - -For example, you can find methods such as `select`, `insert`, `delete`, and `update`. -The design goal was to make it as easy as possible to transition between the use of the base Cassandra driver and `ReactiveCassandraOperations`. -A major difference between the two APIs is that `ReactiveCassandraOperations` can be passed domain objects instead of CQL and query objects. - -NOTE: The preferred way to reference operations on a `ReactiveCassandraTemplate` instance is through its interface, -`ReactiveCassandraOperations`. - -The default converter implementation for `ReactiveCassandraTemplate` is `MappingCassandraConverter`. -While the `MappingCassandraConverter` can make use of additional metadata to specify the mapping of objects to rows, it can also convert objects that contain no additional metadata by using conventions for the mapping of fields and table names. -These conventions, as well as the use of mapping annotations, are explained in "`<>`". - -Another central feature of `CassandraTemplate` is exception translation. -Exceptions thrown by the Cassandra Java driver are translated into Spring's portable Data Access Exception hierarchy. -See "`<>`" for more information. - -[[cassandra.reactive.template.instantiating]] -=== Instantiating `ReactiveCassandraTemplate` - -`ReactiveCassandraTemplate` should always be configured as a Spring bean, although an earlier example showed how to instantiate it directly. -However, this section assumes that the template is used in a Spring module, so it also assumes that the Spring container is being used. - -There are two ways to get a `ReactiveCassandraTemplate`, depending on how you load you Spring `ApplicationContext`: - -* <> -* <> - -[float] -[[reactive.cassandra.template.autowiring]] -==== Autowiring - -You can autowire a `ReactiveCassandraTemplate` into your project, as the following example shows: - -==== -[source,java] ----- -@Autowired -private ReactiveCassandraOperations reactiveCassandraOperations; ----- -==== - -Like all Spring autowiring, this assumes there is only one bean of type `ReactiveCassandraOperations` in the `ApplicationContext`. -If you have multiple `ReactiveCassandraTemplate` beans (which can be the case if you are working with multiple keyspaces in the same project), then you can use the `@Qualifier` annotation to designate which bean you want to autowire. - -==== -[source,java] ----- -@Autowired -@Qualifier("keyspaceTwoTemplateBeanId") -private ReactiveCassandraOperations reactiveCassandraOperations; ----- -==== - -[float] -[[reactive.cassandra.template.application-context]] -==== Bean Lookup with `ApplicationContext` - -You can also look up the `ReactiveCassandraTemplate` bean from the `ApplicationContext`, as shown in the following example: - -==== -[source,java] ----- -ReactiveCassandraOperations reactiveCassandraOperations = applicationContext.getBean("reactiveCassandraOperations", ReactiveCassandraOperations.class); ----- -==== - -[[cassandra.reactive.template.save-update-remove]] -== Saving, Updating, and Removing Rows - -`ReactiveCassandraTemplate` provides a simple way for you to save, update, and delete your domain objects and map those objects to tables managed in Cassandra. - -[[cassandra.reactive.template.insert-update]] -=== Methods for Inserting and Updating rows - -`CassandraTemplate` has several convenient methods for saving and inserting your objects. -To have more fine-grained control over the conversion process, you can register Spring `Converter` instances with the `MappingCassandraConverter` -(for example, `Converter`). - -NOTE: The difference between insert and update operations is that `INSERT` operations do not insert `null` values. - -The simple case of using the `INSERT` operation is to save a POJO. In this case, the table name is determined by the simple class name (not the fully qualified class name). -The table to store the object can be overridden by using mapping metadata. - -When inserting or updating, the `id` property must be set. -Apache Cassandra has no means to generate an ID. - -The following example uses the save operation and retrieves its contents: - -.Inserting and retrieving objects by using the `CassandraTemplate` -==== -[source,java] ----- -import static org.springframework.data.cassandra.core.query.Criteria.where; -import static org.springframework.data.cassandra.core.query.Query.query; -… - -Person bob = new Person("Bob", 33); -cassandraTemplate.insert(bob); - -Mono queriedBob = reactiveCassandraTemplate.selectOneById(query(where("age").is(33)), Person.class); ----- -==== - -You can use the following operations to insert and save: - -* `void` *insert* `(Object objectToSave)`: Inserts the object in an Apache Cassandra table. -* `WriteResult` *insert* `(Object objectToSave, InsertOptions options)`: Inserts the object in an Apache Cassandra table and applies `InsertOptions`. - -You can use the following update operations: - -* `void` *update* `(Object objectToSave)`: Updates the object in an Apache Cassandra table. -* `WriteResult` *update* `(Object objectToSave, UpdateOptions options)`: Updates the object in an Apache Cassandra table and applies `UpdateOptions`. - -You can also use the old fashioned way and write your own CQL statements, as the following example shows: - -[source,java] ----- -String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')"; - -Mono applied = reactiveCassandraTemplate.getReactiveCqlOperations().execute(cql); ----- - -You can also configure additional options such as TTL, consistency level, and lightweight transactions when using `InsertOptions` and `UpdateOptions`. - -[[cassandra.reactive.template.insert-update.table]] -==== Which Table Are My Rows Inserted into? - -You can manage the table name that is used for operating on the tables in two ways. -The default table name is the simple class name changed to start with a lower-case letter. -So, an instance of the `com.example.Person` class would be stored in the `person` table. -The second way is to specify a table name in the `@Table` annotation. - -[[cassandra.reactive.template.update]] -=== Updating Rows in a Table - -For updates, you can select to update a number of rows. - -The following example shows updating a single account object by adding a one-time $50.00 bonus to the balance with the `+` assignment: - -.Updating rows using `ReactiveCasandraTemplate` -==== -[source,java] ----- -import static org.springframework.data.cassandra.core.query.Criteria.where; -import org.springframework.data.cassandra.core.query.Query; -import org.springframework.data.cassandra.core.query.Update; - -… - -Mono wasApplied = reactiveCassandraTemplate.update(Query.query(where("id").is("foo")), - Update.create().increment("balance", 50.00), Account.class); ----- -==== - -In addition to the `Query` discussed earlier, we provide the update definition by using an `Update` object. -The `Update` class has methods that match the update assignments available for Apache Cassandra. - -Most methods return the `Update` object to provide a fluent API for code styling purposes. - -For more detail, see "`<>`". diff --git a/src/main/asciidoc/reference/upgrade.adoc b/src/main/asciidoc/reference/upgrade.adoc deleted file mode 100644 index 0a23e9173..000000000 --- a/src/main/asciidoc/reference/upgrade.adoc +++ /dev/null @@ -1,9 +0,0 @@ -include::../{spring-data-commons-docs}/upgrade.adoc[] - -== What to Read Next - -Once you’ve decided to upgrade your application, you can find detailed information regarding specific features in the rest of the document. -You can find <> specific to major version migrations at the end of this document. - -Spring Data's documentation is specific to that version, so any information that you find in here will contain the most up-to-date changes that are in that version. -