Нечувствительное к регистру Enum-Mapping с Hibernate

Я получил объект со столбцом state. Состояния, хранящиеся в БД, бывают активными и неактивными (и еще некоторые). Я написал себе перечисление, подобное следующему

public enum State {

    ACTIVE("active"), INACTIVE("inactive");

    private String state;

    private State(String state) {
        this.state = state;
    }

}

Сущность выглядит так:

@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

}

К сожалению, я получаю следующее сообщение об ошибке:

javax.ejb.EJBTransactionRolledbackException: Unknown name value [active] for enum class [state]

Можно ли выполнить нечувствительное к регистру сопоставление гибернации с перечислением?


person Chris311    schedule 21.10.2015    source источник
comment
почему ты не используешь ACTIVE("ACTIVE") ???   -  person Jordi Castilla    schedule 21.10.2015
comment
Что бы ты ни делал правильно, покажи свою сущность   -  person Forkmohit    schedule 21.10.2015
comment
stackoverflow.com/ вопросов/17493590/ проверьте это   -  person Vikas Sharma    schedule 21.10.2015
comment
@Castilla: Потому что это не поможет (см. Ответ Костера).   -  person Chris311    schedule 21.10.2015


Ответы (2)


Вы можете сопоставить перечисление как ORDINAL или STRING с аннотациями гибернации, например:

@Enumerated(EnumType.ORDINAL)
private State state;

Порядковое сопоставление помещает порядковый номер перечисления в базу данных. Если вы измените порядок значений перечисления в своем коде, это будет конфликтовать с существующим состоянием базы данных. Сопоставление строк помещает имя перечисления в верхнем регистре в базу данных. Если вы переименуете значение перечисления, вы получите ту же проблему.

Если вы хотите определить пользовательское сопоставление (например, ваш код выше), вы можете создать реализацию org.hibernate.usertype.UserType, которая явно отображает перечисление.

Сначала я предлагаю некоторые изменения в вашем перечислении, чтобы сделать возможным следующее:

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;
    }
}

А вот простая (!) реализация 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);   
    }   
}   

Тогда отображение станет:

@Type(type="foo.bar.StateUserType")
private State state;

Другой пример реализации UserType см. по адресу: http://www.gabiaxel.com/2011/01/better-enum-mapping-with-hibernate.html

person Adriaan Koster    schedule 21.10.2015
comment
Я думаю, что тогда я предпочел бы не использовать перечисление для Entity. Для меня это выглядит как накладные расходы и увеличение сложности, учитывая, что мне просто нужна строка в верхнем регистре записи в БД. - person Chris311; 21.10.2015
comment
Если вы используете аннотацию EnumType.STRING по умолчанию, вы получаете имя перечисления в верхнем регистре в базе данных. Это наиболее используемое решение, с которым я сталкивался. Для этого вам не нужны какие-либо частные поля или дополнительные методы в вашем перечислении, потому что перечисления по умолчанию предоставляют общедоступный метод String name(). - person Adriaan Koster; 21.10.2015
comment
Я знаю, что ты хочешь сказать мне этим? Ваше решение может сработать, но я думаю, что тогда лучше работать без @Enumerated и просто использовать String для сущности вместо перечисления. - person Chris311; 21.10.2015
comment
Я думаю, что лучше использовать поле State, чем поле String в вашей сущности, и для этого есть хорошая поддержка в Hibernate. - person Adriaan Koster; 21.10.2015
comment
Хорошая поддержка? StateUserType повышает сложность кода. Я скорее думаю, что должно быть что-то вроде @Enumeration(Mode = EnumVale.IGNORE_CASE). - person Chris311; 21.10.2015
comment
Есть два разумных сопоставления по умолчанию, и есть возможность свернуть свое собственное (что требует больше работы). Я считаю, что это достаточно хорошая поддержка. Если вам это не нравится, вы должны стать участником проекта с открытым исходным кодом и внедрить лучшее решение. - person Adriaan Koster; 21.10.2015

Я столкнулся с подобной проблемой и нашел простой ответ.

Вы можете сделать что-то вроде:

@Column(name = "my_type")
@ColumnTransformer(read = "UPPER(my_type)", write = "LOWER(?)")
@Enumerated(EnumType.STRING)
private MyType type;

(вам не нужно "записывать" в @ColumnTransformer - для меня это для обратной совместимости, потому что мои строки только в нижнем регистре. Без write Hibernate будет писать enum в том же регистре, что и в коде в константе перечисления)

person Alexey Stepanov    schedule 13.05.2019
comment
или также вы можете использовать конвертеры атрибутов JPA - person Alexey Stepanov; 18.08.2019