I got an entity with a column state. States stored in the DB are active and inactive (and some more). I wrote myself an enum like the following
public enum State {
ACTIVE("active"), INACTIVE("inactive");
private String state;
private State(String state) {
this.state = state;
}
}
The entity looks like:
#Entity
#Table(name = "TEST_DB")
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private Integer id;
#Enumerated(EnumType.STRING)
#Column(name = "STATE", nullable = false)
private Integer state;
// constructor, getter, setter
}
Unfortunately I get the following error message:
javax.ejb.EJBTransactionRolledbackException: Unknown name value [active] for enum class [state]
Is it possible to do a case-insensitive hibernate-mapping to an enum?
I was facing with similar problem and found simple answer.
You can do something like:
#Column(name = "my_type")
#ColumnTransformer(read = "UPPER(my_type)", write = "LOWER(?)")
#Enumerated(EnumType.STRING)
private MyType type;
(you don't need for "write" in #ColumnTransformer - for me it's for back compatibility, because my rows only in lower case. Without write Hibernate will write enum in same case, as in code in enum constant)
You can map an enum as an ORDINAL or a STRING with hibernate annotations, for example:
#Enumerated(EnumType.ORDINAL)
private State state;
The ordinal mapping puts the ordinal position of the enum in the database. If you change the order of the enum values in your code this will conflict with existing database state. The string mapping puts the upper case name of the enum in the database. If you rename an enum value, you get the same problem.
If you want to define a custom mapping (like your code above) you can create an implementation of org.hibernate.usertype.UserType which explicitly maps the enum.
First I suggest some changes to your enum to make what follows possible:
public enum State {
ACTIVE("active"), INACTIVE("inactive");
private String stateName;
private State(String stateName) {
this.stateName = stateName;
}
public State forStateName(String stateName) {
for(State state : State.values()) {
if (state.stateName().equals(stateName)) {
return state;
}
}
throw new IllegalArgumentException("Unknown state name " + stateName);
}
public String stateName() {
return stateName;
}
}
And here is a simple (!) implementation of UserType:
public class StateUserType implements UserType {
private static final int[] SQL_TYPES = {Types.VARCHAR};
public int[] sqlTypes() {
return SQL_TYPES;
}
public Class returnedClass() {
return State.class;
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException {
String stateName = resultSet.getString(names[0]);
State result = null;
if (!resultSet.wasNull()) {
result = State.forStateName(stateName);
}
return result;
}
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException, SQLException {
if (null == value) {
preparedStatement.setNull(index, Types.VARCHAR);
} else {
preparedStatement.setString(index, ((State)value).stateName());
}
}
public Object deepCopy(Object value) throws HibernateException{
return value;
}
public boolean isMutable() {
return false;
}
public Object assemble(Serializable cached, Object owner) throws HibernateException
return cached;
}
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable)value;
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
}
if (null == x || null == y) {
return false;
}
return x.equals(y);
}
}
Then the mapping would become:
#Type(type="foo.bar.StateUserType")
private State state;
For another example of how to implement UserType, see: http://www.gabiaxel.com/2011/01/better-enum-mapping-with-hibernate.html
Related
I am new to drools, please bear with me if this is a silly question. I have a class member type of Object which I am using to store JSON value (passed from frontend), due to unstructured data I am using Object type for a variable. Here's the POJO class.
public class Submission {
#Id
private String id;
private String form;
private String formId;
private Object data;
private Date createdAt = new Date();
private Date modifiedAt = new Date();
private String state;
private Boolean isDeleted = false;
private Boolean valid = false;
public Boolean getValid() {
return valid;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public String getForm() {
return form;
}
public void setForm(String form) {
this.form = form;
}
public String getFormId() {
return formId;
}
public void setFormId(String formId) {
this.formId = formId;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getModifiedAt() {
return modifiedAt;
}
public void setModifiedAt(Date modifiedAt) {
this.modifiedAt = modifiedAt;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Boolean getDeleted() {
return isDeleted;
}
public void setDeleted(Boolean deleted) {
isDeleted = deleted;
}
}
and this is my .drl
package rules;
import com.ics.lcnc.Submission.Submission;
rule "check name is correct"
when
Submission(data.name == "john")
then
submission.setValid(true)
end
But for above file I get following error when I try to load this file into KieBuilder
Message [id=3, kieBase=defaultKieBase, level=ERROR, path=rules/123.drl, line=5, column=0
text=Unable to Analyse Expression data.name == "hashim":
[Error: unable to resolve method using strict-mode: java.lang.Object.name()]
[Near : {... data.name == "hashim" ....}]
Seems like rule engine is unable to find nested property of Object data. How do I target nested property which will be known to a program at runtime only?
you may try with having the data field be converted (eg by Jackson) as a Map following a prototype based approach and then
...
when
Submission(data["name"] == "john") ...
of instead of Map have a JSONNode (and adapt the constraint in the rule), again for the data field in your Submission object model.
Scenario: A data object which persists in the DB table. There are some old entries in the table. Now I have to apply encryption to new further entries in the table. So I add a new column which has the field encrypted set to False by default to check if the values are encrypted.
Problem: I want to write an annotation to encrypt the fields in the data model(POJO) before persisting and decrypt on getter() calls only if it is encrypted.
Context:
The user model.
public class UserData {
#Id
#Column(name = "ID", length = 36)
private String id;
#Column(name = "IS_ENCRYPTED")
private boolean isEncrypted;
#Column(name = "NAME")
#Convert(converter = EncryptionConverter.class)
private String name;
// more fields ....
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// more similar getter and setters
}
The encryption class that i have written.
#Converter
public class EncryptionConverter implements AttributeConverter<String, String>{
private final String secretKey= "someSecret";
UserData Data = new UserData();
#Override
public String convertToDatabaseColumn(String str) {
if(!isNullOrBlank(str))
return AesEncrypt.encrypt(str, secretKey);
return str;
}
#Override
public String convertToEntityAttribute(String encrypedStr) {
if(!isNullOrBlank(encrypedStr) && Data.isEncrypted)
return AesEncrypt.decrypt(encrypedStr, secretKey);
return encrypedStr;
}
}
This class is inside the model class. (can move outside, but how to pass isencrypted flag to annotation)
How can I do this, is my approach correct?
Edit: there are multiple fields which are to be encrypted/decrypted not just name.
You can create the encryption behaviour in another configuration class, say EncryptedPropertyConfig, in this you can create a bean, EncryptablePropertyResolver from jasypt-spring-boot
#EnableAutoConfiguration
public class EncryptedPropertyConfig {
public EncryptedPropertyConfig() {
}
#Bean
public EncryptablePropertyResolver encryptablePropertyResolver() {
EncryptablePropertyResolver r = new MyPropertyPlaceholderConfigurer();
return r;
}
}
public final class MyPropertyPlaceholderConfigurer implements EncryptablePropertyResolver {
private StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
private EnvironmentStringPBEConfig envConfig = new EnvironmentStringPBEConfig();
public MyPropertyPlaceholderConfigurer() {
// set the encryption key and config
}
public String resolvePropertyValue(String passedValue) {
if (!PropertyValueEncryptionUtils.isEncryptedValue(passedValue)) {
return passedValue;
} else {
String returnValue = "";
try {
returnValue = PropertyValueEncryptionUtils.decrypt(passedValue, this.encryptor);
return returnValue;
} catch (Exception var4) {
throw new RuntimeException("Error in decryption of property value:" + passedValue, var4);
}
}
}
}
I suggest alternative solution using Entity Listeners
import javax.persistence.PostLoad;
import javax.persistence.PreUpdate;
public class UserData {
private final String secretKey= "someSecret";
// ...
#PreUpdate
private void onUpdate() {
// triggered before saving entity to DB (both create & update)
if(!isNullOrBlank(name)) {
name = AesEncrypt.encrypt(name, secretKey);
}
}
#PostLoad
private void onLoad() {
// triggered after entity is fetched from Entity Provider
if (!isNullOrBlank(name) && isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
}
}
Instead of using JPA AttributeConverter you can implement hibernate user type in this way:
import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StringType;
import org.hibernate.usertype.UserType;
public class CustomNameType implements UserType
{
private String secretKey = "someSecret";
public CustomNameType()
{
}
#Override
public Object deepCopy(Object value) throws HibernateException
{
if (null == value) return null;
return ((CustomName) value).clone();
}
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
#Override
public Serializable disassemble(Object value) throws HibernateException
{
return (Serializable) value;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
#Override
public boolean equals(Object one, Object two) throws HibernateException
{
return Objects.equals(one, two);
}
#Override
public int hashCode(Object obj) throws HibernateException
{
return Objects.hashCode(obj);
}
#Override
public boolean isMutable()
{
return true;
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException
{
boolean isEncrypted = rs.getBoolean(0); // IS_ENCRYPTED
String name = rs.getString(1); // NAME
if (isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
return new CustomName(isEncrypted, name);
}
#Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException
{
CustomName customName = (CustomName) value;
String name = customName.getName();
if (customName.isEncrypted()) {
name = AesEncrypt.encrypt(name, secretKey);
}
statement.setBoolean(0, customName.isEncrypted());
statement.setString(1, name);
}
#Override
public Class<?> returnedClass()
{
return CustomName.class;
}
#Override
public int[] sqlTypes()
{
// I do not know the types of your IS_ENCRYPTED and NAME fields
// So, this place maybe require correction
int[] types = {BooleanType.INSTANCE.sqlType(), StringType.INSTANCE.sqlType()};
return types;
}
}
where CustomName is:
public class CustomName implements Serializable, Cloneable
{
private boolean isEncrypted;
private String name;
public CustomName(boolean isEncrypted, String name)
{
this.isEncrypted = isEncrypted;
this.name = name;
}
// getters , equals, hashCode ...
#Override
public CustomName clone()
{
return new CustomName(isEncrypted, name);
}
}
and then use it:
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Columns;
#Entity
public class UserData {
#Type(type = "com.your.CustomNameType")
#Columns(columns = {
#Column(name = "IS_ENCRYPTED"),
#Column(name = "NAME")
})
private CustomName name;
}
Assuming I have a Hibernate/JPA Entity like the following:
#Entity
public class FooEntity {
...
#Type(type = "hstore")
HashMap<String, String> tags;
}
... and the hstore Type is a simple UserType implementation from this resource.
Is there a way to access the hstore in a JPQL query similar to this Pseudocode:
SELECT f FROM FooEntity f WHERE f.tags CONTAINS KEY(:key)
You can also simply create a Hibernate org.hibernate.usertype.UserType. You extend that class; an example from our own implementation:
public class HstoreUserType implements UserType {
/**
* PostgreSQL {#code hstore} field separator token.
*/
private static final String HSTORE_SEPARATOR_TOKEN = "=>";
/**
* {#link Pattern} used to find and split {#code hstore} entries.
*/
private static final Pattern HSTORE_ENTRY_PATTERN = Pattern.compile(String.format("\"(.*)\"%s\"(.*)\"", HSTORE_SEPARATOR_TOKEN));
/**
* The PostgreSQL value for the {#code hstore} data type.
*/
public static final int HSTORE_TYPE = 1111;
#Override
public int[] sqlTypes() {
return new int[] { HSTORE_TYPE };
}
#SuppressWarnings("rawtypes")
#Override
public Class returnedClass() {
return Map.class;
}
#Override
public boolean equals(final Object x, final Object y) throws HibernateException {
return x.equals(y);
}
#Override
public int hashCode(final Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(final ResultSet rs, final String[] names,
final SessionImplementor session, final Object owner)
throws HibernateException, SQLException {
return convertToEntityAttribute(rs.getString(names[0]));
}
#SuppressWarnings("unchecked")
#Override
public void nullSafeSet(final PreparedStatement st, final Object value, final int index,
final SessionImplementor session) throws HibernateException, SQLException {
st.setObject(index, convertToDatabaseColumn((Map<String,Object>)value), HSTORE_TYPE);
}
#SuppressWarnings("unchecked")
#Override
public Object deepCopy(final Object value) throws HibernateException {
return new HashMap<String,Object>(((Map<String,Object>)value));
}
#Override
public boolean isMutable() {
return true;
}
#Override
public Serializable disassemble(final Object value) throws HibernateException {
return (Serializable) value;
}
#Override
public Object assemble(final Serializable cached, final Object owner)
throws HibernateException {
return cached;
}
#Override
public Object replace(final Object original, final Object target, final Object owner)
throws HibernateException {
return original;
}
private String convertToDatabaseColumn(final Map<String, Object> attribute) {
final StringBuilder builder = new StringBuilder();
for (final Map.Entry<String, Object> entry : attribute.entrySet()) {
if(builder.length() > 1) {
builder.append(", ");
}
builder.append("\"");
builder.append(entry.getKey());
builder.append("\"");
builder.append(HSTORE_SEPARATOR_TOKEN);
builder.append("\"");
builder.append(entry.getValue().toString());
builder.append("\"");
}
return builder.toString();
}
private Map<String, Object> convertToEntityAttribute(final String dbData) {
final Map<String, Object> data = new HashMap<String, Object>();
if (dbData != null) {
final StringTokenizer tokenizer = new StringTokenizer(dbData, ",");
while(tokenizer.hasMoreTokens()) {
final Matcher matcher = HSTORE_ENTRY_PATTERN.matcher(tokenizer.nextToken().trim());
if(matcher.find()) {
data.put(matcher.group(1), matcher.group(2));
}
}
}
return data;
}
}
Now you may use it within in Entity bean like so:
#Entity
#Table(name="YourEntityBeanTable")
#TypeDefs({
#TypeDef(name = "hstore", typeClass = HstoreUserType.class)
})
public class YourEntityBean {
.....
#Type(type = "hstore")
#Column(name= "an_hstore_column", columnDefinition = "hstore")
private Map<String, String> anHStoreColumn = new HashMap<>();
}
Hibernate offers a common query abstraction across many DBs so a non-SQL syntax is hard to be abstracted out.
I would go for a native query to fetch the ids and use those to get Hibernate entities if you really need those anyway.
If you are only interested in projections than a native query is your best choice.
I am trying to persist my menuItem Object in the database. One of the itemFields is an ItemType string which is bounded by ItemType enum. Here is my MenuItem POJO.
public class MenuItemImpl implements MenuItem{
private long id;
private String itemName;
private String itemType;
private int itemPrice;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName= itemName;
}
public ItemType getItemType() {
return ItemType.valueOf(itemType);
}
public void setItemType(ItemType itemType) {
this.itemType=itemType.toString();
}
public int getItemPrice() {
return this.itemPrice;
}
public void setItemPrice(int itemPrice) {
this.itemPrice=itemPrice;
}
}
This is my ItemType enum
public enum ItemType {
BURGER("BURGER"), BEVERAGE("BEVERAGE"), SNACK("SNACK"), TOY("TOY");
private String value;
private ItemType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return value;
}
}
Now when I try to do a save on this object. I get the following exception:
com.saggezza.ItemType cannot be cast to java.lang.String
I dont see why it should even try casting ItemType. as my getter and setter methods in the menuItem POJO already take care of the conversion of enum to string and string to enum.
Any ideas?
The problem is that Hibernate is using your getters/setters to access your property, so when it wants to get the value of the itemType field to save it, it will call getItemType(), expecting it to return a String (what is in the mapping), and will get an ItemType instead, thus the exception.
You can have a look at this answer to see how you can add your enum to your mapping directly.
I notice in this method:
public void setItemType(ItemType itemType) {
this.itemType=itemType.toString();
}
You have the parameters specified as itemType and the global String variable itemType with the same name, to make it easier to support your code (and figure out where the error is occurring) I would suggest renaming the parameter to item. So like this:
public void setItemType(ItemType item) {
this.itemType=item.toString();
}
Then you always know what item refers to.
I have an enum class named Status as follows
public enum Status {
PENDING(0), SUCCESS(1), FAILED(-1);
private int st;
private Status(int st){
this.st = st;
}
}
and from other class I try to map this status enum
public void setStatus(Status status) {
this.status = status;
}
#Enumerated(EnumType.ORDINAL)
public Status getStatus() {
return status;
}
when I run this code, I get
java.lang.IllegalArgumentException: Unknown ordinal value for enum class data.Status: -1
at org.hibernate.type.EnumType.nullSafeGet(EnumType.java:93)
at org.hibernate.type.CustomType.nullSafeGet(CustomType.java:124)
at org.hibernate.type.AbstractType.hydrate(AbstractType.java:106)
at
but I already have -1 in enum definition.
You could define your own UserType which defines how Hibernate should map those enums.
Note that the ordinal defines the index of the enum value and thus FAILED would have the ordinal 2. To map the enum using its properties your need a UserType implementation.
Some links:
https://community.jboss.org/wiki/UserTypeForPersistingAnEnumWithAVARCHARColumn
http://javadata.blogspot.de/2011/07/hibernate-and-enum-handling.html (look at the "Paramterized Enumeration in Hibernate" section)
Here is a solution where a string label is used instead of an int id, however it is simple to adapt.
public class User {
#Id
private int id;
#Type(type = "com.example.hibernate.LabeledEnumType")
private Role role;
}
public enum Role implements LabeledEnum {
ADMIN("admin"), USER("user"), ANONYMOUS("anon");
private final String label;
Role(String label) {
this.label = label;
}
#Override
public String getLabel() {
return label;
}
}
public interface LabeledEnum {
String getLabel();
}
public final class LabeledEnumType implements DynamicParameterizedType, UserType {
private Class<? extends Enum> enumClass;
#Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x == y;
}
#Override
public int hashCode(Object x) throws HibernateException {
return x == null ? 0 : x.hashCode();
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
String label = rs.getString(names[0]);
if (rs.wasNull()) {
return null;
}
for (Enum value : returnedClass().getEnumConstants()) {
if (value instanceof LabeledEnum) {
LabeledEnum labeledEnum = (LabeledEnum) value;
if (labeledEnum.getLabel().equals(label)) {
return value;
}
}
}
throw new IllegalStateException("Unknown " + returnedClass().getSimpleName() + " label");
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
st.setString(index, ((LabeledEnum) value).getLabel());
}
}
#Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original;
}
#Override
public Class<? extends Enum> returnedClass() {
return enumClass;
}
#Override
public int[] sqlTypes() {
return new int[]{Types.VARCHAR};
}
#Override
public void setParameterValues(Properties parameters) {
ParameterType params = (ParameterType) parameters.get( PARAMETER_TYPE );
enumClass = params.getReturnedClass();
}
}
I would like to suggest following workaround. At first I was supprised it worked but it is really simple:
For enum:
public enum Status {
PENDING(0), SUCCESS(1), FAILED(-1);
private int status;
private Status(int status){
this.status = status;
}
public String getStatus() {
return status;
}
public static Status parse(int id) {
Status status = null; // Default
for (Status item : Status.values()) {
if (item.getStatus().equals(id)) {
Status = item;
break;
}
}
return Status;
}
}
class
class StatedObject{
#Column("status")
private int statusInt;
public Status getStatus() {
return Status.parse(statusInt);
}
public void setStatus(Status paymentStatus) {
this.statusInt = paymentStatus.getStatus();
}
public String getStatusInt() {
return statusInt;
}
public void setStatusInt(int statusInt) {
this.statusInt = statusInt;
}
}
if you are using hibernate in hibernate xml file it would be:
<property name="statusInt " column="status" type="java.lang.Integer" />
that is it