Introduction

Apache Cassandra is a massively scalable open source NoSQL database.

Cassandra can be used for managing large amounts of data across multiple data centers and the cloud.

The goal of this grails-data-mapping subproject is to provide a as-complete-as-possible GORM implemenatition that maps domain classes and instances to Cassandra 2.0 tables.

This project aims to provide an object-mapping layer on top of Cassandra to ease common activities such as:

  • Marshalling from Cassandra to Groovy/Java types and back again

  • Support for GORM dynamic finders, criteria and named queries

  • Session-managed transactions

  • Validating domain instances backed by the Cassandra datastore

  • Cassandra schema creation

Compatibility with GORM for Hibernate

This implementation tries to be as compatible as possible with GORM for Hibernate. In general you can refer to the GORM documentation and the "Domain Classes" section of the reference guide (see the right nav) for usage information.

The following key features are supported by GORM for Cassandra:

  • Simple persistence methods

  • Dynamic finders

  • Criteria queries

  • Named queries

  • Inheritance

  • Embedded types

  • Query by example

However, some features are not supported:

  • HQL queries

  • Associations (these should be supported in the future)

  • Any direct interaction with the Hibernate API

  • Custom Hibernate user types

  • Queries using OR, LIKE, NOT EQUAL, IS NULL, negation, property comparison, offset in paging, most projections (mainly because Cassandra itself doesn’t support these)

  • Table-per-hierarchy mapping

There may be other limitations not mentioned here so in general it shouldn’t be expected that an application based on GORM for Hibernate will "just work" without some tweaking involved. Having said that, the large majority of common GORM functionality is supported.

Getting Started

To get started with GORM for Cassandra you need configure it as a dependency in build.gradle:

dependencies {
    compile 'org.grails.plugins:cassandra:VERSION'
}

The next step is to setup a running Cassandra server. Refer to the Apache Cassandra Getting Started Guide for an explanation on how to setup and startup a Cassandra instance. Once installed, starting Cassandra is typically a matter of executing the following command:

CASSANDRA_HOME/bin/cassandra -f

With the above command executed in a terminal window you should see output like the following appear:

INFO 00:11:16,935 Starting listening for CQL clients on localhost/127.0.0.1:9042...
INFO 00:11:17,013 Using TFramedTransport with a max frame size of 15728640 bytes.
INFO 00:11:17,014 Binding thrift service to localhost/127.0.0.1:9160
INFO 00:11:17,042 Using synchronous/threadpool thrift server on localhost : 9160
INFO 00:11:17,042 Listening for thrift clients...

As you can see the server listens for CQL clients on port 9042, but don’t worry the Cassandra plugin for Grails will automatically configure itself to look for Cassandra on that port by default.

If you want to configure how Grails connects to Cassandra then you can do so using the following settings in grails-app/conf/application.yml:

grails:
    cassandra:
        contactPoints: localhost
        port: 9042
        keyspace:
            name: foo
    }
}
Spring Boot 1.4.x and above includes Cassandra Auto Configuration and by default in Grails 3.2.x above you will need to configure Cassandra using the appropriate Spring Boot configuration unless you exclude the auto configuration in your Application class. For example:
import org.springframework.boot.autoconfigure.cassandra.*
import org.springframework.boot.autoconfigure.data.cassandra.*
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.boot.autoconfigure.EnableAutoConfiguration

@EnableAutoConfiguration(exclude=[CassandraAutoConfiguration, CassandraDataAutoConfiguration])
class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }
}

Using GORM for Cassandra Standalone

If you plan to use Cassandra as your primary datastore then you need to remove the Hibernate plugin from your build.gradle file.

compile 'org.grails.plugins:hibernate'

With this done all domain classes in grails-app/domain will be persisted via Cassandra and not Hibernate. You can create a domain class by running the regular create-domain-class command:

grails create-domain-class Person

The Person domain class will automatically be a persistent entity that can be stored in Cassandra. Example:

class Person {
    String firstName
    String lastName
}
def person = new Person(firstName: "Fred", lastName: "Flintstone")

person.save()
...
def person2 = Person.get(uuid)

Combining Cassandra and Hibernate

If you have both the Hibernate and Cassandra plugins installed then by default all classes in the grails-app/domain directory will be persisted by Hibernate and not Cassandra. If you want to persist a particular domain class with Cassandra then you must use the mapWith property in the domain class:

