(Quick Reference)

2 Getting Started - Reference Documentation

Authors: Paras Lakhani

Version: 5.0.8.RELEASE

2 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'
}

Note that if you get exceptions during startup or execution for newer versions of Grails 3.x (3.1 and above) you may need to enforce the dependencies:

dependencies {
    compile 'org.grails.plugins:cassandra: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'
    }
}

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
    }
}

2.1 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)

2.2 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.

2.3 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 = ["us-west":1, "eu-west":2] //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    
}

2.4 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 }

2.5 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()