How do I configure consistency level with Spring Boot and AWS Keyspaces? - spring-boot

I hope you can help me, I am doing a crud with spring boot and keyspaces (cassandra aws), spring has the default level of Consistency level in ONE and I have not been able to write the data since I get the following error:
"message": "Query; CQL [INSERT INTO tabledemoach (address,ciiu,creation_date,email,id,name,phone,state,user_activation_status) VALUES (?,?,?,?,?,?,?,?,?)]; Consistency level LOCAL_ONE is not supported for this operation. Supported consistency levels are: LOCAL_QUORUM; nested exception is com.datastax.oss.driver.api.core.servererrors.InvalidQueryException: Consistency level LOCAL_ONE is not supported for this operation. Supported consistency levels are: LOCAL_QUORUM",
I don't know how to configure the consistency level, I have tried several solutions on the internet and none have worked for me.
i have the following code
#Configuration
#EnableCassandraRepositories(basePackages = "com.demo")
public class AppConfig {
private final static String KEYSPACE = "demo";
#Primary
public #Bean CqlSession session() {
return CqlSession.builder().withKeyspace(KEYSPACE).build();
}
}
#Table(value = "tabledemoach")
#Data
public class User {
#PrimaryKey
private int id;
private String phone;
private String name;
private String address;
private String email;
private int ciiu;
private String state;
private String user_activation_status;
private LocalDate creation_date;
}
#Override
public void createUser(User user) {
List<User> userFind = (List<User>) userRepository.findAll();
var userList =userFind.stream().map(x -> x.getPhone());
var repeated = (userList.filter(x ->
x.contains(user.getPhone()))).collect(Collectors.toList());
if(repeated.size() <= 0){
userRepository.save(user);
}
}

There are several ways to configure the consistency level. You can define the default consistency in an application.conf file with:
datastax-java-driver {
basic {
request {
consistency = LOCAL_QUORUM
}
}
}
You can also configure it when using InsertOptions and UpdateOptions. For example:
InsertOptions insertOptions =
org.springframework.data.cassandra.core.InsertOptions.builder()
.consistencyLevel(ConsistencyLevel.LOCAL_QUORUM)
.build();
You can also use the #Consistency annotation, for example:
#Consistency(ConsistencyLevel.LOCAL_QUORUM)
List<Person> findByLastname(String lastname);
Finally with QueryOptions:
QueryOptions queryOptions =
newQueryOptions.setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);

Here is an example of Spring Boot and Amazon Keyspaces
https://github.com/aws-samples/amazon-keyspaces-examples/tree/main/java/datastax-v4/spring
package com.example.demo;
import com.datastax.oss.driver.api.core.CqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
#SpringBootApplication
#EnableCassandraRepositories(basePackages="com.example.model")
public class DemoApplication implements CommandLineRunner {
#Autowired
private CassandraOperations cassandraTemplate;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
CqlTemplate cqlTemplate = (CqlTemplate) cassandraTemplate.getCqlOperations();
CqlSession session = cqlTemplate.getSession();
int count = session.execute ("SELECT * FROM system.peers").all().size();
System.out.println("Number of hosts: "+ count);
}
}

Related

Sprint boot - Auto configure to call a REST service on startup

I have a requirement to create an auto-configuration for service call on spring-boot startup.
i.e., During spring-boot startup, the below service has to be called.
#PostMapping(path = "/addProduct", produces = "application/json", consumes = "application/json")
public #ResponseBody String addProduct(#RequestBody String productStr) {
..<My code>..
}
The add product requires an input like:
{
"product":"test",
"price":"10"
}
This will internally call a database service.
During startup, the json input provided in the console should be fed to this service.
I have no idea on how to achieve this. Verified a couple of Spring documentation. But those does'nt suit the requirement.
Kindly help in explaining a way or providing a right documentation to achieve this.
One way to do this is by implementing ApplicationRunner like this :
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
#Component
public class ApplicationInitializer implements ApplicationRunner {
private ProductController productController;
public ApplicationInitializer(ProductController productController) {
this.productController = productController;
}
#Override
public void run(ApplicationArguments args) throws Exception {
String productArg = args.getOptionValues("product").get(0); // Assume that you will have only one product argument
ObjectMapper mapper = new ObjectMapper();
Product product = mapper.readValue(productArg, Product.class);
String response = productController.add(product);
System.out.println(response);
}
}
The run method will be invoked at startup with arguments passed in the command line like this : java -jar yourApp.jar --product="{\"name\":\"test\", \"price\":\"15\"}".
And you need a class to map the json to an object like this :
public class Product {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
You can also call your Controller using the RestTemplate (or WebClient) if needed :
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
String productArg = args.getOptionValues("product").get(0); // Assume that you will have only one product argument
ObjectMapper mapper = new ObjectMapper();
Product product = mapper.readValue(productArg, Product.class);
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.postForObject("http://localhost:8080/products", product, String.class);
System.out.println(response);
}
}
Such requirement can be achieved by using an init() method annotated with #PostConstruct in a bean.
e.g.
#Component
public class Foo {
#PostConstruct
public void init() {
//Call your service
}
}