static mapWith = "cassandra"

By default the Hibernate plugin transparently adds an implicit id property of type Long to your domain class. The Cassandra plugin adds an id property of type UUID. If you install both plugins, the id property will be of type Long. So if you have a domain class with an auto-generated id (the default behaviour) and you want to save it to both datastores, you should define a UUID id property as a Long won’t really work for Cassandra. You also need to set the id’s generator attribute so that the Hibernate plugin can auto-generate a UUID.

Advanced Configuration

Cassandra Database Connection Configuration

As mentioned above, the GORM for Cassandra plugin will configure itself with default settings, but if you wish to customize those defaults you can do so in the grails-app/conf/application.groovy file:

grails {
    cassandra {
        contactPoints = "localhost"
        port = 9042
        schemaAction = "recreate-drop-unused"
        keyspace {
            name = "foo"
            action = "create"
        }
    }
}

The keyspace name property configures the default keyspace to use. If it’s not specified the keyspace used will default to the name of your application.

In production scenarios you will typically use more than one Cassandra node:

grails {
    cassandra {
        contactPoints = "192.168.0.1, 192.168.0.2" //comma-separated list of hostnames or IP addresses of nodes to connect to
    }
}

Keyspace creation

If you want the plugin to automatically create the application’s keyspace you can specify a value for the keyspace action property:

grails {
    cassandra {
        keyspace {
            name = "foo"
            action = "create"
        }
    }
}

Valid options for action are:

  • create - Create the keyspace if it doesn’t exist

  • create-drop - Drop the keyspace and create it if doesn’t exist

Schema creation

The plugin can automatically create the Cassandra tables and indexes required for your domain model. You have some control over when and how it does this through the dbCreate property, which can take these values:

  • none - The default. Do not create any schema objects.

  • create - Create a table and indexes for each domain class on startup. Fail if a table already exists.

  • recreate - Create a table and indexes for each domain class on startup, dropping the table first if it exists.

  • recreate-drop-unused - Drop all tables in the keyspace, then create a table and indexes for each domain class on startup.

  • update - TO IMPLEMENT

Example:

grails {
    cassandra {
        dbCreate = "recreate-drop-unused"
    }
}

You can remove the dbCreate property completely, which is recommended once your schema is relatively stable and definitely when your application and database are deployed in production.

Configuration Options Guide

Below is a complete example showing all configuration options, including keyspace options:

grails {
    cassandra {
            contactPoints = "localhost" //comma-separated list of hostnames or IP addresses of nodes to connect to
        port = 9042 //the port to connect to
        dbCreate = "recreate" //the strategy to create cassandra tables and indexes for domain classes, default: "none"
        stateless = false // whether to use stateless sessions by default

        keyspace {
            name = "foo" //the name of the keyspace to use, default: the name of the application
            action = "create" //whether to create a keyspace, default: no keyspace created

            //keyspace properties to set only if the plugin is to create the keyspace
            durableWrites = false //default: false
            replicationStrategy = "SIMPLE_STRATEGY" OR "NETWORK_TOPOLOGY_STRATEGY" //default: "SIMPLE_STRATEGY"
            replicationFactor = 1 //default: 1
            dataCenter = [1,[us-west] 2][eu-west] //if replicationStrategy is "NetworkTopologyStrategy",
                                                    //a map of data centre names and replication factors
        }
    }
}

Global Mapping Configuration

Using the grails.cassandra.default.mapping setting in application.groovy you can configure global mapping options across your domain classes. The following configuration will disable optimistic locking globally:

grails.cassandra.default.mapping = {
    version false
}

Using GORM in Spring Boot

To use GORM for Hibernate in Spring Boot add the necessary dependencies to your Boot application:

compile("org.grails:gorm-cassandra-spring-boot:VERSION")

Note that if you get exceptions during startup or execution for newer versions of Spring Boot (1.3 and above) you may need to enforce the dependencies:

dependencies {
    compile("org.grails:gorm-cassandra-spring-boot:VERSION")
    compile("com.datastax.cassandra:cassandra-driver-core:2.0.4")
    compile "org.springframework.data:spring-data-commons:1.8.4.RELEASE"
    compile("org.springframework.data:spring-data-cassandra:1.0.4.RELEASE") {
        exclude group:'org.apache.cassandra',module:'cassandra-all'
    }
}

