Anshuman Tripathy
Anshuman Tripathy

Reputation: 113

Don't want to connect to cassandra contact points during application start-up - Spring Data Cassandra

My Application does Write operations (Create/Update/Delete) on two different databases (Oracle and Cassandra).

I want the write operation to go through in Oracle even if the cassandra cluster goes down.

This is why all my write operations in Cassandra are Asynchronous and will execute in a different thread than that of the Oracle ones.

In my Config file I'm loading my CassandraClusterFactoryBean using the @Lazy Annotation. I've also surrounded it with a try-catch so it doesn't throw an exception there when it isn't able to connect to the cluster.

    @Bean
    @Lazy
    public CassandraClusterFactoryBean cluster() {
        CassandraClusterFactoryBean cluster = null;
        try{
         cluster = new CassandraClusterFactoryBean();
        cluster.setContactPoints(CASSANDRA_CONTACTPOINT);
        cluster.setPort(CASSANDRA_PORT);
        cluster.setUsername(CASSANDRA_USERNAME);
        cluster.setPassword(CASSANDRA_PASSWORD);
        }catch(Exception e){

            logger.info("Unable to contact Cassandra Cluster.");
        }
        return cluster;
    }

I've created an AsyncCompnent which performs all the write operations for Cassandra Asynchronously.

@Component
public class AsyncComponent {

    @Autowired
    private OneWalletCassandraRepo oneWalletCassandraRepo;

    @Autowired
    private OneWalletProfileByCustUserIdRepo oneWalletProfileByCustUserIdRepo;

    @Autowired
    private OneWalletProfileByBillingCanRepo oneWalletProfileByBillingCanRepo;

    public static Logger logger = LogManager.getLogger(AsyncComponent.class);


    @Async
    public Future<Tonewalletprofile> saveCassandraAsync(Tonewalletprofile cassandraProfile) throws InterruptedException {

        logger.info("Executing Method asynchronously : " + Thread.currentThread().getName());

        Tonewalletprofile savedCassandraProfile=oneWalletCassandraRepo.save(cassandraProfile);

        return new AsyncResult<>(savedCassandraProfile);
    }

    @Async
    public Future<Tonewalletprofile> findByPmtAccountRefIdAsync(int pmtAccountRefId) {

        logger.info("Executing Method asynchronously : " + Thread.currentThread().getName());

        Tonewalletprofile foundCassandraProfile=oneWalletCassandraRepo.findByPmtAccountRefId(pmtAccountRefId);

        return new AsyncResult<>(foundCassandraProfile);
    }

    @Async
    public Future<List<TonewalletprofileByCustUserId>> findByCustUserIdAsync(String custUserId) {

        logger.info("Executing Method asynchronously : " + Thread.currentThread().getName());

        List<TonewalletprofileByCustUserId> foundByCustUserId=oneWalletProfileByCustUserIdRepo.findByCustUserId(custUserId);

        return new AsyncResult<>(foundByCustUserId);
    }

    @Async
    public Future<List<TonewalletprofileByBillingCan>> findByBillingAcctNumAsync(String billingAcctNum) {

        logger.info("Executing Method asynchronously : " + Thread.currentThread().getName());

        List<TonewalletprofileByBillingCan> foundByBillingAcctNum=oneWalletProfileByBillingCanRepo.findByBillingAcctNum(billingAcctNum);

        return new AsyncResult<>(foundByBillingAcctNum);
    }

    @Async
    public void deleteCassandraAsync(Tonewalletprofile cassandraProfile) {

        logger.info("Executing Method asynchronously : " + Thread.currentThread().getName());

        oneWalletCassandraRepo.delete(cassandraProfile);
    }

}

I then auto-wire this component in my Service class using the @Lazy Annotation.

For Sanity's sake I've also autowired my Threadpoolexceutor and AsyncExceptionHanlder with @Lazy

    @Override
    @Lazy
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }

    @Override
    @Lazy
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

When I put the cassandra cluster down, and when I start-up my application it still tries to look for the contact points and it fails. (Click to Expand)

enter image description here

I'm not sure why this happens.

Basically what I want is even if the cluster goes down the Write operation on the Oracle DB should go ahead without a hitch.

Upvotes: 2

Views: 1051

Answers (2)

F&#225;bio
F&#225;bio

Reputation: 181

You can do this:

Disable the spring data cassandra autoconfigure:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;

@SpringBootApplication(exclude = {
    CassandraDataAutoConfiguration.class
})
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

Create your own CassandraTemplate with @Lazy and its dependencies:

import com.datastax.driver.core.Cluster;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.cassandra.config.CassandraTemplateFactoryBean;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;

/**
 * @author fabiojose
 */
@Configuration
public class Config  {

    @Bean
    @Lazy
    public Cluster cassandra() {

        return 
        Cluster.builder()
            .addContactPoint("localhost")
            .withoutMetrics()
            .withPort(9042)
            .build();
    }

    @Bean
    @Lazy
    public CassandraMappingContext mappingContext() {
        return new CassandraMappingContext();
    }

    @Bean
    @Lazy
    public CassandraConverter converter() {
        return new MappingCassandraConverter(mappingContext());
    }

    @Bean
    @Lazy
    public CassandraTemplateFactoryBean cassandraTemplate(Cluster cassandra,
        CassandraConverter converter) {

        CassandraTemplateFactoryBean factory = new CassandraTemplateFactoryBean();

        factory.setConverter(converter);
        factory.setSession(cassandra.newSession());

        return factory;
    }
}

Upvotes: 3

mp911de
mp911de

Reputation: 18119

Spring Data Cassandra components operate on a Session object that is initialized during startup. Creating an initialized Session requires remote interaction with Cassandra hosts and therefore you see failures if the hosts are not reachable.

You can spin up your own Session and lazily initialize it whenever a Session is requested through org.springframework.data.cassandra.SessionFactory. Both, CqlTemplate and CassandraTemplate, accept SessionFactory as constructor args.

Upvotes: 2

Related Questions