JPA: Validate fetched field against an Enum - java

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 !

Related

How can I perform a Spring Data JPA Query using an Enum in my circumstance?

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?

Desrialize Enum List in Spring JPA

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.

Get values from Database as key pair

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;

"Fail to convert to internal representation" while accessing changed column data with audit query

I am using envers in my project to audit data.
Now I want to access changed data with audit query.
My pojo class for table is below
#Entity
#Audited(withModifiedFlag=true)
#Table(name = "INSTRUMENT", uniqueConstraints = #UniqueConstraint(columnNames = "INSTRUMENT_NAME"))
public class Instrument implements java.io.Serializable {
private long instrumentId;
private String instrumentName;
private WorkflowState workflowState;
#Id
#Column(name = "INSTRUMENT_ID", unique = true, nullable = false, precision = 22, scale = 0)
public long getInstrumentId() {
return this.instrumentId;
}
public void setInstrumentId(long instrumentId) {
this.instrumentId = instrumentId;
}
#Column(name = "INSTRUMENT_NAME", unique = true, nullable = false, length = 50)
public String getInstrumentName() {
return this.instrumentName;
}
public void setInstrumentName(String instrumentName) {
this.instrumentName = instrumentName;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS", nullable = false)
public WorkflowState getWorkflowState() {
return this.workflowState;
}
public void setWorkflowState(WorkflowState workflowState) {
this.workflowState = workflowState;
}
}
Now I tried to access this with audit query as
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Instrument.class, false, true)
.add(AuditEntity.property("status").hasChanged());
List list= query.getResultList();
So at the time of accessing getResultList() , Its throwing Exception as follows
SqlExceptionHelper: Fail to convert to internal representation
I figured it out, this is because in my db Instrument.status column is as String data Type. While here I am using Join.
So please tell me how to write query to resolve this problem
PROBLEM is How to write Audit Query if my table has foreign key (class property have join dependency).
Join table WorkflowState discription is as follows
public class WorkflowState implements java.io.Serializable {
private BigDecimal stateId;
private Workflow workflow;
private String stateName;
//getters and setters
And it has a join column too i.e "workflow" .
Use workflowState rather than status. The API is based on you specifying the property name and not the column name.

Hibernate annotation using #Parameter is not working

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;

Categories