views:

293

answers:

3

Hi, Please best practice and 'how to' for using enum with jpa as a data member of persisted entity. what is the best practice? I want to persist "C", "O" from enum. (codes). If this is not the correct approach please suggest.

Enum defination is --

public enum Status{
CLOSED ("C")
OPEN ("O")
private final int value;
private Status(final int pValue){
this.value = pValue;
}

public int value(){
 return this.value;
}
A: 

We persist enums as strings. Use @Enumerated(EnumType.STRING) to use the string representation (instead of the automatic enum code). That makes your data in the DB much more readable.

If you really need to map the enums to special codes (legacy code and the like), you'll need a custom mapping. First a base class which maps enums to the DB and back:

import java.io.Serializable;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;


public abstract class CustomEnumType implements UserType
{
    public Object deepCopy (Object value) throws HibernateException
    {
        return value;
    }

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

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

    public boolean equals (Object x, Object y) throws HibernateException
    {
        // Correct for Enums
        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;
    }

    public int[] sqlTypes ()
    {
        return new int[]{ Hibernate.STRING.sqlType() };
    }

}

Now an extension which uses DBEnum to map values for your enums to the DB and back:

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.apache.log4j.Logger;

public abstract class DBEnumType extends CustomEnumType
{
    private final static Logger log = Logger.getLogger(DBEnumType.class);
    private static final boolean IS_VALUE_TRACING_ENABLED = log.isTraceEnabled();

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

        return toEnum (value);
    }

    public abstract Object toEnum (String value);

    public void nullSafeSet (PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException
    {
        if (value == null)
            st.setNull (index, Hibernate.STRING.sqlType ());
        else
        {
            DBEnum dbEnum = (DBEnum)value;
            value = dbEnum.getDBCode();
            st.setString (index, dbEnum.getDBCode());
        }

        if (IS_VALUE_TRACING_ENABLED)
        {
            log.trace (getClass().getName()+" "+value);
        }
    }
}

The interface:

public interface DBEnum
{
    public String getDBCode ();
}

Lastly, you must extend DBEnumType for each enum type you want to map:

public class DBEnumCardType extends DBEnumType
{
    public Class returnedClass ()
    {
        return Status.class;
    }

    public Object toEnum (String value)
    {
        return Status.fromDBCode (value);
    }

}

In Status, you must implement the static method which maps DB codes to enums:

    private final static Map<String, Status> dbCode2Enum = new HashMap<String, Status> ();
    static {
        for (Status enm: Status.values ())
        {
            String key = enm.getDBCode ();
            if (dbCode2Enum.containsKey (key))
                throw new ShouldNotHappenException ("Duplicate key "+key+" in "+enm.getClass ());
            dbCode2Enum.put (key, enm);
        }
    }

    private String dbCode;

    private Status (String dbCode)
    {
        this.dbCode = dbCode;
    }

    public String getDBCode ()
    {
        return dbCode;
    }

    public static Status fromDBCode (String dbCode)
    {
        if (dbCode == null)
            return null;

        Status result = dbCode2Enum.get (dbCode);
        if (result == null)
            throw new ShouldNotHappenException ("Can't find key "+dbCode+" in "+Status.class);

        return result;
    }

Lastly, you must use the annotation @org.hibernate.annotations.Type() to tell Hibernate to use the custom mapping.

Conclusion: Don't use custom codes. They just generate a lot of stupid boiler plate code which you can't factor out.

Aaron Digulla
below blog discusses on same issue with EclipseLink...http://blog.xebia.com/2009/08/28/using-enums-with-jpa-without-the-evil-ordinal/Thanks Aaron.
Digambar Daund
This all assumes Hibernate is being used, of course, but that's not stated in the question.
skaffman
http://stackoverflow.com/questions/352586/how-to-use-enums-with-jpa
Digambar Daund
A: 

Enums are by default supported by JPA but problem is they use the ordinals of the values by default which you cannot control. To solve this you could use a little logic on your getter setters.

@Column(name = "KIRSAL_METROPOL")
private String someEnum;
public YourEnum getSomeEnum() {
    return EnumUtils.getEnum(YourEnum.class, this.someEnum);
}

public void setSomeEnum(YourEnum someEnum) {
    this. someEnum = EnumUtils.getValue(someEnum);
}

EnumUtils should do the conversion...

MCA
JPA can persist either the ordinals *or* the enum names.
skaffman
+1  A: 

expected Solution: enum defination: public enum Status { CLOSED(1), NEW(2), RUNNING(3), OPEN(4), ADDED(5), SUCEESS(-1), DONE(0);

private int code;

private Status(int code) {
    this.code = code;
}

public int getCode() {
    return code;
}

public void setCode(int code) {
    this.code = code;
}

public static Status valueOf(int i){
    for (Status s : values()){
        if (s.code == i){
            return s;
        }
    }
    throw new IllegalArgumentException("No matching constant for " + i);
}

}

Entity Definition: @Entity @NamedQuery(name="Process.findAll", query="select p from Process p ") public class Process {

@Id
private long id;
private String name;

@Transient
private transient Status status; //actual enum; not stored in db

@Column(name="STATUS")  
private int statusCode; // enum code gets stored in db

@PrePersist
void populateDBFields(){
    statusCode = status.getCode();
}

@PostLoad
void populateTransientFields(){
    status = Status.valueOf(statusCode);
}
public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Status getStatus() {
    return status;
}

public void setStatus(Status status) {
    this.status = status;
}
Digambar Daund