10

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?

Saket
  • 3,079
  • 3
  • 29
  • 48
  • I am forwarding this to the engineer who handled most of the Repository coding to get you an answer.. Sit tight. – David Webb Sep 19 '14 at 03:53

6 Answers6

5

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> {
// ....
}
Valchkou
  • 369
  • 5
  • 8
2

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.

Matthew Adams
  • 2,059
  • 2
  • 21
  • 31
0

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

bclarance
  • 159
  • 1
  • 2
  • 5
  • 1
    Good find. It's worth noting that your linked example has 100 keyspaces, whereas this one has 2. Two not-so-lightweight sessions might be acceptable where 100 will cause problems. – M. Justin Aug 20 '20 at 20:01
0

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)

Avi N
  • 1
  • 1
0

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

Kristoffer
  • 410
  • 2
  • 6
  • 16
  • One issue with my approach is that the Session object is created in a afterPropertiesSet on the CassandraCqlSessionFactoryBean. So it needs to be a Bean in Spring for that to happen – Kristoffer Aug 20 '15 at 09:05
0

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);
    }
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130