Cassandra Repository is always using keyspace from application.properties file not from the extended class of AbstractCassandraConfiguration

I have my data are on different keyspaces, i have more than one keyspace in a spring boot application, so i want my cassandra repositories to be configurable with different keyspaces each rather than having one keyspace in application level. In a spring project, i am looking something like a keyspace should be configurable at Repository level.
Cassandra Repository is always using keyspace from application.properties file, Not working if i extend AbstractCassandraConfiguration class and configure keyspace and port.
I have created a Entity, CassandraConfig and Repository and tried to query and update Entity using Respository. Refer my code below.
pom.xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
Entity code:-
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
#Table
public class Company {
#PrimaryKey
private String companyid;
private String companyName;
private String address;
public String getCompanyid() {
return companyid;
}
public void setCompanyid(String companyid) {
this.companyid = companyid;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Cassandra config code :-
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
#Configuration
#EnableCassandraRepositories(basePackageClasses=CompanyRepository.class)
public class CassandraConfig extends AbstractCassandraConfiguration {
public String getContactPoints() {
return "127.0.0.1";
}
#Override
protected String getKeyspaceName() {
return "CompanyDetails";
}
}
Repository code :-
import java.util.List;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.stereotype.Repository;
#Repository
public interface CompanyRepository extends CassandraRepository<Company, String> {
#Query(allowFiltering=true)
public Company findByCompanyName(String companyname);
public List<Company> findAll();
}
I tried to set keyspace for Repository using the class which extend AbstractCassandraConfiguration instead of application.properties. it didn't work
cassandraTemplate is working fine. i am getting issue with only Repository.
even if i create and configure CassandraClusterFactoryBean, CassandraMappingContext, CassandraConverter and CassandraSessionFactoryBean beans repository is throwing below exception
com.datastax.driver.core.exceptions.InvalidQueryException: No keyspace has been specified. USE a keyspace, or explicitly specify keyspace.tablename
at com.datastax.driver.core.Responses$Error.asException(Responses.java:147) ~[cassandra-driver-core-3.4.0.jar:na]
at com.datastax.driver.core.DefaultResultSetFuture.onSet(DefaultResultSetFuture.java:179) ~[cassandra-driver-core-3.4.0.jar:na]
if i add below entries to application.properties file then it is working fine.
spring.data.cassandra.port=9042
spring.data.cassandra.keyspace-name=CompanyDetails
spring.data.cassandra.contact-points=127.0.0.1
is it possible to set keyspace for Repository through class which extend AbstractCassandraConfiguration class instead of application.properties?
Feel free to point, if i missed or did something wrong.
It looks like you forgot to create the keysace. With getKeySpaceName() you tell cassandra which keyspace you want to use but if it is not there you'll get an error. To create a keyspace use the following method:
#Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
return Arrays.asList(
CreateKeyspaceSpecification.createKeyspace()
.name("your_keyspace_name")
.ifNotExists()
);
}

ListenerObject not found in imports for Ehcache 3?

I am trying to implement a listener for an Ehcache 3.3.1 project using the code below. Can anyone suggest a solution for the ListenerObject? I can't seem to find it anywhere,except on the docs page I got the code from
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheEventListenerConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.event.EventType;
public class CacheHandler{
private Logger LOG = Logger.getLogger(this.getClass().getName());
private String cacheName="basicCache";
public Cache cache;
public CacheHandler(){
if(cache==null)
cache=initCache();
}
private Cache initCache(){
CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED)
.unordered().asynchronous();
final CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache(cacheName,
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
.add(cacheEventListenerConfiguration)
).build(true);
final Cache<String, String> cache = manager.getCache("foo", String.class, String.class);
return cache;
}
public Cache getCache(){
if(cache==null)
cache=initCache();
return cache;
}
}
It is indeed not mentioned but since it is only one method it is normally easy to figure out.
Here is an example:
public class ListenerObject implements CacheEventListener<Object, Object> {
#Override
public void onEvent(CacheEvent<? extends Object, ? extends Object> event) {
System.out.println(event);
}
}
The real one used in the documentation is here.
Then, I've played a bit with your code to real production usable code.
public class CacheHandler implements AutoCloseable {
private static final String CACHE_NAME = "basicCache";
private final Cache<String, String> cache;
private final CacheManager cacheManager;
public CacheHandler() {
cacheManager = initCacheManager();
cache = cacheManager.getCache(CACHE_NAME, String.class, String.class);
}
private CacheManager initCacheManager(){
CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED)
.ordered().synchronous();
return CacheManagerBuilder.newCacheManagerBuilder()
.withCache(CACHE_NAME,
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
.add(cacheEventListenerConfiguration)
).build(true);
}
public Cache getCache(){
return cache;
}
#Override
public void close() {
cacheManager.close();
}
public static void main(String[] args) {
try(CacheHandler handler = new CacheHandler()) {
Cache<String, String> cache = handler.getCache();
cache.put("a", "b");
cache.putIfAbsent("a", "c");
}
}
}
Some comments:
I assumed you want singleton cache kept in a variable. So that's what I did. The lazy initCache wasn't useful because the withCache tells Ehcache to create the cache when creating the cache manager.
We will want to keep a reference to the CacheManager in order to close it at the end.
The getCache was retrieving "foo", not "basicCache"

