Saket
Saket

Reputation: 3129

Multiple keyspace support for spring-data-cassandra repositories?

Does Spring Data Cassandra support multiple keyspace repositories in the same application context? I am setting up the cassandra spring data configuration using the following JavaConfig class

@Configuration
@EnableCassandraRepositories(basePackages = "com.blah.repository")
public class CassandraConfig extends AbstractCassandraConfiguration {

@Override
public String getKeyspaceName() {
    return "keyspace1";
}

I tried creating a second configuration class after moving the repository classes to a different package.

@Configuration
@EnableCassandraRepositories(basePackages = "com.blah.secondrepository")
public class SecondCassandraConfig extends AbstractCassandraConfiguration {

@Override
public String getKeyspaceName() {
    return "keyspace2";
}

However in that case the first set if repositories fail as the configured column family for the entities is not found in the keyspace. I think it is probably looking for the column family in the second keyspace.

Does spring-data-cassandra support multiple keyspace repositories? The only place where I found a reference for multiple keyspaces was here. But it does not explain if this can be done with repositories?

Upvotes: 10

Views: 9342

Answers (6)

M. Justin
M. Justin

Reputation: 21258

In my case, I had a Spring Boot app where the majority of repositories were in one keyspace, and just two were in a second. I kept the default Spring Boot configuration for the first keyspace, and manually configured the second keyspace using the same configuration approach Spring Boot uses for its autoconfiguration.

@Repository
@NoRepositoryBean // This uses a different keyspace than the default, so not auto-creating
public interface SecondKeyspaceTableARepository 
        extends MapIdCassandraRepository<SecondKeyspaceTableA> {
}
@Repository
@NoRepositoryBean // This uses a different keyspace than the default, so not auto-creating
public interface SecondKeyspaceTableBRepository
        extends MapIdCassandraRepository<SecondKeyspaceTableB> {
}
@Configuration
public class SecondKeyspaceCassandraConfig {
    public static final String KEYSPACE_NAME = "second_keyspace";

    /**
     * @see org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration#cassandraSession(CassandraConverter)
     */
    @Bean(autowireCandidate = false)
    public CassandraSessionFactoryBean secondKeyspaceCassandraSession(
            Cluster cluster, Environment environment, CassandraConverter converter) {
        CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
        session.setCluster(cluster);
        session.setConverter(converter);
        session.setKeyspaceName(KEYSPACE_NAME);
        Binder binder = Binder.get(environment);
        binder.bind("spring.data.cassandra.schema-action", SchemaAction.class)
                .ifBound(session::setSchemaAction);
        return session;
    }

    /**
     * @see org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration#cassandraTemplate(com.datastax.driver.core.Session, CassandraConverter)
     */
    @Bean(autowireCandidate = false)
    public CassandraTemplate secondKeyspaceCassandraTemplate(
            Cluster cluster, Environment environment, CassandraConverter converter) {
        return new CassandraTemplate(secondKeyspaceCassandraSession(cluster, environment, converter)
                .getObject(), converter);
    }

    @Bean
    public SecondKeyspaceTableARepository cdwEventRepository(
            Cluster cluster, Environment environment, CassandraConverter converter) {
        return createRepository(CDWEventRepository.class, 
                secondKeyspaceCassandraTemplate(cluster, environment, converter));
    }

    @Bean
    public SecondKeyspaceTableBTypeRepository dailyCapacityRepository(
            Cluster cluster, Environment environment, CassandraConverter converter) {
        return createRepository(DailyCapacityRepository.class,
                secondKeyspaceCassandraTemplate(cluster, environment, converter));
    }

    private <T> T createRepository(Class<T> repositoryInterface, CassandraTemplate operations) {
        return new CassandraRepositoryFactory(operations).getRepository(repositoryInterface);
    }
}

Upvotes: 0

Valchkou
Valchkou

Reputation: 379

Working APP Sample: http://valchkou.com/spring-boot-cassandra.html#multikeyspace

The Idea you need override default beans: sessionfactory and template

Sample:

1) application.yml

 spring:
  data:
    cassandra:
      test1:
        keyspace-name: test1_keyspace
        contact-points: localhost
      test2:
        keyspace-name: test2_keyspace
        contact-points: localhost

2) base config class

public abstract class CassandraBaseConfig extends AbstractCassandraConfiguration{
    protected String contactPoints;
    protected String keyspaceName;

    public String getContactPoints() {
        return contactPoints;
    }
    public void setContactPoints(String contactPoints) {
        this.contactPoints = contactPoints;
    }

    public void setKeyspaceName(String keyspaceName) {
        this.keyspaceName = keyspaceName;
    }
    @Override
    protected String getKeyspaceName() {
        return keyspaceName;
    }
}

