2

I've searched for the implementation of the State pattern in Java with Hibernate and found several references to a solution using Enums in order to provide a flexible way to add new States to the situation.

I liked the solution here, where reflection is used to create the state object from the value of the table field "state" where the ConcreteState class name is saved: http://nerdboys.com/2007/06/08/state-pattern-persistence-with-hibernate/

But this solution was discouraged because holding a String value of the type com.myCompany.myProject.asdasd.ConcreteState in the DB would be a waste of space, in contrast to hold an integer value. So I wonder if there's a way to save the possible states in a table like:

customer_states (PK id INT, className VARCHAR)

And modify my customers table in order to have a FK to the state, like:

customers (PK id INT, name VARCHAR, FK state INT)

So I won't use more disk space than needed, and I would mantain the consistence of customer states so it is easy to add a new state to the situation... But, how would you implement this in your UserType???

Thanks!

jaco0646
  • 15,303
  • 7
  • 59
  • 83
Joaquín L. Robles
  • 6,261
  • 10
  • 66
  • 96

2 Answers2

0

The hibernate mapping could be:

<property name="_Status">
        <column name="STATUS" sql-type="NUMBER" not-null="true"/>
        <type name="GenericEnumUserType">
            <param name="enumClass">Status</param>
            <param name="identifierMethod">getCode</param>
            <param name="valueOfMethod">fromString</param>
        </type>
    </property>

The Status enum

public static enum Status {
    ACTIVE(1, "Active"),
    DELETED(2, "Deleted"),
    INACTIVE(3, "Inactive"),
    PASSWORD_EXPIRED(4, "Password Expired");

    /** Formal representation (single character code). */
    private int code;
    /** Textual, human-readable description. */
    private String description;

    // Needed by Hibernate to map column values to enum values

    public static Status fromString(String code) {
        for (Status status : Status.values()) {
            if (status.getCode().equals(code.toUpperCase())) {
                return status;
            }
        }
        throw new IllegalArgumentException("Unknown user status: " + code);
    }

    Status(int code, String description) {
        this.code = code;
        this.description = description;
    }

    public int getCode() {
        return code;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return getDescription();
    }
}

The general class:

public class GenericEnumUserType implements UserType, ParameterizedType {
    private static final String DEFAULT_IDENTIFIER_METHOD_NAME = "name";
    private static final String DEFAULT_VALUE_OF_METHOD_NAME = "valueOf";

    private Class<? extends Enum> enumClass;
    private Method identifierMethod;
    private Method valueOfMethod;
    private NullableType type;
    private int[] sqlTypes;

    public void setParameterValues(Properties parameters) {
        String enumClassName = parameters.getProperty("enumClass");
        try {
            enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
        } catch (ClassNotFoundException cfne) {
            throw new HibernateException("Enum class not found", cfne);
        }

        String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);
        Class<?> identifierType;

        try {
            identifierMethod = enumClass.getMethod(identifierMethodName);
            identifierType = identifierMethod.getReturnType();
        } catch (Exception e) {
            throw new HibernateException("Failed to obtain identifier method", e);
        }

        type = (NullableType) TypeFactory.basic(identifierType.getName());

        if (type == null)
            throw new HibernateException("Unsupported identifier type " + identifierType.getName());

        sqlTypes = new int[] { type.sqlType() };

        String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);

        try {
            valueOfMethod = enumClass.getMethod(valueOfMethodName, identifierType);
        } catch (Exception e) {
            throw new HibernateException("Failed to obtain valueOf method", e);
        }
    }

    public Class returnedClass() {
        return enumClass;
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
        Object identifier = type.get(rs, names[0]);
        if (rs.wasNull()) {
            return null;
        }

        try {
            return valueOfMethod.invoke(enumClass, identifier);
        } catch (Exception e) {
            throw new HibernateException(
                    "Exception while invoking valueOf method '" + valueOfMethod.getName() + "' of " +
                            "enumeration class '" + enumClass + "'", e);
        }
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        try {
            if (value == null) {
                st.setNull(index, type.sqlType());
            } else {
                Object identifier = identifierMethod.invoke(value);
                type.set(st, identifier, index);
            }
        } catch (Exception e) {
            throw new HibernateException(
                    "Exception while invoking identifierMethod '" + identifierMethod.getName() + "' of " +
                            "enumeration class '" + enumClass + "'", e);
        }
    }

    public int[] sqlTypes() {
        return sqlTypes;
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        return x == y;
    }

    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    public boolean isMutable() {
        return false;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}
HamoriZ
  • 2,370
  • 18
  • 38
0

Assuming you are using MySQL as a database to store your enums, the enum values are stored as numbers not as the strings. So you don't lose space by using an enum vs using an integer FK. See How Does MySQL Store Enums?

Enums are elegant as they are simple to use vs creating another table and then referencing it via an FK. If they refer to application logic it's better to have them as part of the schema/program than as part of the data (i.e. rows in a table). See http://www.databasesandlife.com/mysqls-enum-datatype-is-a-good-thing/

See here for how to use enums with Hibernate. (I wish this was a lot easier, I don't get why Hibernate doesn't support Enums out-of-the-box). http://community.jboss.org/wiki/UserTypeforpersistingaTypesafeEnumerationwithaVARCHARcolumn

Community
  • 1
  • 1
Adrian Smith
  • 17,236
  • 11
  • 71
  • 93
  • Adrian, sorry I didn't explain myself correctly, but I was looking for a way to avoid the usage of Java Enums... BTW I dind't think about using MySQL enums, and seems to be a very good solution! But what you mentioned about Hibernate and Enums doesn't sound very friendly... anyway, thanks for your answer! – Joaquín L. Robles Nov 08 '10 at 13:52
  • Yes, true, Hibernate connecting Java enums to MySQL enums is not nice at all :( – Adrian Smith Nov 08 '10 at 15:03
  • 1
    @Joaquín Personally, I don't recommend MySQL's ENUM with or without Hibernate but [even less when using Hibernate](http://stackoverflow.com/questions/766299/mysql-enum-performance-advantage). See also what [Bill says about them](http://stackoverflow.com/questions/766299/mysql-enum-performance-advantage) from a pure database point of view. – Pascal Thivent Nov 08 '10 at 18:14
  • Yeah, I'm not a great friend of MySQL enums, but mainly because I prefer formal FK relations in contrast of this kind of "multi-valued attributes", I didn't know the performance penalties of using them at all... So, still searching! – Joaquín L. Robles Nov 08 '10 at 18:17
  • The first link of my comment was supposed to point to [Enum in Hibernate, persisting as an enum](http://stackoverflow.com/questions/2160700/enum-in-hibernate-persisting-as-an-enum). I somehow messed with links. – Pascal Thivent Nov 09 '10 at 07:10