6

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?

Chris311
  • 3,794
  • 9
  • 46
  • 80

2 Answers2

9

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)

Alexey Stepanov
  • 561
  • 6
  • 15
4

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

Chris311
  • 3,794
  • 9
  • 46
  • 80
Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60
  • I think i would rather use no enumeration for the Entity then. This looks like a overhead and increasement in complexity to me, considering i just need the uppercase string of an entry in the db. – Chris311 Oct 21 '15 at 09:11
  • If you use the default EnumType.STRING annotation you get the uppercase enum name in the database. This is the most used solution I have come across. You don't need any private field or extra methods in your enum for that because enums expose a public String name() method by default. – Adriaan Koster Oct 21 '15 at 09:19
  • I know, what do you want to tell my with that? Your solution might work, but I think that it is better to work without @Enumerated then, and just use the String for the entity instead of the enum. – Chris311 Oct 21 '15 at 09:21
  • I think it is cleaner to use a State field than a String field in your entity and there is good support for doing so in Hibernate. – Adriaan Koster Oct 21 '15 at 09:33
  • 1
    Good support? The StateUserType raises the complexity of the code. I rather think there should be something like @Enumeration(Mode = EnumVale.IGNORE_CASE). – Chris311 Oct 21 '15 at 11:30
  • 2
    There are two reasonable default mappings and there is an option to roll your own (which is more work). I consider that reasonably good support. If you don't like it you should become a contributor to an open source project and implement a better solution. – Adriaan Koster Oct 21 '15 at 11:48