Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case insensitive Enum-Mapping with Hibernate

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?

like image 547
Chris311 Avatar asked Oct 21 '15 08:10

Chris311


2 Answers

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)

like image 83
Alexey Stepanov Avatar answered Sep 22 '22 12:09

Alexey Stepanov


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

like image 20
Adriaan Koster Avatar answered Sep 23 '22 12:09

Adriaan Koster