I have created custom defined types in cassandra and I'm trying to insert the data using spring. At first i thought using entity like this would work:
#PrimaryKeyColumn(name = "appid", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String appid;
#PrimaryKeyColumn(name = "event", ordinal = 1, type = PrimaryKeyType.PARTITIONED, ordering = Ordering.ASCENDING)
private String event;
#PrimaryKeyColumn(name = "date", ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private Date date;
#PrimaryKeyColumn(name = "userid", ordinal = 3, type = PrimaryKeyType.PARTITIONED)
private String userid;
#Column (name = "items")
#Frozen
private Map<CassandraEventParamModel,CassandraEventStatsModel> items;
And created a UDT in java like this (param from create type):
#UDT(keyspace = "ms_analytics", name = "param")
public class CassandraEventParamModel {
#Field(name = "key")
public String key;
#Field(name = "value")
public String value;
but I kept getting an error which says
Invocation of init method failed; nested exception is org.springframework.data.cassandra.mapping.VerifierMappingExceptions:
Cassandra entities must have the #Table, #Persistent or #PrimaryKeyClass Annotation
I wonder what is the proper way to deliver data using spring-data into cassandra.
Thanks
Related
So, I have a Model defined where I want to have a particular variable as ENUM.
Now I have defined it in the model like this.
#Type(
type = "array",
parameters = { #Parameter(name = ListArrayType.SQL_ARRAY_TYPE, value = "member_role") }
)
#Column(name = "access_roles", columnDefinition = "member_role[]")
#Enumerated(EnumType.STRING)
private List<ProjectMemberRole> accessRoles;
The Enum is
#Getter
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ProjectMemberRole {
LEAD("lead", 4),
COLLABORATOR("collaborator", 3),
PARTICIPANT("participant", 2),
VIEWER("viewer", 1);
private final String value;
private final Integer level;
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static ProjectMemberRole forString(String value) {
return stream(ProjectMemberRole.values())
.filter(v -> v.value.equals(value))
.findFirst()
.orElse(null);
}
#Override
#JsonValue
public String toString() {
return this.value;
}
}
Even. though I am able to create, when I fetch I get this error
"No enum constant project.model.ProjectMemberRole.collaborator; nested exception is java.lang.IllegalArgumentException: No enum constant project.model.ProjectMemberRole.collaborator"
So, it seems its getting serialized but not getting de-serialized. What should I be doing in this situation?
EDIT:
I was checking if we have a single element instead of an Array.
If we have a single value then it goes with ENUM Name COLLABORATOR but if we send it as List then it becomes collaborator
So for some reason it is saving JSON value for enum.
#Column(name = "access_roles")
#Enumerated(EnumType.STRING)
private ProjectMemberRole[] accessRoles;
#Column(name = "access_role", columnDefinition = "text")
#Enumerated(EnumType.STRING)
private ProjectMemberRole accessRole;
enums are usually expected to be upper-case during deserialization. You can enable it for Jackson using
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
when you configure your ObjectMapper.
I have this table which I would like to store different values as keys and vales:
#Entity
#Table(name = "wpf_payment_attributes")
public class WpfPaymentAttributes implements Serializable {
private static final long serialVersionUID = -2629784870868584850L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private int id;
#Column(length = 255)
private String name;
#Column(length = 255)
private String global_ley;
#Column(name = "VALUE", columnDefinition = "TEXT", length = 65535)
private String value;
....
}
WpfPaymentAttributes attibutes = new WpfPaymentAttributes();
attibutes.setName("usage");
attibutes.setValue("Test Usage");
attibutes.setGlobal_key(12333);
WpfPaymentAttributes attibutes = new WpfPaymentAttributes();
attibutes.setName("name");
attibutes.setValue("Peter");
attibutes.setGlobal_key(12333);
But how I can get all value with the same global key with one SQL query using JPA? The problem is that I don't know in advance what are the table columns and values.
I need to get this structure:
usage | name
-------------------
Test Usage | Peter
Is this possible with JPA?
This is not possible, since there are some issues that JPA won't be able to help you with:
there could be multiple WpfPaymentAttributes values with the same
global key and name (however, this could be solved by using a
database constraint);
there could be arbitrary values in the name
column, hence you'd have to make sure that they actually map to your expected result structure, there are no unknown "names" etc.
If you don't need a super-generic system, I'd advice you to write a simple mapper, that shouldn't be very complex. Just get all WpfPaymentAttributes by a specific global_key and apply the mapping. For example, here's the structure that you need:
public class Result {
private String usage;
private String name;
// ...
}
And then:
Result result = new Result();
List<WpfPaymentAttributes> attributes = entityManager.createQuery(
// query should be parameterized
"select a from WpfPaymentAttributes a where global_key = 12333"
).getResultList();
for (WpfPaymentAttributes attribute : attributes) {
String value = attribute.getValue();
switch(attribute.getName()) {
case "name":
result.setName(value);
break;
case "usage":
result.setUsage(value);
break;
default:
throw new IllegalStateException();
}
}
return result;
I like to store a object like:
#Table(value = "my_table")
public class MyTableDto {
#PrimaryKeyColumn(name = "uid", type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = DataType.Name.UUID)
private UUID uid;
#Column(value = "child_ids")
private List<ChildIdDto> childIds;
}
Then I get the exception:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Only primitive types are allowed inside Collections for property [childIds] of type ['interface java.util.List'] in entity [de.myapplication.repository.dto.MyTableDto]
I do understand the exception, but is there another way to persist custom objects?
EDIT:
When I comment out this attribute, everything works
! Never say never, I got the solution.
To give a good example, I will list all according classes.
ParentClass.java
#Table(value = "my_table") //OPT
public class MyTableDto {
#PrimaryKeyColumn(name = "uid", type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = DataType.Name.UUID)
private UUID uid;
#Column(value = "child_ids") //OPT
private List<ChildDto> childIds;
}
ChildDto.java
#UserDefinedType // THE SOLUTION
public class ChildDto {
#Column(value = "child") //OPT
#CassandraType(type = DataType.Name.TEXT) //OPT
private String groupId;
#Column(value = "description") //OPT
#CassandraType(type = Name.TEXT) //OPT
private String description;
}
The #UserDefinedType is the solution.
For more information see here.
NOTE: Each annotation with "OPT" is NOT required
I'm working on setting up a DAO for Cassandra in spring.
Now I have a question regarding using composite classes multiple times in an object.
I have this class Setting:
#Table(value = "settings")
public class Setting {
#PrimaryKey
private User owner;
#Column("key")
private String key;
#Column("value")
private String value;
#Column("updated_by")
private User updatedBy;
}
And the class User:
#PrimaryKeyClass
public class User implements Serializable{
#PrimaryKeyColumn(name = "userId", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String id;
#PrimaryKeyColumn(name = "userIdType", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private String idType;
}
So I'm using the class User twice. Once as primary key "owner" and once as "updatedBy".
When using this in the CassandraOperations classes, it works fine as the primary key, but not again as another column.
It complains about column name userId already being used. Makes sense.
So how can I make this work?
I could use UserDefined Types perhaps?
CREATE TYPE test_keyspace.user (
id text,
id_type text
);
But how can I do that from java Annotations?
Or, how can I reuse the same class otherwise?
Considering the relatively simple data structures in Cassandra, I am ok with flattening the User class as a single String like idType.id too.
Thanks for any help!
Ok, found it here, answered by denzal.
My classes now look like:
#Table("settings")
public class Setting {
#PrimaryKeyColumn(name = "owner", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
#CassandraType(type = DataType.Name.UDT, userTypeName = "user_type")
private User owner;
#PrimaryKeyColumn(name = "key", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
#CassandraType(type = DataType.Name.TEXT)
private String key;
#CassandraType(type = DataType.Name.TEXT)
private String value;
#CassandraType(type = DataType.Name.UDT, userTypeName = "user_type")
private User lastUpdatedBy;
}
And User Class:
#UserDefinedType("user_type")
public class User implements Serializable{
#CassandraType(type = DataType.Name.TEXT)
private String id;
#CassandraType(type = DataType.Name.TEXT)
private IdType idType;
}
Works nicely.
I am using Hibernate custom user type to map enum to a table varchar/char. I am using custom user type code from here (https://community.jboss.org/wiki/Java5StringValuedEnumUserType). I am trying following annotation to do the mapping but its not working.
#Transient
#Type(type = "data.model.base.StringValuedEnumType", parameters = {
#Parameter(name = "enumClass", value = "data.common.TypeEnum"),
#Parameter(name = "identifierMethod", value = "dbCode") })
private TypeEnum typeEnum;
TypeEnum code:
public enum TypeEnum implements StringValuedEnum {
OFF("OFF", "O"),
ON("ON, "O"),
private String dbCode;
private String desc;
TypeEnum(String desc, String dbCode) {
this.desc=desc;
this.dbCode = dbCode;
}
#Override
public String dbCode() {
return dbCode;
}
public String desc() {
return desc;
}
}
I believe I am doing something wrong in the annotation but I am not able to figure out what is it. Any idea anyone?
I have found it. I updated the annotation by removing #Transient and adding in a #Column for the mapping. I also updated the code to take care of the passed name and removed defaultValue.
#Column(name = "TYP_CD", length = 1)
#Type(type = "data.model.base.StringValuedEnumType", parameters = {
#Parameter(name = "enumClass", value = "data.common.TypeEnum")})
private TypeEnum typeEnum;