-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Custom EnumUserType using lookup table
PostPosted: Tue Jan 16, 2007 4:04 pm 
Newbie

Joined: Wed Sep 06, 2006 7:32 am
Posts: 6
Need help with Hibernate? Read this first:
http://www.hibernate.org/ForumMailingli ... AskForHelp

Hibernate version: 3.2.0.ga

I know that it seems mapping Java 5 enums in hibernate has been done to death here and elsewhere, but after many hours of searching (both of the web and soul variety), I still haven't found any existing code which does what I want. I've written something myself, but it is somewhat unorthodox, so I'm looking for feedback on what I've done, and how it could be done better.

Imagine I have 2 tables (simplified to keep the posting short):

Code:
Car
===
model VARCHAR
colourID INT

Colour
======
colourID INT
colourName VARCHAR


and I want to model this as:

Code:
public class Car {
    private String model;
    private Colour colour;

    public String getModel() {return model;}
    public void setModel(String model) {this.model = model;}
    public Colour getColour() {return colour;}
    public void setColour(Colour colour) {this.colour = colour;}
}

public enum Colour { RED, BLUE, GREEN }


Now let me list the things I don't want to (or can't) do:

1. Map colourID to enums using ordinals - ours is a global team, and schema changes are not always communicated widely enough. If someone decides to set colourID = 2 where colourName = RED and colourID = 1 where colourName = BLUE (assuming they also update the relevant Car.colourIDs), then they should be free to do this, and the java code should not start silently returning the wrong values.

2. Change the schema so that Car.colourID is replaced by Car.colourName - this would effect a large number of other processes already using the same schema.

3. Map Colour as an entity - client code should be able to call myCar.setColour(Colour.BLUE), and Car.colourID should change, not the contents of the Colour table.

Right - preamble over, here's my code. To precis, it creates a temporary session the first time a particular enum is referenced, loads the enum table (eg. Colour) into a map, and then uses this to map between integers and enums. It all seems to work fine, but that's often only half the battle.

Code:
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.hibernate.Hibernate;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;

import com.msdw.fxoption.javacommon.persistence.MappingException;

/**
* @author brettand
*/
public class EnumUserType implements CompositeUserType, ParameterizedType {

    private static class Props {
        public static final String CLASS = "enumClass";
        public static final String TABLE = "enumTable";
        public static final String ID_COLUMN = "idColumn";
        public static final String ENUM_COLUMN = "enumColumn";
    }

    //TODO: Tidy up generics
    @SuppressWarnings("unchecked")
    private Class<? extends Enum> enumClass;
    private String enumTable;
    private String idColumn;
    private String enumColumn;

    private String sqlString;

    @SuppressWarnings("unchecked")
    private static final Map<Class<? extends Enum>, BidiMap> MAPPINGS = new HashMap<Class<? extends Enum>, BidiMap>();

    public void setParameterValues(final Properties parameters) {
        try {
            enumClass = Class.forName(parameters.getProperty(Props.CLASS)).asSubclass(Enum.class);
            enumTable = parameters.getProperty(Props.TABLE);
            idColumn = parameters.getProperty(Props.ID_COLUMN);
            enumColumn = parameters.getProperty(Props.ENUM_COLUMN);

            sqlString = "SELECT " + idColumn + ", " + enumColumn + " FROM " + enumTable;
        } catch (final ClassNotFoundException e) {
            throw new MappingException("Failed to configure EnumUserType: " + e, e);
        }
    }

    /**
     * @see org.hibernate.usertype.CompositeUserType#sqlTypes()
     */
    public int[] sqlTypes() {
        return new int[] {Types.INTEGER};
    }

    /**
     * @see org.hibernate.usertype.CompositeUserType#returnedClass()
     */
    public Class<?> returnedClass() {
        return enumClass;
    }

    /**
     * @see org.hibernate.usertype.CompositeUserType#equals(java.lang.Object, java.lang.Object)
     */
    public boolean equals(final Object x, final Object y) {
        return (x == y) || (x != null && y != null && (x.equals(y)));
    }


    private BidiMap getMap(final SessionImplementor session) {
        synchronized (MAPPINGS) {
            BidiMap thisEnumMapping = MAPPINGS.get(enumClass);
            if (thisEnumMapping == null) {
                if (session == null) {
                    throw new NullPointerException();
                }

                thisEnumMapping = new DualHashBidiMap();

                final Session tempSession = session.getFactory().openTemporarySession();
                final SQLQuery query = tempSession.createSQLQuery(sqlString);
                @SuppressWarnings("unchecked")
                final List<Object[]> results = query.list();
                for (final Object[] row : results) {
                    @SuppressWarnings("unchecked")
                    final Enum<?> enumValue = Enum.valueOf(enumClass, (String)row[1]);
                    if (enumValue == null) {
                        throw new ObjectNotFoundException((Serializable)row[1], enumClass.getName());
                    }
                    thisEnumMapping.put(row[0], enumValue);
                }
                MAPPINGS.put(enumClass, thisEnumMapping);
            }
            return thisEnumMapping;
        }
    }

    @SuppressWarnings("unchecked")
    public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session, final Object owner)
    throws SQLException {
        final Integer enumID = rs.getInt(names[0]);
        if (enumID == null) {
            return null;
        }

        final BidiMap thisEnumMapping = getMap(session);

        final Enum<?> enumValue = (Enum<?>)thisEnumMapping.get(enumID);
        if (enumValue == null) {
            throw new ObjectNotFoundException(enumID, enumClass.getName());
        }
        return enumValue;
    }

    public void nullSafeSet(final PreparedStatement st, final Object value, final int index, final SessionImplementor session)
    throws SQLException {
        if (value == null) {
            st.setObject(index, null);
        }

        final BidiMap thisEnumMapping = getMap(session);

        final Integer enumID = (Integer)thisEnumMapping.getKey(value);
        if (enumID == null) {
            throw new ObjectNotFoundException((Serializable)value, enumClass.getName());
        }
        st.setObject(index, enumID);
    }

    /**
     * @see org.hibernate.usertype.CompositeUserType#deepCopy(java.lang.Object)
     */
    public Object deepCopy(final Object o) {
        return o;
    }

    /**
     * @see org.hibernate.usertype.CompositeUserType#isMutable()
     */
    public boolean isMutable() {
        return false;
    }

    public Object assemble(final Serializable cached, final SessionImplementor session, final Object owner) {
        return cached;
    }

    public Serializable disassemble(final Object value, final SessionImplementor session) {
        return (Serializable)value;
    }


    public Object replace(final Object original, final Object target, final SessionImplementor session, final Object owner) {
        return original;
    }


    /**
     * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
     */
    public int hashCode(final Object x) {
        return x.hashCode();
    }



    public String[] getPropertyNames() {
        return new String[] {enumColumn};
    }

    public Type[] getPropertyTypes() {
        return new Type[] {Hibernate.INTEGER};
    }

    public Object getPropertyValue(final Object component, final int property) {
        final BidiMap thisEnumMapping = getMap(null);

        final Integer enumID = (Integer)thisEnumMapping.getKey(component);
        if (enumID == null) {
            throw new ObjectNotFoundException((Serializable)component, enumClass.getName());
        }
        return enumID;
    }


    public void setPropertyValue(final Object component, final int property, final Object value) {
        throw new MappingException(enumClass + "#" + component + " is immutable");
    }
}


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.