Ensure your Boot Application class is annotated with ComponentScan, example:

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.*

@Configuration
@EnableAutoConfiguration
@ComponentScan
class Application {
    static void main(String[] args) {
        SpringApplication.run Application, args
    }
}
Using ComponentScan without a value results in Boot scanning for classes in the same package or any package nested within the Application class package. If your GORM entities are in a different package specify the package name as the value of the ComponentScan annotation.

Finally create your GORM entities and ensure they are annotated with grails.persistence.Entity:

import grails.persistence.*

@Entity
class Person {
    String firstName
    String lastName
}

GORM for Cassandra outside Grails

If you wish to use GORM for Cassandra outside of a Grails application you should declare the necessary dependencies, for example in Gradle:

compile "org.grails:grails-datastore-gorm-cassandra:VERSION"

Then annotate your entities with the grails.gorm.annotation.Entity annotation:

@Entity
class Person {
    String name
}

Then you need to place the bootstrap logic somewhere in your application which uses CassandraDatastoreSpringInitializer:

def initializer = new CassandraDatastoreSpringInitializer(Person)
def applicationContext = initializer.configure()

println Person.count()

For configuration you can either pass a map or an instance of the org.springframework.core.env.PropertyResolver interface:

def initializer = new CassandraDatastoreSpringInitializer(['grails.cassandra.port':7891], Person)
def applicationContext = initializer.configure()

println Person.count()

If you are using Spring with an existing ApplicationContext you can instead call configureForBeanDefinitionRegistry prior to refreshing the context. You can pass the Spring Environment object to the constructor for configuration:

ApplicationContext myApplicationContext = ...
def initializer = new CassandraDatastoreSpringInitializer(myApplicationContext.getEnvironment(), Person)
initializer.configureForBeanDefinitionRegistry(myApplicationContext)

println Person.count()

Mapping to Cassandra Tables

Basic Mapping

The way the GORM for Cassandra plugin works is to map each domain class to a Cassandra table. For example given a domain class such as:

class Person {
    String firstName
    String lastName
}

This will map onto a Cassandra table called "person" and generate the following table if schema creation is on:

CREATE TABLE person (id uuid, firstname text, lastname text, version bigint, PRIMARY KEY (id));
The plugin transparently adds an implicit id property of type UUID which is auto-generated when an entity is saved.

Data types

In general a property’s Java type maps onto a CQL3 data type as listed here Some Java types can map onto more than one CQL3 data type, the default mappings are shown in bold: * java.util.UUID - CQL uuid or timeuuid * java.lang.String - CQL text or ascii or varchar * long / java.lang.Long - CQL bigint or counter

Java byte and short map onto CQL int.

To map onto a different CQL type specify the type attribute in the mapping. Example:

class Person {
    String firstName
    String lastName
    UUID timeuuid
    String ascii
    String varchar
    long counter

    static mapping = {
        timeuuid type:"timeuuid"
        ascii type:'ascii'
        varchar type:'varchar'
        counter type:'counter'
    }
}

Embedded Collections and Maps

You can map embedded lists, sets and maps of standard CQL data types simply by defining the appropriate collection type:

class Person {
    String firstName
    String lastName
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

...

new Person(friends:['Fred', 'Bob'], pets:[chuck:"Dog", eddie:'Parrot']).save()

There are certain limitations on collections and only the standard CQL data types can be stored inside embedded collections and maps.

When persisting a domain class containing embedded collections or maps using the save method, the entire collection or map is saved or updated to Cassandra. This may not be appropriate if you only want to persist the non-collection properties, in which case you can use the updateSimpleTypes instance method. Example:

def person = Person.get(uuid)
person.age = 31
person.updateSimpleTypes(flush:true)

If you want to add or remove an item from a collection or map and only have that change updated to Cassandra you can use the various dynamic methods listed in the "Domain Classes" section of the right nav. Example:

person.prependToScores(5)
Person.appendToFriends(person.id, 'Barney')
Person.deleteFromPets(person.id, 'eddie', [flush:true])

The last flush:true argument causes the session to flush the pending collection updates to the datastore.

Customized Database Mapping

You may wish to customize how a domain class maps onto a Cassandra table. This is possible using the mapping block as follows:

class Person {
    ..
    static mapping = {
        table "the_person"
    }
}

In this example we see that the Person entity has been mapped to a table called "the_person".

You can also control how an individual property maps onto a table column (the default is to use the property name itself):

class Person {
    ..
    static mapping = {
        firstName column:"first_name"
    }
}

Identity Generation

By default in Cassandra GORM domain classes are supplied with a UUID-based identifier. So for example the following entity:

class Person {}

Has a property called id of type java.util.UUID. In this case GORM for Cassandra will generate a UUID identifier using java.util.UUID.randomUUID(). For a timeuuid it will generate one using the Java driver

You can name the id property something else, in which case you have to set the name attribute in the mapping:

class Person {
    UUID primary