3) Config implementation for test1

package com.sample.repo.test1;

@Configuration
@ConfigurationProperties("spring.data.cassandra.test1")
@EnableCassandraRepositories(
        basePackages = "com.sample.repo.test1",
        cassandraTemplateRef = "test1Template"
)
public class Test1Config extends CassandraBaseConfig {

    @Override
    @Primary
    @Bean(name = "test1Template")
    public CassandraAdminOperations cassandraTemplate() throws Exception {
        return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
    }

    @Override
    @Bean(name = "test1Session")
    public CassandraSessionFactoryBean session() throws Exception {

        CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();

        session.setCluster(cluster().getObject());
        session.setConverter(cassandraConverter());
        session.setKeyspaceName(getKeyspaceName());
        session.setSchemaAction(getSchemaAction());
        session.setStartupScripts(getStartupScripts());
        session.setShutdownScripts(getShutdownScripts());

        return session;
    }
}

4) same for test2, just use different package package com.sample.repo.test2;

5) place repo for each keyspace in dedicated package i.e.

package com.sample.repo.test1;

@Repository
public interface RepositoryForTest1 extends CassandraRepository<MyEntity> {
// ....
}


package com.sample.repo.test2;

@Repository
public interface RepositoryForTest2 extends CassandraRepository<MyEntity> {
// ....
}

Upvotes: 5

Kristoffer
Kristoffer

Reputation: 440

Hmm. Can't comment on the answer by matthew-adams. But that will reuse the session object as AbstractCassandraConfiguration is annotated with @Bean on all the relevant getters.

In a similar setup I initially had it working with overwriting all the getters and specifically give them different bean names. But due to Spring still claiming to need beans with the names. I have now had to make a copy of AbstractCassandraConfiguration with no annotations that I can inherit.

Make sure to expose the CassandraTemplate so you can refer to it from @EnableCassandraRepositories if you use those.

I also have a separate implementation of AbstractClusterConfiguration to expose a common CassandraCqlClusterFactoryBean so the underlying connections are being reused.

Edit: I guess according to the email thread linked by bclarance one should really attempt to reuse the Session object. Seems the way Spring Data Cassandra isn't really set up for that though

Upvotes: 0

Avi N
Avi N

Reputation: 1

I tried this approach. However I ran into exceptions while trying to access the column family 2. Operations on column family 1 seems to be fine.

I am guessing because the underlying CassandraSessionFactoryBean bean is a singleton. And this causes unconfigured columnfamily columnfamily2

Here are some more logs to provide context

DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'entityManagerFactory' DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'session' DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'cluster'

org.springframework.cassandra.support.exception.CassandraInvalidQueryException: unconfigured columnfamily shardgroup; nested exception is com.datastax.driver.core.exceptions.InvalidQueryException: unconfigured columnfamily columnfamily2 at org.springframework.cassandra.support.CassandraExceptionTranslator.translateExceptionIfPossible(CassandraExceptionTranslator.java:116) at org.springframework.cassandra.config.CassandraCqlSessionFactoryBean.translateExceptionIfPossible(CassandraCqlSessionFactoryBean.java:74)

Upvotes: 0

bclarance
bclarance

Reputation: 159

It seems that it is recommended to use fully qualified keyspace names in queries managed by one session, as the session is not very lightweight.
Please see reference here

Upvotes: 0

Matthew Adams
Matthew Adams

Reputation: 2197

Try explicitly naming your CassandraTemplate beans for each keyspace and using those names in the @EnableCassandraRepositories annotation's cassandraTemplateRef attribute (see lines with /* CHANGED */ for changes).

In your first configuration:

@Configuration
@EnableCassandraRepositories(basePackages = "com.blah.repository",
    /* CHANGED */ cassandraTemplateRef = "template1")
public class CassandraConfig extends AbstractCassandraConfiguration {

@Override
public String getKeyspaceName() {
    return "keyspace1";
}

/* CHANGED */
@Override
@Bean(name = "template1")
public CassandraAdminOperations cassandraTemplate() throws Exception {
    return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}

...and in your second configuration:

@Configuration
@EnableCassandraRepositories(basePackages = "com.blah.secondrepository",
    /* CHANGED */ cassandraTemplateRef = "template2")
public class SecondCassandraConfig extends AbstractCassandraConfiguration {

@Override
public String getKeyspaceName() {
    return "keyspace2";
}

/* CHANGED */
@Override
@Bean(name = "template2")
public CassandraAdminOperations cassandraTemplate() throws Exception {
    return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}

I think that might do the trick. Please post back if it doesn't.

Upvotes: 2

Related Questions