I am playing with spring-data and cassandra, seeing how to code a service which has to store data from a REST service within a cassandra ring, no problem about it, but when i try to retrieve data from cassandra, i want to use a composite primary key and i am facing a ClassCastException.
I already saw in a previous post in SO a similar problem, but the solution is not working for me. I already saw another but this is related to spring-data-cassandra, no spring-data-rest.
This is @Table anotated class:
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.Table;
import java.time.LocalDateTime;
import java.util.Map;
import javax.validation.constraints.NotNull;
@Table(value=AdminUserCassandraTable.tableName)
public class AdminUserCassandraTable {
@org.springframework.data.annotation.Transient
public static final String tableName = "AdminUser";
@PrimaryKey
private AdminUserPK id;
private String tenantId;
private String orgId;
private boolean superAdmin;
@NotNull
private String password;
private String emailAddress;
private Map<String, String> metadata;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime lastModified;
public AdminUserCassandraTable() {
this.id = null;
}
@Override public String toString() {
return "AdminUserCassandraTable{" +
"id=" + id +
", tenantId='" + tenantId + '\'' +
", orgId='" + orgId + '\'' +
", superAdmin=" + superAdmin +
", password='" + password + '\'' +
", emailAddress='" + emailAddress + '\'' +
", metadata=" + metadata +
", createdAt=" + createdAt +
", lastModified=" + lastModified +
'}';
}
...
}
This is AdminUserPK:
import java.io.Serializable;
import java.util.UUID;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.PrimaryKeyClass;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import com.datastax.driver.core.DataType;
@PrimaryKeyClass
public class AdminUserPK implements Serializable{
@PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
@CassandraType(type = DataType.Name.UUID)
private UUID id;
@PrimaryKeyColumn(name = "username", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
@CassandraType(type = DataType.Name.TEXT)
private String username;
// https://stackoverflow.com/questions/34363344/querying-tables-with-composite-primary-keys-using-spring-data-cassandra
public AdminUserPK() {
this.id = null;
this.username = null;
}
public AdminUserPK(UUID id, String username) {
this.id = id;
this.username = username;
}
...
getters, setters, equals and hashCode methods.
}
This is AdminUserRepository:
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
import unicon.matthews.admin.AdminUser;
import unicon.matthews.admin.service.AdminUserCassandraTable;
import unicon.matthews.admin.service.AdminUserPK;
public interface AdminUserRepository extends TypedIdCassandraRepository<AdminUserCassandraTable, AdminUserPK> {
**The problem must be here, with this @Query.**
@Query("select * from AdminUser where id = ?0 and username = ?1")
Optional<AdminUser> findByIdAndUsername(final UUID id,final String userName);
}
And finally the test case that provokes the exception:
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version;
import org.springframework.test.context.junit4.SpringRunner;
import example.springdata.cassandra.util.CassandraKeyspace;
import unicon.matthews.admin.AdminUser;
import unicon.matthews.admin.service.AdminUserCassandraTable;
import unicon.matthews.admin.service.AdminUserPK;
import unicon.matthews.admin.service.repository.AdminUserRepository;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = unicon.matthews.admin.service.repository.CassandraConfiguration.class)
public class AdminUserRepositoryTests {
final String userName = UUID.randomUUID().toString();
final UUID _id = UUID.randomUUID();
@ClassRule public final static CassandraKeyspace CASSANDRA_KEYSPACE = CassandraKeyspace.onLocalhost().atLeast(Version.parse("3.0"));
@Autowired AdminUserRepository repository;
@Before
public void setUp() throws Exception {
repository.deleteAll();
AdminUserCassandraTable aPojo = new AdminUserCassandraTable();
AdminUserPK adminUserPK = new AdminUserPK();
adminUserPK.setId(_id);
adminUserPK.setUsername(userName);
aPojo.setId(adminUserPK);
aPojo.setEmailAddress("[email protected]");
aPojo.setOrgId("orgId");
aPojo.setPassword("pass");
aPojo.setTenantId("tenantId");
aPojo.setCreatedAt(LocalDateTime.now());
aPojo.setLastModified(null);
// OJITO! metadatos a null...
aPojo.setMetadata(null);
aPojo.setSuperAdmin(Boolean.FALSE);
repository.save(aPojo);
assertTrue(repository.count() > 0);
}
@Test
public void testFindByIdAndUsername() {
Optional<AdminUser> loadedUserName = repository.findByIdAndUsername(_id,userName);
assertNotNull(loadedUserName);
assertEquals("something went wrong!",userName,loadedUserName.get().getUsername());
}
}
The exception is:
java.lang.ClassCastException: Cannot cast unicon.matthews.admin.service.AdminUserPK to java.lang.String
at java.lang.invoke.MethodHandleImpl.newClassCastException(MethodHandleImpl.java:361)
at java.lang.invoke.MethodHandleImpl.castReference(MethodHandleImpl.java:356)
at unicon.matthews.admin.AdminUser_Accessor_cmi9qy.setProperty(Unknown Source)
at org.springframework.data.cassandra.repository.query.DtoInstantiatingConverter$2.doWithPersistentProperty(DtoInstantiatingConverter.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:346)
at org.springframework.data.cassandra.repository.query.DtoInstantiatingConverter.convert(DtoInstantiatingConverter.java:92)
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:221)
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter$1.convert(ResultProcessor.java:209)
at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:221)
at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:162)
at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$ResultProcessingConverter.convert(CassandraQueryExecution.java:178)
at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$ResultProcessingExecution.execute(CassandraQueryExecution.java:143)
at org.springframework.data.cassandra.repository.query.AbstractCassandraQuery.execute(AbstractCassandraQuery.java:113)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:483)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy96.findByIdAndUsername(Unknown Source)
at unicon.mattheews.admin.service.repository.test.AdminUserRepositoryTests.testFindByIdAndUsername(AdminUserRepositoryTests.java:120)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
The problem must be related with AdminUserRepository interface, with @Query, but how can i pass the UUID and the String that compounds the primary key coming from parameters to the final primary key that spring-data-cassandra needs?
Now it works, i can use a composite key within a Cassandra table. I had to put the PrimaryKeyColumn fields within the same @Table anotated pojo, not Primary Class and the types of the composite key must be String or Long or whatever you think. I wasnt able to get it working with a UUID class, but i can live with it.
This is the @Table anotated class:
import org.springframework.cassandra.core.Ordering;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import java.time.LocalDateTime;
import java.util.Map;
import javax.validation.constraints.NotNull;
import com.datastax.driver.core.DataType;
@Table(value=AdminUserCassandraTable.tableName)
public class AdminUserCassandraTable {
@org.springframework.data.annotation.Transient
public static final String tableName = "AdminUser";
@PrimaryKeyColumn(name = "id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
@CassandraType(type = DataType.Name.TEXT)
private String id;
@PrimaryKeyColumn(name = "username", ordinal = 1, type = PrimaryKeyType.CLUSTERED,ordering = Ordering.ASCENDING)
@CassandraType(type = DataType.Name.TEXT)
private String username;
@Column("tenant_id")
private String tenantId;
@Column("org_id")
private String orgId;
@Column("is_superAdmin")
private boolean superAdmin;
@NotNull
@Column("password")
private String password;
@Column("email_address")
private String emailAddress;
@Column("metadata")
private Map<String, String> metadata;
@CreatedDate
@Column("created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Column("last_modified")
private LocalDateTime lastModified;
public static String getTableName() {
return tableName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public boolean isSuperAdmin() {
return superAdmin;
}
public void setSuperAdmin(boolean superAdmin) {
this.superAdmin = superAdmin;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
}
The CassandraRepository looks like:
import java.util.Optional;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import unicon.matthews.admin.AdminUser;
import unicon.matthews.admin.service.AdminUserCassandraTable;
public interface AdminUserRepository extends CassandraRepository<AdminUserCassandraTable> {
@Query("select * from AdminUser where username = ?0 ALLOW FILTERING")
Optional<AdminUser> findByUsername(final String userName);
@Query("select * from AdminUser where id = ?0 ALLOW FILTERING")
Optional<AdminUser> findById(final String id);
@Query("select * from AdminUser where id = ?0 and username = ?1")
Optional<AdminUser> findByIdAndUsername(final String id,final String userName);
}
The service looks the same than before.
Thank you and have a nice day!