    static mapping = {
            id name:"primary"
    }
}

Assigned Identifiers

If you want to manually assign an identifier, the following mapping should be used:

class Person {
    String firstName
    String lastName

    static mapping = {
        id name:"lastName", generator:"assigned"
    }
}

Note that it is important to specify generator:"assigned" so GORM can work out whether you are trying to achieve an insert or an update. Example:

def p = new Person(lastName:"Wilson")
// to insert
p.save()
// to update
p.save()

An existing manually-assigned entity will only be updated with the save method if it is in the current persistence session. Otherwise GORM will try to insert the entity again, which will result in an upsert to Cassandra (with no version checking if versioning is on). So if the entity is not in the session and you want to explicitly direct an update to Cassandra then use the update method instead of the save method. Example:

def p = new Person(lastName:"Wilson")
// to insert
p.save()
session.clear() or p.discard()
// to update
p.update()

Compount Primary Keys

In Cassandra, a compound primary key consists of more than one column and treats only one column as the partition key. The other columns are treated as clustering columns. To define a compound primary key on a domain class, each property that is part of the key has to be defined in the mapping block with a primaryKey map. Example:

class Person  {

    String lastName
    String firstName
    Integer age = 0
    String location

    static mapping = {
        id name:"lastName", primaryKey:[ordinal:0, type:"partitioned"], generator:"assigned"
        firstName index:true, primaryKey:[ordinal:1, type: "clustered"]
        age primaryKey:[ordinal:2, type: "clustered"]
        version false
    }
}

The above mapping will generate the following Cassandra table if schema creation is on:

CREATE TABLE person (lastname text, firstname text, age int, location text, PRIMARY KEY (lastname, firstname, age))

Composite Partition Key

A composite partition key consists of multiple columns and treats more than one column as the partition key. The other columns are treated as clustering columns. To define a composite partition key on a domain class, each property that is a part of the key has to have its primaryKey type attribute set to "partitioned". Example:

class Person  {

    String lastName
    String firstName
    Integer age = 0
    String location

    static mapping = {
        id name:"lastName", primaryKey:[ordinal:0, type:"partitioned"], generator:"assigned"
        firstName index:true, primaryKey:[ordinal:1, type: "partitioned"]
        age index:true, primaryKey:[ordinal:0, type: "clustered"]
        version false
    }
}
...
CREATE TABLE person (lastname text, firstname text, age int, location text, PRIMARY KEY ((lastname, firstname), age))

The mapping block

The first column of the partition key is always mapped using id, and then the name of the actual property.

You should then add the primaryKey map to all columns of the compound/composite primary key. The two attributes are: * ordinal - specifies the order of the column in the compound/composite primary key. * type - "partitioned" or "clustered". For a compound primary key only one property is type "partitioned" and the rest are type "clustered". For a composite partition key more than one property is type "partitioned".

Persistence and Querying for Compound/Composite Primary Key domain classes

Where you need to pass in an id to a persistence or query method, use a map containing the components of the compound/composite primary key instead.

Example:

def person = Person.get([firstName:"Bob", lastName: "Wilson", age: 25])
Person.updateProperties([firstName:"Bob", lastName: "Wilson", age: 25], [location: "London"], [flush:true])

Query Indexes

Basics

Cassandra doesn’t require that you specify indices to query. Cassandra supports creating an index on most columns, including a clustering column of a compound primary key or on the partition (primary) key itself. Indexing can impact performance greatly. Before creating an index, be aware of when and when not to create an index.

With that in mind it is important to specify the properties you plan to query using the mapping block:

class Person {
    String name
    static mapping = {
        name index:true
    }
}

The above mapping will generate the following Cassandra index if schema creation is on:

CREATE INDEX IF NOT EXISTS  ON person (name)

Table Properties

You can configure clustering order, caching, compaction, and a number of other operations that Cassandra performs on a new table.

Clustering order

An explanation is provided in the Cassandra docs. Clustering order can only be used on a clustered primary key, to use it set its order attribute. Example:

class Person {
    ..
    static mapping = {
        id name:"lastName", primaryKey:[ordinal:0, type:"partitioned"], generator:"assigned"
        firstName index:true, primaryKey:[ordinal:1, type: "clustered"], order: "desc" //or order: "asc"
    }
}

Setting a table property

Available properties and their descriptions are defined in the Cassandra docs. If you want to set a table property, define a static tableProperties block. Below is an example of the properties you can set with Cassandra GORM:

class Person {
    ..
    static mapping = {
        id name:"lastName", primaryKey:[ordinal:0, type:"partitioned"]
        firstName primaryKey:[ordinal:1, type: "clustered"], order:"desc"
    }

