Spring-data-cassandra's CassandraTemplate returns String, not a specified Object, when run queryForObject function. - spring-data-cassandra

I've been going through the Spring Data Cassandra documentation (http://docs.spring.io/spring-data/cassandra/docs/1.0.1.RELEASE/reference/html/cassandra.core.html)
Basically, with proper annotation, I hoped the CassandraTemplate maps a row to a POJO object, but it didn't work as I expected.
For the call,
cassandraOps.queryForObject(s, Person.class)
I received an error as following:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to Person
Anything that I'm missing? Following is the same copy and paste from the doc above.
Person Class looks like:
#Table
public class Person {
#PrimaryKey
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
#Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
and the application class looks like...:
public class CassandraApp {
private static final Logger LOG = LoggerFactory.getLogger(CassandraApp.class);
private static Cluster cluster;
private static Session session;
public static void main(String[] args) {
try {
cluster = Cluster.builder().addContactPoints(InetAddress.getLocalHost()).build();
session = cluster.connect("mykeyspace");
CassandraOperations cassandraOps = new CassandraTemplate(session);
cassandraOps.insert(new Person("1234567890", "David", 40));
Select s = QueryBuilder.select().from("person");
s.where(QueryBuilder.eq("id", "1234567890"));
LOG.info(cassandraOps.queryForObject(s, Person.class).getId());
cassandraOps.truncate("person");
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

CassandraTemplate's queryForObject(String,Class) is not meant for arbitrary object mapping. It is modeled after JdbcTemplate's queryForObject(String,Class) method. It's intended to take types that the Cassandra driver can convert directly.
To convert arbitrary application-defined classes, use queryForObject(String,RowMapper<T>) or one of its overloads. CqlTemplate doesn't know how to map arbitrary classes; you have to supply the RowMapper<T> implementation for your class T.

you can do it like this way:-
String myQuery = "select * from person where id=1234567890";
Person personObj = cassandraOperations.selectOne(myQuery, Person.class);
<<
For all
List<Person> personListObj = cassandraOperations.select(myQuery, Person.class); >>
this work for me using cassandraTemplete object perfectly... didn't try for cassandraOperation.
also you might need #Column(value = "your_columnName_in_DB") if your pojo class's variable name is different
like
#Column(value = "name")
private String userName;
#Column(value = "age")
private int userAge;
revert here if its work?
Also can you help me pass dynamic value to that myQuery string.. using object[] same like prepareStatment in SQL
thanks.

Related

How to provide CassandraEntityInformation<T, ID> for a SimpleReactiveCassandraRepository?

I'm doing some research bout spring-data-cassandra-reactive and it looks like I can't figure out or find the right documentation about this.
According to the documentation you can create an interface which is annotated with #Repository and then use annotations to create custom queries that will retrieve the data you want. The code would look something like this:
#Repository
public interface UserRepository
extends ReactiveCassandraRepository<UserEntity, UUID>
{
#Query("SELECT u FROM UserEntity u WHERE username = :user ALLOW FILTERING")
Mono<UserEntity> findUserEntityByUsername(#Param("user") String user);
/**
* This is just to illustrate/simulate some custom/advanced logic that cannot be
* done via #Query()
*/
default Mono<UserEntity> saveWithBase64EncodedPassword(UserEntity entity)
{
String encodedPassword = Base64.getEncoder().encodeToString(entity.getPassword().getBytes());
entity.updatePassword(encodedPassword);
return this.save(entity);
}
}
#Table(UserEntity.TABLE_NAME)
public class UserEntity
{
public final static String TABLE_NAME = "users";
#PrimaryKeyColumn(name = "uuid", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = CassandraType.Name.UUID)
#Column
private UUID id;
#Column
#CassandraType(type = CassandraType.Name.VARCHAR)
private String username;
#Column
#CassandraType(type = CassandraType.Name.VARCHAR)
private String password;
public UserEntity()
{
this.id = UUID.randomUUID();
}
public UserEntity(String username, String password)
{
this.id = UUID.randomUUID();
this.username = username;
this.password = password;
}
public UUID getId()
{
return id;
}
public void setId(UUID id)
{
this.id = id;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public void updatePassword(String newPassword)
{
this.password = newPassword;
}
}
Dependencies:
plugins {
id("org.springframework.boot") version "2.6.6"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
dependencies {
// Embedded Cassandra Server - used for testing.
implementation("com.github.nosan:embedded-cassandra-spring-boot-starter:4.1.0")
// Spring Data Cassandra Dependencies
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-cassandra-reactive")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude("org.junit.vintage:junit-vintage-engine")
exclude("com.vaadin.external.google:android-json")
}
testImplementation("io.projectreactor:reactor-test")
// /Spring Data Cassandra Dependencies
}
This, so far, works fine. However, I'm trying to figure out how to switch to implementing the interface in order to be able to use #Autowire (i.e. in this example code to autowire
org.springframework.security.crypto.password.PasswordEncoder)
Surfing through the code in the spring-data-cassandra jar I noticed there's a class named org.springframework.data.cassandra.repository.support.SimpleReactiveCassandraRepository which you can extend from. It already has most of the common things you would need implemented for you which is why it looks like the perfect candidate to extend from.
And here comes the problem - it requires CassandraEntityInformation<T, ID> metadata.
I cannot seem to find where this is taken from or how it's supposed to be auto-wired and I feel like I might be missing something or maybe a dependency.
Any ideas?
SimpleReactiveCassandraRepository is a great class as it gives you access to ReactiveCassandraOperations and as such to CqlSession. It is a great way to have fine grained operations. (LWT, Bacthes)
You are correct the different classes would be autowired. Here is a sample code:
#Repository
public class OwnerReactiveCassandraRepository extends SimpleReactiveCassandraRepository<OwnerEntitySpring, UUID> {
protected final CqlSession cqlSession;
protected final ReactiveCassandraOperations reactiveCassandraTemplate;
#SuppressWarnings("unchecked")
public OwnerReactiveCassandraRepository(CqlSession cqlSession, ReactiveCassandraOperations ops) {
super(new MappingCassandraEntityInformation<OwnerEntitySpring, UUID>(
(CassandraPersistentEntity<OwnerEntitySpring>) ops.getConverter().getMappingContext()
.getRequiredPersistentEntity(OwnerEntitySpring.class), ops.getConverter()), ops);
this.cqlSession = cqlSession;
this.reactiveCassandraTemplate = ops;
}
public Flux<OwnerEntitySpring> searchByOwnerName(String ownerLastName) {
return reactiveCassandraTemplate.getReactiveCqlOperations()
.query(SimpleStatement
.builder("SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_LASTNAME + "=?")
.addPositionalValues(ownerLastName)
.build(), (row, rownum) -> new OwnerEntitySpring(row));
}
TLDR; I implemented the Spring PetClinic with Spring data Reactive and you can have access to the full code here It is a step by step workshop with the code. You may want to look specially at this folder
In the project you will find reactive with the drivers only, reactive with CassandraRepositories and reactive with SimpleCassandraRepositories. (Check the TEST folder)

Spring data Cassandra, Allow filtering

I have the following table
CREATE TABLE magazines.magazine_name (
frequency smallint,
magazine_id varchar,
magazine_name varchar,
PRIMARY KEY (magazine_id,magazine_name)
);
Should I use allow filter annotation to have the following repository method get executed
#Query("SELECT * from magazine_name where magazine_id = ?0")
MagazineName findMagazineCQlQuery(String id);
because I get the folowing execption :
org.springframework.data.cassandra.CassandraInvalidQueryException:Query;
CQL[com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement#c78c2039];
Cannot execute this query as it might involve data filtering and thus may have unpredictable performance.
If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING;
nested exception is
com.datastax.oss.driver.api.core.servererrors.InvalidQueryException:
Cannot execute this query as it might involve data filtering
and thus may have unpredictable performance.
If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING
By the way, I know that I can use query methods or even findById method, but actually I am just experimenting with cql quires and try to learn about it.
--update
The domain object
#Table(value = "magazine_name")
#Data
#Builder
public class MagazineName {
#PrimaryKeyColumn(name = "magazine_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String magazineId;
#PrimaryKeyColumn(name = "magazine_name", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
private String name;
}
I defined the table exactly like yours and here is my repository. I can query without error.
1. My Repository
public interface IMagazineDao extends CrudRepository<Magazine, String> {
#Query("SELECT * from magazine_name where magazine_id = ?0")
Magazine findMagazineCQlQuery(String id);
}
2. Application
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private IMagazineDao magazineDao;
#Override
public void run(String... args) throws Exception {
this.magazineDao.save(new Magazine("magazine1", "name", (short) 1));
this.magazineDao.findMagazineCQlQuery("magazine1");
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. Magazine class
#Table(value = "magazine_name")
public class Magazine {
#PrimaryKeyColumn(name = "magazine_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String magazineId;
#PrimaryKeyColumn(name = "magazine_name", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
private String name;
#Column
private Short frequency;
public Magazine() {
}
public Magazine(String magazineId, String name, Short frequency) {
this.magazineId = magazineId;
this.name = name;
this.frequency = frequency;
}
public String getMagazineId() {
return magazineId;
}
public void setMagazineId(String magazineId) {
this.magazineId = magazineId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Short getFrequency() {
return frequency;
}
public void setFrequency(Short frequency) {
this.frequency = frequency;
}
}

spring-data-cassandra component/complex type denormalized mapping

I'm trying to use spring-data-cassandra (1.1.2.RELEASE) and I'm running into an issue with the lack of embeddable type mapping in JPA parlance.
I have an entity class like this:
#Table
public class Customer {
private UUID id;
private String name;
private Address address;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
and the embeddable Address class:
public class Address {
private String address;
private String city;
private String state;
private String zip;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
}
My cassandra table:
create table customer (
id uuid primary key,
name text,
address text,
city text,
state text,
zip text
);
I want the properties of Address to be mapped into the containing entity, I don't want a separate table for addresses. In JPA, I believe I'd use an #Embeddable annotation. Is there some similar construct in spring-data-cassandra?
Embeddable types are not yet supported by spring-data-cassandra. A feature request is available at DATACASS-167.
The only possible part of an entity to embed is the primary key. If your primary key consists of multiple fields, you can externalize that fields into a separate class and use it afterwards with the #PrimaryKey annotation.
Comment.java
#Table("comments")
public class Comment {
#PrimaryKey
private CommentKey pk;
private String text;
}
CommentKey.java
#PrimaryKeyClass
public class CommentKey implements Serializable {
private static final long serialVersionUID = -7871651389236401141L;
#PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String author;
#PrimaryKeyColumn(ordinal = 1)
private String company;
}
HTH, Mark

groovy not recognizing java getters/setters through field syntax

I have the following inheritance hierarchy defined in java.
public class BaseModel extends HashMap<String, Object> {
public String getString(String key) {
return (String)this.getOrDefault(key, "EMPTY");
}
}
public class Entity extends BaseModel {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now in a groovy script I try to do the following:
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
and "id" and "name" are not recognized. The funny thing is they are recognized if I do one of the following:
not inherit Entity from BaseModel
Rather than inherit BaseModel from HashMap, make HashMap a data member of BaseModel
inherit Entity directly from HashMap
At first I thought that groovy is not recognizing the "id" and "name" syntax because of extending HashMap, but #3 above proves that incorrect. I am stumped as to why this is not being recognized at this point. Can someone help me out? It should be easy enough to copy paste this and try it out yourself.
The problem seems to be the setters and getters inside the Entity Class, everything in groovy is public and it creates all the getters and setters methods.
I tested the next code in the groovy console and it worked.
public class BaseModel extends HashMap<String, Object> {
public String getString(String key) {
return (String)this.getOrDefault(key, "EMPTY");
}
}
public class Entity extends BaseModel {
private String id;
private String name;
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id
It prints 101 in the groovyConsole output screen.
When Entity is extending from BaseModel or directly a HashMap, Entity becomes a Map. So, when we say entity.id, Groovy is trying to find an entry in the map whose key is 'id'. As there is no such entry, it prints out null.
public class Entity extends HashMap<String, String> {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id //prints null
But when Entity is not extending from BaseModel anymore, entity.id will be interpreted just as a member of Entity.
public class Entity {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id //prints 101

Serialize HashMap like a Collection Property

Here I got two classes "Student" and "Course":
#XmlRootElement
class Student {
private String name;
private HashMap<String, Course> courses;
public Student() {}
public Student(String name, HashMap<String, Course> courses) {
super();
this.name = name;
this.courses = courses;
}
// -------------------------getters
#XmlAttribute(name="name")
public String getName() {
return name;
}
#XmlElement(name = "course")
public HashMap<String, Course> getCourses() {
return courses;
}
//---------------------------setters
public void setName(String name) {
this.name = name;
}
public void setCourses(HashMap<String, Course> courses) {
this.courses = courses;
}
}
#XmlRootElement
class Course {
private String id;
private String name;
public Course() {}
public Course(String id, String name) {
super();
this.id = id;
this.name = name;
}
#XmlID
#XmlAttribute(name = "id")
public String getId() {
return id;
}
#XmlAttribute(name = "name")
public String getName() {
return name;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
I want to serialize the Student class using JaxB annotations to something like the following:
<Student name="David">
<courses>
<Course id="1" name="Fundamentals of Programming"/>
<Course id="2" name="Advanced Programming">
</courses>
</Student>
Note that I want courses field of the Student to be HashMap and when unmarshalling the serialized Object use id as the key for each Course.
You can use an XmlAdapter for this use case. An XmlAdapter allows you to programmatically convert an object to another type for the purpose of marshalling/unmarshalling. For your use case you will convert the HashMap to an instance of an object that has a collection of Course.
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html

Resources