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.
Related
I have an entity like so:
#Entity
#Table(name = "MyTable", schema = "test")
#Getter #Setter
public class PurgeSystemsEntity {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "system_name", nullable = false, length = 255)
private String systemName;
.
.
}
How do I validate that the string obtained from DB (like when doing a .findAll()) in systemName field is one of the possible options defined in the Enum System :
public static enum System {
PROD, DEV, QA;
}
So, If a row is fetched with systemName value being 'STAGING', it should throw an exception immediately.
Is there some elegant way to do this?
Set the field type to the enum.
#Column(name = "system_name", nullable = false, length = 255)
#Enumerated(EnumType.STRING)
private System systemName;
This will cause an error if you encounter a value not defined in the enum.
You also have to set EnumType.STRING explicitly, as it defaults to EnumType.ORDINAL which would correspond to the enum ordinal value instead of the name
You can write your own method in ENUM
Something like this
public static MyEnum fromValue(String value) {
for (MyEnum b : MyEnum.values()) {
if (b.value.equals(value)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
}
Alternatively, if your Enum is part of your class, I think , Java should automatically handle and throw IllegalArgumentException if the value cannot be mapped !
First sorry this is long but I wanted to provide all information possible.
I am working on a much larger query that will build on this hence the reason I am not taking an easier or other approaches. In addition I can't really change the way we implemented the DB and Domain Objects.
My problem is I can't get a Spring Data JPA Query to work with an Enum. The field is an Enum in the DB as well. Here is the abbreviated code.
The SQL for the 2 tables:
CREATE TABLE my_order (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
creation_date TIMESTAMP NOT NULL,
);
CREATE TYPE ORDER_STATE as ENUM ('new', 'open', 'closed');
CREATE CAST (CHARACTER VARYING AS ORDER_STATE) WITH INOUT AS IMPLICIT;
CREATE TABLE my_order_history (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_date TIMESTAMP NOT NULL,
order_state ORDER_STATE NOT NULL,
order_id INT REFERENCES my_order
);
Here is the corresponding Domain Objects:
#Entity
#Table(name = "my_order")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "creation_date")
private Date creationDate;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderHistory> orderHistories;
}
#Entity
#Table(name = "my_order_history")
public class OrderHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "order_date ")
private Date orderDate;
#Column(name = "order_state")
#Convert(converter = OrderStateConverter.class)
private OrderState orderState
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
Here is the converter:
#Component
#Converter(autoApply = true)
public class OrderStateConverter implement AttributeConverter<OrderState, String> {
#Override
public String convertToDatabaseColumn(OrderState attribute) {
return attribute.getValue;
}
#Override
public OrderState convertToEntityAttribute(String dbData) {
return OrderState.fromValue(dbData);
}
}
Here is the Enum:
public enum OrderState {
NEW("new"), OPEN("open"), CLOSED("closed");
#Getter
private String value;
private OrderState(String value) {
this.value = value;
}
public static OrderState fromValue(String value) {
for (OrderState orderState : values()) {
if (orderState.getValue().equals(value)) {
return orderState;
}
}
return null;
}
}
Here is my Spring Repo. I am putting the 3 ways I have tried and then below I will give you the exceptions I am receiving with each:
#Repository
public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :orderState")
List<Order> getOrdersByOrderState1(#Param("orderState") OrderState orderState);
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :orderState")
List<Order> getOrdersByOrderState2(#Param("orderState") String orderState);
#Query("SELECT o FROM Order o JOIN o.orderHistories oh WHERE oh.orderState = :#(#orderState?.getValue())")
List<Order> getOrdersByOrderState3(#Param("orderState") OrderState orderState);
}
For #1 when I provide an OrderState enum I get the following exception:
Caused by: org.postgres.util.PSQLException: ERROR: operator does not exist: order_state = character varying
Hint: No operator matches the given name and argument types. You might need explicit type casts.
For #2 when I provide OrderState.getValue(), which is a String, I get the following exception:
java.jang.IllegalArgumentException: Parameter value [new] did not match expected type [com.testing.enums.OrderState (n/a)]
For #3 when I provide an OrderState enum I get the following exception (same as #2):
java.jang.IllegalArgumentException: Parameter value [new] did not match expected type [com.testing.enums.OrderState (n/a)]
Basically I try to send in the enum and get an error but I also get an error when I try to send in a String. Is there anything I can do? What is exactly happening?
I have the following Entity containing a field of Enum type:
#Entity
#Table(name = "INPUT_DATA")
public class InputDataEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "INPUT_DATA_SEQ", allocationSize = 1, sequenceName = "INPUT_DATA_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INPUT_DATA_SEQ")
private Long id;
#Column(name = "FIELD1", nullable = false)
private String field1;
#Column(name = "FIELD2", nullable = false)
#Convert(converter = Type.Converter.class)
private Type field2;
// getters and setters
}
The Enum type looks like:
public enum Type {
ENUM_ITEM_1("item1"),
// more items
ENUM_ITEM_N("itemN");
private String code;
private Type(String code) {
this.code = code;
}
public static Type fromString(String name) {
switch (name) {
case "item1":
return ENUM_ITEM_1;
// more cases
case "itemN":
return ENUM_ITEM_N;
default:
throw new IllegalArgumentException("Wrong value for Type");
}
}
#Override
public String toString() {
return code;
}
#javax.persistence.Converter
public static class Converter implements AttributeConverter<Type, String> {
#Override
public String convertToDatabaseColumn(Type attribute) {
return attribute.toString();
}
#Override
public Type convertToEntityAttribute(String s) {
return Type.fromString(s);
}
}
}
The problem is that hibernate doesn't recognize my Converter when I want to fetch data from the database.
I've also tried:
#Embedded and #Embeddable but with no luck.
#Enumerated(EnumType.STRING) but again with no luck.
My question is:
how to make hibernate to recognize my converter when converting the appropriate field?
Many thanks in advance.
I eventually ended up by implementing a StringValuedEnum interface and its relevant reflector and type class by implementing EnhancedUserType, ParameterizedType as it was described here.
This helped me to properly store into and retrieve from DB data corresponding to user defined enum types, although the questions with converters remains still open. If someday a proper answer will be given, that will be very appreciated.
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;