    static tableProperties = {
        comment "table comment"
        "COMPACT STORAGE" true //OR "compact_storage" true
        replicate_on_write false
        caching "all"
        bloom_filter_fp_chance 0.2
        read_repair_chance 0.1
        dclocal_read_repair_chance 0.2
        gc_grace_seconds 900000
        compaction class: "SizeTieredCompactionStrategy", bucket_high: 2.5, bucket_low: 0.6, max_threshold: 40, min_threshold: 5, min_sstable_size: 60
        compression sstable_compression: "LZ4Compressor", chunk_length_kb: 128,        crc_check_chance: 0.85
    }
}

The above mapping will generate the following Cassandra table if schema creation is on:

CREATE TABLE person (lastname text, firstname text, version bigint, PRIMARY KEY (lastname, firstname))
WITH CLUSTERING ORDER BY (firstname DESC) AND comment = 'table comment' AND COMPACT STORAGE AND replicate_on_write = 'false'
AND caching = 'all' AND bloom_filter_fp_chance = 0.2 AND read_repair_chance = 0.1 AND dclocal_read_repair_chance = 0.2
AND gc_grace_seconds = 900000
AND compaction = { 'class' : 'SizeTieredCompactionStrategy', 'bucket_high' : 2.5, 'bucket_low' : 0.6, 'max_threshold' : 40, 'min_threshold' : 5,
                   'min_sstable_size' : 60 }
AND compression = { 'sstable_compression' : 'LZ4Compressor', 'chunk_length_kb' : 128, 'crc_check_chance' : 0.85 };

Table property options

  • comment : String

  • "COMPACT STORAGE" OR "compact_storage" : boolean

  • replicate_on_write : boolean

  • caching : "all", "keys_only", "rows_only", "none"

  • bloom_filter_fp_chance : double

  • read_repair_chance : double

  • dclocal_read_repair_chance : double

  • gc_grace_seconds : long

  • compaction : [class, tombstone_threshold, tombstone_compaction_interval, min_sstable_size, min_threshold, max_threshold, bucket_low, bucket_high, sstable_size_in_mb]

  • compression : [sstable_compression, chunk_length_kb, crc_check_chance]

Stateless Mode

GORM for Cassandra supports both stateless and stateful modes for mapping domain classes to Cassandra. In general stateful mapping is superior for write heavy applications and stateless mode better for read heavy applications (particularily when large amounts of data is involved).

Stateful mode

Domain classes are by default stateful, which means when they are read from Cassandra their state is stored in the user session (which is typically bound to the request in Grails). This has several advantages for write heavy applications:

  • GORM can automatically detect whether a call to save() is a an update or an insert and act appropriately

  • GORM can store the current version and therefore implement optimistic locking