Use pivotal cloud foundry redis and rabbitmq service using as vcap service in spring boot application

I am able to use REDIS and RABBITMQ services which is on pivotal.While binding services I am able to get the Credentials and using that credentials in my application.properties for spring boot project.
But this configuration that I am using is hard-coded in application.Properties
To Make this configuration dynamically I came to know that we can use vcap services provided by pivotal.
So want to use run-time credentials for redis and rabbimq.
My Code is below for reference.
application.propeties
rabbitmq.host=hostname
rabbitmq.virtual-host=vhostanme
rabbitmq.username=username
rabbitmq.password=password
rabbit.mainqueue=abhi
rabbit.errorqueue=abc
redis.host=redishostname
redis.port=port
redis.password=password
My Config class:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class Config {
static String rabbitMqHost;
static String rabbitMqVHost;
static String rabbitMqUsername;
static String rabbitMqPassword;
static String rabbitMqMainQueue;
static String rabbitMqErrorQueue;
static String redisHost;
static int redisPort;
static String redisPassword;
Config() {
}
public static String getRedisHost() {
return redisHost;
}
public static void setRedisHost(String redisHost) {
Config.redisHost = redisHost;
}
public static int getRedisPort() {
return redisPort;
}
public static void setRedisPort(int redisPort) {
Config.redisPort = redisPort;
}
public static String getRedisPassword() {
return redisPassword;
}
public static void setRedisPassword(String redisPassword) {
Config.redisPassword = redisPassword;
}
public static String getRabbitMqMainQueue() {
return rabbitMqMainQueue;
}
public static void setRabbitMqMainQueue(String rabbitMqMainQueue) {
Config.rabbitMqMainQueue = rabbitMqMainQueue;
}
public static String getRabbitMqErrorQueue() {
return rabbitMqErrorQueue;
}
public static void setRabbitMqErrorQueue(String rabbitMqErrorQueue) {
Config.rabbitMqErrorQueue = rabbitMqErrorQueue;
}
public static String getRabbitMqHost() {
return rabbitMqHost;
}
public static void setRabbitMqHost(String rabbitMqHost) {
Config.rabbitMqHost = rabbitMqHost;
}
public static String getRabbitMqVHost() {
return rabbitMqVHost;
}
public static void setRabbitMqVHost(String rabbitMqVHost) {
Config.rabbitMqVHost = rabbitMqVHost;
}
public static String getRabbitMqUsername() {
return rabbitMqUsername;
}
public static void setRabbitMqUsername(String rabbitMqUsername) {
Config.rabbitMqUsername = rabbitMqUsername;
}
public static String getRabbitMqPassword() {
return rabbitMqPassword;
}
public static void setRabbitMqPassword(String rabbitMqPassword) {
Config.rabbitMqPassword = rabbitMqPassword;
}
#Value("${rabbitmq.host}")
public void setRabbitMqHosts(String url) {
setRabbitMqHost(url);
}
#Value("${rabbitmq.virtual-host}")
public void setRabbitMqVHosts(String url) {
setRabbitMqVHost(url);
}
#Value("${rabbitmq.username}")
public void setRabbitUsernames(String url) {
setRabbitMqUsername(url);
}
#Value("${rabbitmq.password}")
public void setRabbitPasswords(String url) {
setRabbitMqPassword(url);
}
#Value("${rabbit.mainqueue}")
public void setRabbitMainQueues(String url) {
setRabbitMqMainQueue(url);
}
#Value("${rabbit.errorqueue}")
public void setRabbitErrorQueues(String url) {
setRabbitMqErrorQueue(url);
}
#Value("${redis.host}")
public void setRedisHosts(String url) {
setRedisHost(url);
}
#Value("${redis.port}")
public void setRedisPorts(int url) {
setRedisPort(url);
}
#Value("${redis.password}")
public void setRedisPasswords(String url) {
setRedisPassword(url);
}
}
My MessagesConsumer class in which I am using that Conguration to take message from rabbitmq jms queue and save to redis:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.es.consumer.config.Config;
import com.rabbitmq.jms.admin.RMQConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
#Component
public class MessagesConsumer {
#Autowired
JmsTemplate jmsTemplate;
final Logger logger = LoggerFactory.getLogger(MessagesConsumer.class);
Jedis jedis;
JedisShardInfo shardInfo;
#PostConstruct
public void init() {
shardInfo = new JedisShardInfo(Config.getRedisHost(), Config.getRedisPort());
shardInfo.setPassword(Config.getRedisPassword());
jedis = new Jedis(shardInfo);
jedis.connect();
jedis.select(2);
}
#Bean
ConnectionFactory connectionFactory() {
RMQConnectionFactory connectionFactory = new RMQConnectionFactory();
connectionFactory.setUsername(Config.getRabbitMqUsername());
connectionFactory.setPassword(Config.getRabbitMqPassword());
connectionFactory.setVirtualHost(Config.getRabbitMqVHost());
connectionFactory.setHost(Config.getRabbitMqHost());
return connectionFactory;
}
#SuppressWarnings("rawtypes")
#Scheduled(fixedRate = 1)
public void readQueueAndSaveData() {
// take message process it and save to redis as hmset
}}
Any Help would be appreciable.
There are two ways of doing this.
1) Remove all the properties from application.properties, and write a configuration bean that will create RedisTemplate and RabbitTemplate beans for you. The factory properties required for these are to be obtained from the VCAP_SERVICES. On CF, the VCAP_SERVICES env variable, will have the services information that are binded to the app. When u push the app, with redis, rabbit service binded into your space, then their properties are available in VCAP_SERVICES. So just do System.getEnv("VCAP_SERVICES") in your code and then parse the json to get the service details to create the templates.
2) Remove the properties from application.properties, and use spring cloud connector. Spring cloud has a subproject called Spring cloud connectors that provide utilities to connect to various cloud services.
http://cloud.spring.io/spring-cloud-connectors/spring-cloud-spring-service-connector.html#_rabbitmq
Just you need to define a class that extends AbstractCloudConfig as below
class CloudConfig extends AbstractCloudConfig {
#Bean
public RabbitConnectionFactory rabbitFactory() {
return connectionFactory().rabbitConnectionFactory("rabbit-servicename");
}
#Bean
public RedisConnectionFactory redisFactory() {
return connectionFactory().redisConnectionFactory("redis-servicename");
}
}
The second approach is prefered if you are using Spring, as this needs very less coding and can be switched, to different cloud providers with no much effort.

Entity can still be found after being deleted

I'm working with Spring Data Neo4j 4 and have the following user entity
#NodeEntity
public class User{
private Long id;
private String username;
//Getter, Setter
}
Using the Neo4j GraphRepository, i first create the user in one transaction and later delete him in a second transaction.
Working with the standalone Neo4j server on localhost:7474 i get no result when running "MATCH (n) return n" but when i run the findOne(Long id) method of the GraphRepository using the id of the User i just deleted, i get the user, i just deleted returned.
Is there some kind of behavior involved i don't understand?
Regards
Urr4
Edit:
My application class
#SpringBootApplication(scanBasePackages = {/.../})
#EnableNeo4jRepositories(basePackages = {/.../})
#EnableTransactionManagement
public class MyApplication extends Neo4jConfiguration {
public static void main(String[] args) {
SpringApplication.run(TSApplication.class, args);
}
#Override
#Bean
public Neo4jServer neo4jServer() {
return new RemoteServer(/.../);
}
#Override
#Bean
public SessionFactory getSessionFactory() {
return new SessionFactory("/.../);
}
}
After Michaels comment, i've googled a bit and added the following to my Controller:
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)
Afterwards it worked - Thank you all :)

Resources