From 714e2ba3b68c604154af8b8fc05c5b29529264e4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 28 Sep 2016 16:20:57 +0200 Subject: [PATCH] DATACASS-272 - Extend documentation. Improve documentation on CassandraTemplate. Explain differences between Spring CQL and Spring Data Cassandra. Add User-Defined-Type mapping example. Fix typos. --- src/main/asciidoc/new-features.adoc | 2 + .../reference/cassandra-repositories.adoc | 1 + src/main/asciidoc/reference/cassandra.adoc | 541 ++++++++++++------ src/main/asciidoc/reference/mapping.adoc | 59 +- 4 files changed, 407 insertions(+), 196 deletions(-) diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 9ea8d0f25..55cc6fe70 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -6,4 +6,6 @@ * Assert compatibility with Cassandra 3.0 and Cassandra Java Driver 3.0. * Configurable `ProtocolVersion` and `QueryOptions` on `Cluster` level. * Support for `Optional` as query method result and argument. +* Declarative query methods using query derivation +* Support for User-Defined types and mapped User-Defined types using `@UserDefinedType`. diff --git a/src/main/asciidoc/reference/cassandra-repositories.adoc b/src/main/asciidoc/reference/cassandra-repositories.adoc index e09dee923..f1cd7a46a 100644 --- a/src/main/asciidoc/reference/cassandra-repositories.adoc +++ b/src/main/asciidoc/reference/cassandra-repositories.adoc @@ -247,6 +247,7 @@ class CassandraTemplateProducer { public CassandraOperations createCassandraOperations(Session session) throws Exception { MappingCassandraConverter cassandraConverter = new MappingCassandraConverter(); + cassandraConverter.setUserTypeResolver(new SimpleUserTypeResolver(session.getCluster(), session.getLoggedKeyspace())); CassandraAdminTemplate cassandraTemplate = new CassandraAdminTemplate(session, cassandraConverter); return cassandraTemplate; diff --git a/src/main/asciidoc/reference/cassandra.adoc b/src/main/asciidoc/reference/cassandra.adoc index 4010f48d6..dcfdeed4e 100644 --- a/src/main/asciidoc/reference/cassandra.adoc +++ b/src/main/asciidoc/reference/cassandra.adoc @@ -21,19 +21,79 @@ such as `com.datastax.driver.core.Session` allowing you to communicate directly uses consistent naming conventions on objects in various APIs to those found in the DataStax Java Driver so that they are familiar and so you can map your existing knowledge onto the Spring APIs. -[[cassandra-getting-started]] + +[[cassandra.modules]] +== Spring CQL and Spring Data Cassandra modules + +Spring Data for Apache Cassandra comes with two modules: Spring CQL and Spring Data Cassandra. + +The value-add provided by the Spring Data for Apache Cassandra abstraction is perhaps best shown by the sequence of actions outlined in the table below. The table shows what actions Spring will take care of and which actions are the responsibility of you, the application developer. + +[[cassandra.modules.who-does-what]] +.Spring CQL - who does what? +|=== +| Action| Spring| You + +| Define connection parameters. +| +| X + +| Open the connection. +| X +| + +| Specify the CQL statement. +| +| X + +| Declare parameters and provide parameter values +| +| X + +| Prepare and execute the statement. +| X +| + +| Set up the loop to iterate through the results (if any). +| X +| + +| Do the work for each iteration. +| +| X + +| Process any exception. +| X +| + +| Close the Session. +| X +| +|=== + +Spring CQL takes care of all the low-level details that can make Cassandra and CQL such a +tedious API to develop with. Spring Data Cassandra adds object mapping, schema generation and repository support to the featureset. + + +[[cassandra.choose-style]] +=== Choosing an approach for Cassandra database access +You can choose among several approaches to form the basis for your Cassandra database access. 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. + +* __CqlTemplate__ is the classic Spring CQL approach and the most popular. This "lowest level" approach and all others use a `CqlTemplate` under the covers. +* __CassandraTemplate__ wraps a `CqlTemplate` to provide 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__ allows you to create repository declarations in your data access layer. The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores. + + +[[cassandra.getting-started]] == Getting Started -Spring Data Cassandra uses the DataStax Java Driver, which supports DataStax Enterprise and Apache Cassandra. The latest commercial or open source release is recommended. The easiest way to setup -a working environment is to create a Spring-based project in http://spring.io/tools/sts[STS]. +Spring Apache Cassandra support requires Cassandra 2.1 or higher, Datastax Java Driver 3.0 or higher and Java SE 6 or higher. An easy way to bootstrap setting up a working environment is to create a Spring based project in http://spring.io/tools/sts[STS]. -First, you need to setup a running Cassandra server. +First you need to set up a running Apache Cassandra server. Refer to the http://cassandra.apache.org/doc/latest/getting_started/index.html[Apache Cassandra Quick Start guide] for an explanation on how to startup 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 add the following to the `pom.xml` dependencies section. +To create a Spring project in STS go to File -> New -> Spring Template Project -> Simple Spring Utility Project -> press Yes when prompted. Then enter a project and a package name such as org.spring.cassandra.example. +Then add the following to pom.xml dependencies section. [source,xml,subs="verbatim,attributes"] ---- @@ -49,44 +109,31 @@ Then add the following to the `pom.xml` dependencies section. ---- -Also, change the version of Spring in `pom.xml` to be +Also change the version of Spring in the pom.xml to be [source,xml,subs="verbatim,attributes"] ---- {springVersion} ---- -If you are using a milestone version (e.g. M1 or RC1) of Spring Data Cassandra, you will also need to include the -Spring Milestone repository declaration to your `pom.xml`, like so... +If using a milestone release instead of a GA release, you will also need to add the location of the Spring Milestone repository for maven to your pom.xml which is at the same level of your element [source,xml] ---- spring-milestone - Spring Milestones Maven Repository + Spring Maven MILESTONE Repository http://repo.spring.io/libs-milestone ---- -If you are on the bleeding edge and use build snapshots, then you will need to include the Spring Snapshots repository -declaration to your `pom.xml`, like so... - -[source,xml] ----- - - - spring-snapshot - Spring Snapshots Maven Repository - http://repo.spring.io/libs-snapshot - - ----- +The repository is also http://repo.spring.io/milestone/org/springframework/data/[browseable here]. You can also browse the Spring repositories https://repo.spring.io/webapp/#/home[here]. -Now we will create a simple little Java application that stores and reads a domain object to/from Cassandra. +Now we will create a simple Java application that stores and reads a domain object to/from Cassandra. First, create a simple domain object class to persist. @@ -100,35 +147,35 @@ import org.springframework.data.cassandra.mapping.Table; @Table public class Person { - @PrimaryKey - private final String id; + @PrimaryKey + private final String id; - private final String name; - private final int age; + private final String name; + private final int age; - public Person(String id, String name, int age) { - this.id = id; - this.name = name; - this.age = age; - } + public Person(String id, String name, int age) { + this.id = id; + this.name = name; + this.age = age; + } - public String getId() { - return id; - } + public String getId() { + return id; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public int getAge() { - return age; - } + public int getAge() { + return age; + } - @Override - public String toString() { - return String.format("{ @type = %1$s, id = %2$s, name = %3$s, age = %4$d }", - getClass().getName(), getId(), getName(), getAge()); - } + @Override + public String toString() { + return String.format("{ @type = %1$s, id = %2$s, name = %3$s, age = %4$d }", + getClass().getName(), getId(), getName(), getAge()); + } } ---- @@ -139,8 +186,6 @@ Next, create the main application to run. package org.spring.data.cassandra.example; import java.io.Closeable; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.UUID; import org.slf4j.Logger; @@ -155,53 +200,34 @@ import com.datastax.driver.core.querybuilder.Select; public class CassandraApplication { - private static final Logger LOGGER = LoggerFactory.getLogger(CassandraApplication.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CassandraApplication.class); - private static Cluster cluster; - private static Session session; + protected static Person newPerson(String name, int age) { + return newPerson(UUID.randomUUID().toString(), name, age); + } - protected static boolean close(Closeable obj) { - if (obj != null) { - try { - obj.close(); - return true; - } - catch (Exception ignore) { - } - } + protected static Person newPerson(String id, String name, int age) { + return new Person(id, name, age); + } - return false; - } + public static void main(String[] args) { - protected static Person newPerson(String name, int age) { - return newPerson(UUID.randomUUID().toString(), name, age); - } + Cluster cluster = Cluster.builder().addContactPoints("localhost").build(); + Session session = cluster.connect("mykeyspace"); - protected static Person newPerson(String id, String name, int age) { - return new Person(id, name, age); - } + CassandraOperations template = new CassandraTemplate(session); - public static void main(String[] args) throws UnknownHostException { - try { - cluster = Cluster.builder().addContactPoints(InetAddress.getLocalHost()).build(); - session = cluster.connect("mykeyspace"); + Person jonDoe = template.insert(newPerson("Jon Doe", 40)); - CassandraOperations template = new CassandraTemplate(session); + Select selectStatement = QueryBuilder.select().from("person"); + selectStatement.where(QueryBuilder.eq("id", jonDoe.getId())); - Person jonDoe = template.insert(newPerson("Jon Doe", 40)); + LOGGER.info(template.queryForObject(selectStatement, Person.class).getId()); - Select selectStatement = QueryBuilder.select().from("person"); - selectStatement.where(QueryBuilder.eq("id", jonDoe.getId())); - - LOGGER.info(template.queryForObject(selectStatement, Person.class).getId()); - - template.truncate("person"); - } - finally { - close(session); - close(cluster); - } - } + template.truncate("person"); + session.close(); + cluster.close(); + } } ---- @@ -212,15 +238,164 @@ Even in this simple example, there are a few things to observe. override these mapping names to match your Cassandra database table and column names. * You can either use a CQL String or the DataStax `QueryBuilder` API to construct you queries. +[[cassandra.examples-repo]] == Examples Repository -After the initial release of Spring Data for Apache Cassandra 1.0.0, we will start working on a showcase repository with full examples. +There is a https://github.com/spring-projects/spring-data-examples[Github repository with several examples] that you can download and play around with to get a feel for how the library works. -[[cassandra-connectors]] +[[cassandra.connectors]] == Connecting to Cassandra with Spring -[[cassandra-connectors.ext_properties]] -=== Externalize Connection Properties +One of the first tasks when using Apache Cassandra and Spring is to create a `com.datastax.driver.core.Session` object using the IoC container. There are two main ways to do this, either using Java based bean metadata or 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 http://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 http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html#beans-java-instantiating-container[ here]. + +[[cassandra.cassandra-java-config]] +=== Registering a Session instance using Java based metadata + +An example of using Java based bean metadata to register an instance of a `com.datastax.driver.core.Session` is shown below + +.Registering a com.datastax.driver.core.Session object using Java based bean metadata +==== +[source,java] +---- +@Configuration +public class AppConfig { + + /* + * Use the standard Cassandra driver API to create a com.datastax.driver.core.Session instance. + */ + public @Bean Session session() { + Cluster cluster = Cluster.builder().addContactPoints("localhost").build(); + return cluster.connect("mykeyspace"); + } +} +---- +==== + +This approach allows you to use the standard `com.datastax.driver.core.Session` API that you may already be used to using. + +An alternative is to register an instance of `com.datastax.driver.core.Session` instance with the container using Spring's `CassandraCqlSessionFactoryBean` and `CassandraCqlClusterFactoryBean`. As compared to instantiating a `com.datastax.driver.core.Session` 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 for data access classes annotated. This hierarchy and use of `@Repository` is described in http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html[Spring's DAO support features]. + +An example of a Java based bean metadata that supports exception translation on `@Repository` annotated classes is shown below: + +.Registering a com.datastax.driver.core.Session object using Spring's CassandraCqlSessionFactoryBean and enabling Spring's exception translation support +==== +[source,java] +---- +@Configuration +public class AppConfig { + + /* + * Factory bean that creates the com.datastax.driver.core.Session instance + */ + public @Bean CassandraCqlClusterFactoryBean cluster() { + + CassandraCqlClusterFactoryBean cluster = new CassandraCqlClusterFactoryBean(); + cluster.setContactPoints("localhost"); + + return cluster; + } + + /* + * Factory bean that creates the com.datastax.driver.core.Session instance + */ + public @Bean CassandraCqlSessionFactoryBean session() { + + CassandraCqlSessionFactoryBean session = new CassandraCqlSessionFactoryBean(); + session.setCluster(cluster().getObject()); + session.setKeyspaceName("mykeyspace"); + + return session; + } +} +---- +==== + +Using `CassandraTemplate` with object mapping and repository support requires a `CassandraTemplate`, `CassandraMappingContext`, `CassandraConverter` and enabling repository support. + +.Registering components to configure object mapping and repository support +==== +[source,java] +---- +@Configuration +@EnableCassandraRepositories(basePackages = { "org.spring.cassandra.example.repo" }) +public class CassandraConfig { + + @Bean + public CassandraClusterFactoryBean cluster() { + + CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean(); + cluster.setContactPoints("localhost"); + + return cluster; + } + + @Bean + public CassandraMappingContext mappingContext() { + + BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext(); + mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster().getObject(), "mykeyspace")); + + return mappingContext; + } + + @Bean + public CassandraConverter converter() { + return new MappingCassandraConverter(mappingContext()); + } + + @Bean + public CassandraSessionFactoryBean session() throws Exception { + + CassandraSessionFactoryBean session = new CassandraSessionFactoryBean(); + session.setCluster(cluster().getObject()); + session.setKeyspaceName("mykeyspace"); + session.setConverter(converter()); + session.setSchemaAction(SchemaAction.NONE); + + return session; + } + + @Bean + public CassandraOperations cassandraTemplate() throws Exception { + return new CassandraTemplate(session().getObject()); + } +} +---- +==== + +Creating configuration classes registering Spring Data Cassandra components can get an exhausing challenge so Spring Data Cassandra comes with a prebuilt configuration support class. Classes extending from `AbstractCassandraConfiguration` will register beans for Spring Data Cassandra use. `AbstractCassandraConfiguration` lets you provide various configuration options such as initial entities, default query options, socket options, pooling options and much more. `AbstractCassandraConfiguration` will support you also with schema generation based on initial entities, if any provided. Extending from `AbstractCassandraConfiguration` requires you to at least provide the keyspace name by implementing the `getKeyspaceName` method. + +.Registering Spring Data Cassandra beans using AbstractCassandraConfiguration +==== +[source,java] +---- +@Configuration +public class AppConfig extends AbstractCassandraConfiguration { + + /* + * Provide a contact point to the configuration. + */ + public String getContactPoints() { + return "localhost"; + } + + /* + * Provide a keyspace name to the configuration. + */ + public getKeyspaceName() { + return "mykeyspace"; + } +} +---- +==== + +[[cassandra-connectors.xmlconfig]] +=== XML Configuration + +[[cassandra-connectors.xmlconfig.ext_properties]] +==== Externalize Connection Properties Create a properties file containing the information needed to connect to Cassandra. `contactpoints` and `keyspace` are required fields; `port` has been added for clarity. @@ -236,10 +411,67 @@ cassandra.keyspace=showcase We will use Spring to load these properties into the Spring context in the next two examples. -[[cassandra-connectors.xmlconfig]] -=== XML Configuration +==== Registering a Session instance using XML based metadata -The XML Configuration elements for a basic Cassandra configuration are shown below. These elements all use default bean names to keep the configuration code clean and readable. +While you can use Spring's traditional `` XML namespace to register an instance of `com.datastax.driver.core.Session` with the container, the XML can be quite verbose as it is general purpose. XML namespaces are a better alternative to configuring commonly used objects such as the Session instance. The `cql` and `cassandra` namespaces allow you to create a Session instance. + +To use the Mongo namespace elements you will need to reference the Mongo schema: + +.XML schema to configure Cassandra using the `cql` namespace +==== +[source,xml] +---- + + + + + + + + + + + + + +---- +==== + +.XML schema to configure Cassandra using the `cassandra` namespace +==== +[source,xml] +---- + + + + + + + + + + + + + +---- +==== + +NOTE: You may have noticed the slight difference between namespaces: `cql` and `cassandra`. Using the `cql` namespace is limited to low level CQL support while `cassandra` extends the `cql` namespace by object mapping and schema generation support. + +The XML Configuration elements for a more advanced Cassandra configuration are shown below. These elements all use default bean names to keep the configuration code clean and readable. While this example show 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 is including, but 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 mapping any existing driver configuration should be straight forward. @@ -247,12 +479,12 @@ While this example show how easy it is to configure Spring to connect to Cassand ---- + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:cassandra="http://www.springframework.org/schema/data/cassandra" + xmlns:context="http://www.springframework.org/schema/context" + http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> @@ -267,7 +499,9 @@ While this example show how easy it is to configure Spring to connect to Cassand - + + + @@ -283,90 +517,25 @@ While this example show how easy it is to configure Spring to connect to Cassand ---- -[[cassandra-connectors.javaconfig]] -=== Java Configuration - -The following class show a basic and minimal Cassandra configuration using the AnnotationConfigApplicationContext (aka JavaConfig). - -[source,java] ----- -package org.spring.cassandra.example.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.core.env.Environment; -import org.springframework.data.cassandra.config.CassandraClusterFactoryBean; -import org.springframework.data.cassandra.config.CassandraSessionFactoryBean; -import org.springframework.data.cassandra.config.SchemaAction; -import org.springframework.data.cassandra.convert.CassandraConverter; -import org.springframework.data.cassandra.convert.MappingCassandraConverter; -import org.springframework.data.cassandra.core.CassandraOperations; -import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; -import org.springframework.data.cassandra.mapping.CassandraMappingContext; -import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories; - -@Configuration -@PropertySource(value = { "classpath:cassandra.properties" }) -@EnableCassandraRepositories(basePackages = { "org.spring.cassandra.example.repo" }) -public class CassandraConfig { - - private static final Logger LOG = LoggerFactory.getLogger(CassandraConfig.class); - - @Autowired - private Environment env; - - @Bean - public CassandraClusterFactoryBean cluster() { - - CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean(); - cluster.setContactPoints(env.getProperty("cassandra.contactpoints")); - cluster.setPort(Integer.parseInt(env.getProperty("cassandra.port"))); - - return cluster; - } - - @Bean - public CassandraMappingContext mappingContext() { - return new BasicCassandraMappingContext(); - } - - @Bean - public CassandraConverter converter() { - return new MappingCassandraConverter(mappingContext()); - } - - @Bean - public CassandraSessionFactoryBean session() throws Exception { - - CassandraSessionFactoryBean session = new CassandraSessionFactoryBean(); - session.setCluster(cluster().getObject()); - session.setKeyspaceName(env.getProperty("cassandra.keyspace")); - session.setConverter(converter()); - session.setSchemaAction(SchemaAction.NONE); - - return session; - } - - @Bean - public CassandraOperations cassandraTemplate() throws Exception { - return new CassandraTemplate(session().getObject()); - } -} ----- - -[[cassandra.auditing]] -== General auditing configuration - -Auditing support is not available in the current version. - [[cassandra-template]] == Introduction to CassandraTemplate +The class `CassandraTemplate`, located in the package `org.springframework.data.cassandra`, is the central class of the Spring's Cassandra support providing 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 Cassandra rows. + +NOTE: Once configured, `CassandraTemplate` is thread-safe and can be reused across multiple instances. + +The mapping between Cassandra rows and domain classes is done by delegating to an implementation of the interface `CassandraConverter`. Spring provides a default implementation, `MappingCassandraConverter`, but you can also write your own converter. Please refer to the section on <> for more detailed information. + +The `CassandraTemplate` class implements the interface `CassandraOperations`. In as much as possible, the methods on `CassandraOperations` are named after methods available with Cassandra to make the API familiar to existing Cassandra developers who are used to Cassandra. For example, you will 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 in between the two APIs is that `CassandraOperations` can be passed domain objects instead of CQL and query objects. + +NOTE: The preferred way to reference the operations on `CassandraTemplate` instance is via its interface `CassandraOperations`. + +The default converter implementation used by `CassandraTemplate` is `MappingCassandraConverter`. While the `MappingCassandraConverter` can make use of additional metadata to specify the mapping of objects to rows it is also capable of converting 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 is 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. Refer to the section on <> for more information. + +Now let's look at a examples of how to work with the `CassandraTemplate` in the context of the Spring container. + [[cassandra-template.instantiating]] === Instantiating CassandraTemplate @@ -770,7 +939,7 @@ String cqlAll = "select * from person"; List results = cassandraOperations.select(cqlAll, Person.class); for (Person p : results) { - LOG.info(String.format("Found People with Name [%s] for id [%s]", p.getName(), p.getId())); + LOG.info(String.format("Found People with Name [%s] for id [%s]", p.getName(), p.getId())); } ---- @@ -950,7 +1119,7 @@ cassandraOperations.execute(dropper); [[cassandra.exception]] == 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. The Spring support for Cassandra extends this feature to the Cassandra Database by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface. +The Spring framework provides exception translation for a wide variety of database and mapping technologies. This has traditionally been for JDBC and JPA. The Spring support for Cassandra extends this feature to Cassandra by providing an implementation of the `org.springframework.dao.support.PersistenceExceptionTranslator` interface. The motivation behind mapping to Spring's http://docs.spring.io/spring/docs/current/spring-framework-reference/html/dao.html#dao-exceptions[consistent data access exception hierarchy] is that you are then able to write portable and descriptive exception handling code without resorting to coding against Cassandra Exceptions. All of Spring's data access exceptions are inherited from the root `DataAccessException` class so you can be sure that you will be able to catch all database related exception within a single try-catch block. diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index abc89f6fc..0b3fdbe85 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -1,14 +1,14 @@ -[[mapping-chapter]] +[[mapping.chapter]] = Mapping -Rich mapping support is provided by the `CassandraMappingConverter` . `CassandraMappingConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to CQL Tables. The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `CassandraMappingConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. +Rich mapping support is provided by the `MappingCassandraConverter` . `MappingCassandraConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to CQL Tables. The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MappingCassandraConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. -In this section we will describe the features of the CassandraMappingConverter. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. +In this section we will describe the features of the MappingCassandraConverter. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. [[mapping-conventions]] == Convention based Mapping -`CassandraMappingConverter` has a few conventions for mapping objects to CQL Tables when no additional mapping metadata is provided. The conventions are: +`MappingCassandraConverter` has a few conventions for mapping objects to CQL Tables when no additional mapping metadata is provided. The conventions are: * The short Java class name is mapped to the table name in the following manner. The class `com.bigbank.SavingsAccount` maps to `savingsaccount` table name. * The converter will use any Spring Converters registered with it to override the default mapping of object properties to document field/values. @@ -23,7 +23,7 @@ TODO == Data mapping and type conversion This section explain how types are mapped to a Cassandra representation and vice versa. Spring Data Cassandra supports several types that are provided by Apache Cassandra. -In addition to these types, Spring Data Cassandra provides a set of built-in converters to map additional types. You can provide your own converters to adjust type conversion, see <> for further details. +In addition to these types, Spring Data Cassandra provides a set of built-in converters to map additional types. You can provide your own converters to adjust type conversion, see <> for further details. [cols="3,2", options="header"] .Type @@ -76,6 +76,9 @@ In addition to these types, Spring Data Cassandra provides a set of built-in con | `java.util.UUID` | `timeuuid` +| `UDTValue`, mapped User-Defined types +| user type + | `java.util.Map` | `map` @@ -132,7 +135,7 @@ NOTE: `Enum` mapping using ordinal values requires at least Spring 4.3.0. Using [[mapping-configuration]] === Mapping Configuration -Unless explicitly configured, an instance of `CassandraMappingConverter` is created by default when creating a `CassandraTemplate` . You can create your own instance of the `MappingCassandraConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. +Unless explicitly configured, an instance of `MappingCassandraConverter` is created by default when creating a `CassandraTemplate` . You can create your own instance of the `MappingCassandraConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. .@Configuration class to configure Cassandra mapping support @@ -176,7 +179,7 @@ You can add additional converters to the converter by overriding the method `cus NOTE: `AbstractCassandraConfiguration` will create a `CassandraTemplate` instance and registered with the container under the name `cassandraTemplate`. -[[mapping-usage]] +[[mapping.usage]] == Metadata based Mapping To take full advantage of the object mapping functionality inside the Spring Data/Cassandra support, you should annotate your mapped objects with the `@Table` annotation. It allows the classpath scanner to find and pre-process your domain objects to extract the necessary metadata. If you don't use this annotation your entities will be not found or rejected, if used in repository definitions or during runtime. Only annotated entities will be used to perform schema actions. In the worst case a `SchemaAction.RECREATE_DROP_UNUSED` will drop your tables and you'll experience data loss. @@ -191,7 +194,7 @@ package com.mycompany.domain; public class Person { @Id - private ObjectId id; + private String id; @CassandraType(type = Name.VARINT) private Integer ssn; @@ -206,7 +209,7 @@ public class Person { IMPORTANT: The `@Id` annotation tells the mapper which property you want to use for the Cassandra primary key. Composite primary keys can require a slightly different data model. -[[mapping-usage-annotations]] +[[mapping.usage-annotations]] === Mapping annotation overview The `MappingCassandraConverter` can use metadata to drive the mapping of objects to rows. An overview of the annotations is provided below @@ -219,11 +222,14 @@ The `MappingCassandraConverter` can use metadata to drive the mapping of objects * `@Transient` - by default all private fields are mapped to the row, this annotation excludes the field where it is applied from being stored in the database * `@Column` - applied at the field level. Describes the name of the column as it will be represented in the Cassandra table thus allowing the name to be different than the fieldname of the class. * `@CassandraType` - applied at the field level to specify a Cassandra data type. Types are derived from the declaration by default. +* `@UserDefinedType` - applied at the type level to specify a Cassandra user defined data type. Types are derived from the declaration by default. The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic. Here is an example of a more complex mapping. +.Mapped `Person` class +==== [source,java] ---- @Table("my_person") @@ -256,6 +262,11 @@ public class Person { @Column(forceQuote = true) private String lastName; + private Address address; + + @CassandraType(type = Name.UDT, userTypeName = "myusertype") + private UDTValue usertype; + @Transient private Integer accountTotal; @@ -279,9 +290,37 @@ public class Person { } // other getters/setters ommitted +} ---- +==== -[[mapping-explicit-converters]] + +.Mapped User-Defined type `Address` +==== +[source,java] +---- +@UserDefinedType("address") +public class Address { + + private String city; + + @CassandraType(type = Name.VARCHAR) + private String city; + + private Set zipcodes; + + @CassandraType(type = Name.SET, typeArguments = Name.BIGINT) + private List timestamps; + +// other getters/setters ommitted +} +---- +==== + +NOTE: Working with User-Defined types requires a `UserTypeResolver` configured with the mapping context. See the <> how to configure a `UserTypeResolver`. + + +[[cassandra.mapping.explicit-converters]] === Overriding Mapping with explicit Converters When storing and querying your objects it is convenient to have a `CassandraConverter` instance handle the mapping of all Java types to Rows. However, sometimes you may want the `CassandraConverter` s do most of the work but allow you to selectively handle the conversion for a particular type or to optimize performance.