  • Repeated reads of the same entity can be retrieved from the cache, thus optimizing reads as well

An example of when a stateful domain class is better is batching (TO BE IMPLEMENTED)

Stateless Domain classes

However, stateful domain classes can cause problems for read-heavy applications. Take for example the following code:

def books = Book.list() // read 100,000 books
for(b in books) {
    println b.title
}

The above example will read 100,000 books and print the title of each. In stateful mode this will almost certainly run out of memory as each Cassandra row is stored in user memory as is each book. Rewriting the code as follows will solve the problem:

Book.withStatelessSession {
    def books = Book.list() // read 100,000 books
    for(b in books) {
        println b.title
    }
}

Alternatively you can map the domain class as stateless, in which case its state will never be stored in the session:

class Book {
    ...
    static mapping = {
        stateless true
    }
}

Disadvantages of Stateless Mode

There are several disadvantages to using stateless domain classes as the default. One disadvantage is that if you are using assigned identifiers GORM cannot detect whether you want to do an insert or an update so you have to be explicit about which one you want:

def b = new Book(id:"The Book")
b.insert()
b.revenue = 100
b.update()

In the above case we use the explicit insert or 'update method to tell Cassandra GORM what to do.

Querying and Persistence Options

The GORM for Cassandra plugin supports passing various options to Cassandra when querying or persisting.

Querying Options

Limiting results

Using the max parameter, you can specify that the query return a limited number of rows. Example:

def people = Person.list(max:2)

Note that Cassandra does not support the offset parameter, so you cannot page results this way.

Fetch Size

Setting the fetch size, or number of rows returned simultaneously by a select query, is typically needed when queries return extremely large numbers of rows. To do this you can use the fetchSize argument:

def people = Person.list([fetchSize: 200])
people = Person.findAllByFirstName("Barney", [fetchSize: 200])

Setting the fetch size to small values is discouraged as it will yield very poor performance.

In some cases you may want or have to disable paging entirely, for example when using order by and IN, in which case set the fetchSize to Integer.MAX_VALUE. Example:

People.createCriteria().list {
    'in' "lastName", ["Flintstone", "Rubble"]
    order "name"
    fetchSize Integer.MAX_VALUE
}

Allow filtering

When you attempt a potentially expensive query Cassandra may throw an exception mentioning ALLOW FILTERING. To run the query, you can use the allowFiltering argument which is passed onto Cassandra. Imposing a limit using the max parameter is recommended to reduce memory used.

Example:

def people = Person.findAllByFirstNameAndAgeLessThanEquals('Barney', 35, [allowFiltering:true, max:5])
def person = Person.findOrSaveWhere(firstName: 'Rubble', age: 35, [allowFiltering:true, flush:true])
def criteria = Person.createCriteria()
people = criteria.list (allowFiltering:true, max:5) {
            and {
                eq('firstName', 'Fred')
                eq('age', 40)
            }
         }
people = criteria.list {
            projections {
                eq('firstName', 'Bob')
                between("age", 40, 43)
            }
            allowFiltering true
            max 5
         }

Cassandra Low-level API

A lower level API is provided by the plugin that is based on the Spring Data Cassandra project.

Spring Data Cassandra provides a CassandraTemplate with methods to execute statements using the regular Cassandra Java Driver

To get hold of the cassandraTemplate instance inside a controller or service simply define a cassandraTemplate property. An example can be seen below:

def cassandraTemplate

def myAction = {
        def people = []
        people << new Person(firstName: "Fred", lastName: "Flintstone")
        people << new Person(firstName: "Barney", lastName: "Rubble")
        cassandraTemplate.insert(people)
}

You can also create a CQL or Java Driver statement and execute it using cassandraTemplate

Select personSelect = QueryBuilder.select().all().from("person")
List personList = cassandraTemplate.select(personSelect, Person.class)

String personCQL = "SELECT firstName FROM person"
List personList = cassandraTemplate.select(personCQL, Person.class)

Reference

Beans

cassandraTemplate

CassandraTemplate
Purpose

A Spring bean that provides access to the lower level CassandraTemplate API

Examples
def cassandraTemplate

def myAction = {
    def person = cassandraTemplate.selectOneById(Person.class, uuid)
    person.age = 30
    cassandraTemplate.update(person)
}
Description

See the CassandraTemplate docs, the Cassandra Java Driver docs, and the Cassandra Java Driver API for API usage info.

Domain Classes

appendTo

appendTo*
Purpose

Adds an element to an existing instance’s embedded set, list or map, where the actual collection or map added to is indicated by the property used as the suffix to the method.

Examples
class Person {
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.appendToScores(5)
Person.appendToFriends(person.id, 'Barney', [flush:true])
Description

The appendTo* method is a dynamic method that adds an element to an embedded set, list or map. In the case of a list it adds the element to the end of the list. The method exists as a static and instance method. The static method simply adds the element to the datastore, the instance method adds the element to the in-memory instance and the datastore.

Argument: * id - the id or primary key map of the instance. Only required if using the static method. * element - the element to append * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

deleteAtFrom

deleteAtFrom*
Purpose

Removes an element at a specified index from an existing instance’s embedded list, where the actual list removed from is indicated by the property used as the suffix to the method.

Examples
class Person {
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.deleteAtFromScores(1)
Person.deleteAtFromScores([id: person.id], 2, [flush:true])
Description

The deleteAtFrom* method is a dynamic method that removes an element at the specified position in an embedded list. The method exists as a static and instance method. The static method simply removes the element in the datastore, the instance method removes the element in the in-memory instance and the datastore.

Argument: * id - the id or primary key map of the instance. Only required if using the static method. * index - the index of the element to remove * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

deleteFrom

deleteFrom*
Purpose

Removes an element from an existing instance’s embedded set, list or map, where the actual collection or map removed from is indicated by the property used as the suffix to the method.

Examples
class Person {
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.deleteFromScores(5)
Person.deleteFromFriends(person.id, 'Barney', [flush:true])
Person.deleteFromPets(person.id, 'eddie', [flush:true])
Description

The deleteFrom* method is a dynamic method that removes an element from an embedded set, list or map. In the case of a map it removes the entry keyed by the specified element. The method exists as a static and instance method. The static method simply removes the element from the datastore, the instance method removes the element from the in-memory instance and the datastore.

Argument: * id - the id or primary key map of the instance. Only required if using the static method. * element - the element to remove, or the key of the entry to remove from a map * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

prependTo

prependTo*
Purpose

Prepends an element to an existing instance’s embedded list, where the actual list prepended to is indicated by the property used as the suffix to the method.

Examples
class Person {
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.prependToScores(5)
Person.prependToScores(5(person.id, 6, [flush:true])
Description

The prependTo* method is a dynamic method that adds an element to the start of an embedded list. The method exists as a static and instance method. The static method simply adds the element to the datastore, the instance method adds the element to the in-memory instance and the datastore.

Argument: * id - the id or primary key map of the instance. Only required if using the static method. * element - the element to prepend * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

replaceAtIn

replaceAtIn*
Purpose

Replaces an element at a specified index in an existing instance’s embedded list, where the actual list used is indicated by the property used as the suffix to the method.

Examples
class Person {
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.replaceAtInScores(1, 9)
Person.replaceAtInScores([id: person.id], 2, 10, [flush:true])
Description

The replaceAtIn* method is a dynamic method that replaces an element at the specified position in an embedded list. The method exists as a static and instance method. The static method simply replaces the element in the datastore, the instance method replaces the element in the in-memory instance and the datastore.

Argument: * id - the id or primary key map of the instance. Only required if using the static method. * index - the index of the element to replace * element - element to be stored at the specified position * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

updateProperties

updateProperties
Purpose

Static method that updates multiple properties on an existing instance in the datastore.

Examples
Book.updateProperties(uuid, [revenue : 300, sold : 10])
Person.updateProperties([firstName:"Fred", lastName: "Flintstone"], [age : 30, email: "[email protected]"], [flush:true])
Description

Allows an update to multiple properties of an existing instance without having to get it from the datastore first. Will not validate the properties nor update the version column for optimistic locking.

Argument: * id - the id or primary key map of the instance to update * properties - a map of property name/value pairs to update * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

updateProperty

updateProperty
Purpose

Static method that updates a single property on an existing instance in the datastore.

Examples
Book.updateProperty(uuid, "revenue", 300)
Person.updateProperty([firstName:"Fred", lastName: "Flintstone"], "friends", ["Barney"], [flush:true])
Description

Allows an update to a property of an existing instance without having to get it from the datastore first. Will not validate the property nor update the version column for optimistic locking.

Argument: * id - the id or primary key map of the instance to update * propertyName - the name of the property to update * value - the new value of the property * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately

updateSingleTypes

updateProperty
Purpose

Updates non collection, non map types only on an existing instance in the datastore. Useful when you don’t want to update the entire embedded collection or map on an instance when updating.

Examples
class Person {
    ..
    int age
    List<Integer> scores
    Set<String> friends
    Map<String, String> pets
}

def person = Person.get(uuid)
person.age = 31
person.updateSimpleTypes(flush:true)
Description

The regular save() method would update all the elements in an embedded collection or map even if not required. This method allows an update of only the single types on an existing instance.

Argument: * flush (optional) - When set to true flushes the persistence context, updating the datastore immediately