Spring Cassandra Custom Configuration

0

In this article, we will see how we can have our own cassandra configuration rather than letting spring rely on cassandra auto-configuration mechanism.

Cassandra Configuration

First thing to be done is to extend spring’s AbstractCassandraConfiguration. In order to externalize the configuration, we need to provide the CassandraProperties and override the getter properties so that the properties set in application.properties comes in use to build the cluster.

All cassandra properties defined in application.properties gets encapsulated into CassandraProperties.
The cassandra properties start with prefix “spring.data.cassandra”.

SimpleCassandraConfig :

/*
 * Copyright (c) 2019 Navis LLC. All Rights Reserved.
 */
package com.javarticles.cassandra.demo.config;

import com.javarticles.cassandra.demo.domain.Movie;
import org.apache.tomcat.util.buf.StringUtils;
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification.createKeyspace;

/**
 * Application configuration
 */
@Configuration
@EnableConfigurationProperties(CassandraProperties.class)
@ConfigurationProperties("spring.data.cassandra")
public class SimpleCassandraConfig extends AbstractCassandraConfiguration {
    private final CassandraProperties properties;

    private int replicationFactor;

    public SimpleCassandraConfig(CassandraProperties properties) {
        this.properties = properties;
    }

    @Override
    protected String getKeyspaceName() {
        return properties.getKeyspaceName();
    }

    public boolean getMetricsEnabled() {
        return false;
    }

    public String[] getEntityBasePackages() {
        List entityPackages = new ArrayList<>();
        entityPackages.add(Movie.class.getPackage().getName());
        return entityPackages.toArray(new String[0]);
    }

    public SchemaAction getSchemaAction() {
        return SchemaAction.valueOf(properties.getSchemaAction().toUpperCase());
    }

    public String getContactPoints() {
        return StringUtils.join(properties.getContactPoints());
    }

    @Override
    public int getPort() {
        return properties.getPort();
    }

    public Integer getReplicationFactor() {
        return replicationFactor;
    }

    public void setReplicationFactor(int replicationFactor) {
        this.replicationFactor = replicationFactor;
    }

    @Override
    protected List getKeyspaceCreations() {
        return Collections.singletonList(createKeyspace(getKeyspaceName()).ifNotExists().withSimpleReplication(getReplicationFactor()));
    }

    @Override
    protected String getClusterName() {
        return properties.getClusterName();
    }
}

Here we override getKeyspaceCreations() to create the keyspace if doesn’t exist.
Now we will test the cassandra integration with a simple domain class called Movie.

Movie:

package com.javarticles.cassandra.demo.domain;

import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Table;

import java.util.Map;

@Table("movie")
public class Movie {
    @PrimaryKey
    private String id;

    @Column
    private String name;

    @Column
    private String genre;

    public Movie(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }
}

Here is the movie repository.

MovieRepository:

package com.javarticles.cassandra.demo.domain;

import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MovieRepository extends CassandraRepository {
}

We specify the cassandra properties.

application.properties:

server.port=8081
spring.data.cassandra.keyspace-name=movies
spring.data.cassandra.schema-action=recreate
spring.data.cassandra.contactpoints=localhost
spring.data.cassandra.port=9042
logging.level.org.springframework.data.cassandra.core.cql: DEBUG

Now we will put this into test.

CassandraExamplesApplicationTests:

package com.javarticles.cassandra.demo;

import com.javarticles.cassandra.demo.domain.Movie;
import com.javarticles.cassandra.demo.domain.MovieRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static junit.framework.TestCase.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
class CassandraExamplesApplicationTests {
	@Autowired
	private MovieRepository movieRepository;

	@Test
	void movieCreation() {
		Movie movie = new Movie("01", "Batman");
		movie.setGenre("Superhero");
		movieRepository.save(movie);
		movie = movieRepository.findById("01").orElse(null);
		assertNotNull(movie);
		assertEquals("Batman", movie.getName());
	}
}

Output:

2020-08-05 19:12:14.610  INFO 17536 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 21ms. Found 1 Cassandra repository interfaces.
2020-08-05 19:12:15.371  INFO 17536 --- [           main] com.datastax.driver.core                 : DataStax Java driver 3.7.2 for Apache Cassandra
....
2020-08-05 19:12:16.675  WARN 17536 --- [           main] com.datastax.driver.core.Cluster         : You listed localhost/0:0:0:0:0:0:0:1:9042 in your contact points, but it wasn't found in the control host's system.peers at startup
2020-08-05 19:12:16.848  INFO 17536 --- [           main] c.d.d.c.p.DCAwareRoundRobinPolicy        : Using data-center name 'datacenter1' for DCAwareRoundRobinPolicy (if this is incorrect, please provide the correct datacenter name with DCAwareRoundRobinPolicy constructor)
2020-08-05 19:12:16.850  INFO 17536 --- [           main] com.datastax.driver.core.Cluster         : New Cassandra host localhost/127.0.0.1:9042 added
2020-08-05 19:12:16.898 DEBUG 17536 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate  : Executing CQL Statement [CREATE KEYSPACE IF NOT EXISTS movies WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 0 } AND durable_writes = true;]
2020-08-05 19:12:17.165 DEBUG 17536 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate  : Executing CQL Statement [DROP TABLE movie;]
2020-08-05 19:12:18.300 DEBUG 17536 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate  : Executing CQL Statement [CREATE TABLE movie (genre text, id text, name text, PRIMARY KEY (id));]
...
2020-08-05 19:12:20.418 DEBUG 17536 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate  : Executing CQL Statement [SELECT * FROM movie WHERE id='01';]
Share.

Comments are closed.