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;
}
}
Related
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)
can someone point to me how a field declared list<frozen<list<int>>> can be mapped back into java in spring-data-cassandra. I'm able to simply save data through List<List<Integer>>> but doesn't work when reading from the database, a codec not found exception pops.
Help is much appreciated.
Your Declaration is correct. But for nested collection read you need to create Custom RowMapper to convert row to DTO.
Example :
Let's we have the table ctest
CREATE TABLE ctest (
id int PRIMARY KEY,
data list<frozen<list<int>>>
);
And DTO
public class CTest {
#PrimaryKey
private int id;
private List<List<Integer>> data;
public CTest() {
}
private void setData(List<List<Integer>> data) {
this.data = data;
}
public List<List<Integer>> getData() {
return data;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
Now we want to query data from it.
List<CTest> results = cassandraOperations.query("SELECT * FROM ctest WHERE id = 1", new RowMapper<CTest>() {
private final TypeToken<List<Integer>> listOfInt = new TypeToken<List<Integer>>() {};
public CTest mapRow(Row row, int rowNum) throws DriverException {
CTest test = new CTest();
test.setId(row.getInt("id"));
test.setData(row.getList("data", listOfInt));
return test;
}
});
I am using Spring data Cassandra, to connect with Cassandra database, with configuration file extending AbstractCassandraConfiguration and overriding functions -
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE_DROP_UNUSED;
}
#Override
public String[] getEntityBasePackages() {
return new String[] {"com.example"};
}
My aim is to create tables automatically in Cassandra from the mentioned entities in com.example package with #Table annotation.
For example -
package com.example;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.Table;
#Table(value="goal")
public class Goal {
#PrimaryKey
private int id;
private String description;
public Goal(int id, String description) {
this.id = id;
this.description = description;
}
public Goal() {
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public void setId(int id) {
this.id = id;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "Goals [id=" + id + ", description=" + description + "]";
}
}
For this entity, with the given configuration, one table should get created during spring initialization, But it fails to do so.
No exception though, It just doesn't create anything in Cassandra.
Any help would be appreciated. Thanks.
I also faced similar requirement and following works for me:-
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints("127.0.0.1");
cluster.setPort(9042);
return cluster;
}
public String[] getEntityBasePackages() {
return new String[] { "com.user.entity" };
}
protected String getKeyspaceName() {
return "user_db";
}
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
Once your context is up, you can check the table in cassandra.
It is assumed your keyspace is already created in cassandra.
Please inform if you also want to create keyspace on the fly.
I'm trying to use Spring Caching annotations #Cacheable and #CacheEvict together with the GuavaCacheManager.
I've created a test case with these two tests:
cachesById - verifies that two invocations to a method annotatted with #Cacheable returns the same object
evict - verifies that two different instances are returned if a method annotated with #CacheEvict is called in-between those two invocations
Both work fine when i don't specify a key for #CacheEvict, however when I do i get the following exception:
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:210)
at com.google.common.cache.LocalCache$LocalManualCache.invalidate(LocalCache.java:4764)
at org.springframework.cache.guava.GuavaCache.evict(GuavaCache.java:135)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doEvict(AbstractCacheInvoker.java:95)
at org.springframework.cache.interceptor.CacheAspectSupport.performCacheEvict(CacheAspectSupport.java:409)
at org.springframework.cache.interceptor.CacheAspectSupport.processCacheEvicts(CacheAspectSupport.java:392)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:362)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:299)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.myorg.caching.CacheTest$Repo$$EnhancerBySpringCGLIB$$eed50f3e.update(<generated>)
at com.myorg.caching.CacheTest.evict(CacheTest.java:50)
This can be reproduced by executing the below test.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes = { Repo.class, CacheTest.SpringConfig.class },
loader = AnnotationConfigContextLoader.class)
public class CacheTest {
private static final String CACHE_NAME = "cacheName";
#Inject
private Repo repo;
#Test
public void cachesById() {
Entity aResult1 = repo.getEntity(1);
Entity aResult2 = repo.getEntity(1);
assertEquals(aResult1.getId(), aResult2.getId());
assertSame(aResult1, aResult2);
}
#Test
public void evict() {
Entity aResult1 = repo.getEntity(1);
repo.update(aResult1);
Entity aResult2 = repo.getEntity(1);
assertEquals(aResult1.getId(), aResult2.getId());
assertNotSame(aResult1, aResult2);
}
/** Mock repository/entity classes below. */
#Component
public static class Repo {
#Cacheable(value = CACHE_NAME, key = "#id")
public Entity getEntity(int id) {
return new Entity(id);
}
#CacheEvict(value = CACHE_NAME, key = "#id")
public void update(Entity e) {
}
}
public static class Entity {
private int id;
public Entity(int id) {
super();
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
/** Guava Cachemanager Spring configuration */
#Configuration
#EnableCaching
public static class SpringConfig {
#Bean
public CacheManager cacheManager() {
GuavaCacheManager manager = new GuavaCacheManager(CACHE_NAME);
manager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(
1, TimeUnit.MINUTES).recordStats());
return manager;
}
}
}
However the test passes if I change
#CacheEvict(value = CACHE_NAME, key = "#id")
public void update(Entity e) {
into:
#CacheEvict(value = CACHE_NAME)
public void update(Entity e) {
..but then I'm missing the point where I need to specify the cache key for Entity. Does anyone know what I'm missing?
Thanks!
You have to fix you component class from
#Component
public static class Repo {
#Cacheable(value = CACHE_NAME, key = "#id")
public Entity getEntity(int id) {
return new Entity(id);
}
#CacheEvict(value = CACHE_NAME, key = "#id")
public void update(Entity e) {
}
}
to
#Component
public static class Repo {
#Cacheable(value = CACHE_NAME, key = "#id")
public Entity getEntity(int id) {
return new Entity(id);
}
#CacheEvict(value = CACHE_NAME, key = "#e?.id")
public void update(Entity e) {
}
}
Why? In getEntity method you're caching an Entity object using int id, you have to pass the same int id into the #CacheEvict annotated method. You don't have to change method's signature - by using SPEL you can "get into" entity and use its id field.
Hope I helped.
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.