Installing Cassandra
The first step is to install a local version of Cassandra and I continue to find the ccm tool to be outstanding in being able to bring up and tear down a small cluster. Here is the command that I am running to bring up a 3 node Apache Cassandra 3.9 based cluster.
ccm create test -v 3.9 -n 3 -s --vnodes
Create Schemas
Connect to a node in the cluster:
ccm node1 cqlsh CREATE KEYSPACE IF NOT EXISTS sample WITH replication = {'class':'SimpleStrategy', 'replication_factor':1};
Next, I need to create the tables to hold the data. A general Cassandra recommendation is to model the tables based on query patterns - given this let me first define a table to hold the basic "hotel" information:
CREATE TABLE IF NOT EXISTS sample.hotels ( id UUID, name varchar, address varchar, state varchar, zip varchar, primary key((id), name) );
Assuming I have to support two query patterns - a retrieval of hotels based on say the first letter, and a retrieval of hotels by state, I have a "hotels_by_letter" denormalized table to support retrieval by "first letter":
CREATE TABLE IF NOT EXISTS sample.hotels_by_letter ( first_letter varchar, hotel_name varchar, hotel_id UUID, address varchar, state varchar, zip varchar, primary key((first_letter), hotel_name, hotel_id) );
And just for variety a "hotels_by_state" materialized view to support retrieval by state that the hotels are in:
CREATE MATERIALIZED VIEW sample.hotels_by_state AS SELECT id, name, address, state, zip FROM hotels WHERE state IS NOT NULL AND id IS NOT NULL AND name IS NOT NULL PRIMARY KEY ((state), name, id) WITH CLUSTERING ORDER BY (name DESC)
Coding Repositories
On the Java side, since I am persisting and querying a simple domain type called "Hotel", it looks like this:
@Table("hotels") public class Hotel implements Serializable { @PrimaryKey private UUID id; private String name; private String address; private String state; private String zip; ... }
Now, to be able to perform a basic CRUD operation on this entity all that is required is a repository interface as shown in the following code:
import cass.domain.Hotel; import org.springframework.data.repository.CrudRepository; import java.util.UUID; public interface HotelRepository extends CrudRepository<Hotel, UUID>, HotelRepositoryCustom {}
This repository is additionally inheriting from a HotelRepositoryCustom interface which is to provide the custom finders to support retrieval by first name and state.
Now to persist a Hotel entity all I have to do is to call the repository method:
hotelRepository.save(hotel);
The data in the materialized view is automatically synchronized and maintained by Cassandra, however the data in the "hotels_by_letter" table has to be managed through code, so I have another repository defined to maintain data in this table:
public interface HotelByLetterRepository extends CrudRepository<HotelByLetter, HotelByLetterKey>, HotelByLetterRepositoryCustom {}
The custom interface and its implementation is to facilitate searching this table on queries based on first letter of the hotel name and is implemented this way through the a custom repository implementation feature of Spring data Cassandra.
import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class HotelRepositoryImpl implements HotelRepositoryCustom { private final CassandraTemplate cassandraTemplate; @Autowired public HotelRepositoryImpl(CassandraTemplate cassandraTemplate) { this.cassandraTemplate = cassandraTemplate; } @Override public List<Hotel> findByState(String state) { Select select = QueryBuilder.select().from("hotels_by_state"); select.where(QueryBuilder.eq("state", state)); return this.cassandraTemplate.select(select, Hotel.class); } } @Repository public class HotelByLetterRepositoryImpl implements HotelByLetterRepositoryCustom { private final CassandraTemplate cassandraTemplate; public HotelByLetterRepositoryImpl(CassandraTemplate cassandraTemplate) { this.cassandraTemplate = cassandraTemplate; } @Override public List<HotelByLetter> findByFirstLetter(String letter) { Select select = QueryBuilder.select().from("hotels_by_letter"); select.where(QueryBuilder.eq("first_letter", letter)); return this.cassandraTemplate.select(select, HotelByLetter.class); } }
Given these repository classes, custom repositories that provide query support, the rest of the code is to wire everything together which Spring Boot's Cassandra Auto Configuration facilitates.
That is essentially all there is to it, the Spring Data Cassandra makes it ridiculously simple to interact with Cassandra 3+.
A complete working project is I believe a far better way to get familiar with this excellent library and I have such a sample available in my github repo here - https://github.com/bijukunjummen/sample-boot-with-cassandra
No comments:
Post a Comment