-->
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.  [ 6 posts ] 
Author Message
 Post subject: Enum clarifications and doc lapses for Annotations Beta 8
PostPosted: Wed Jan 25, 2006 2:38 pm 
Regular
Regular

Joined: Wed Jun 30, 2004 4:02 pm
Posts: 64
The annotations docs do not list @Email in the table in the validator section.

I am trying to map my Java 5 enums, not by ordinal or String, but to the lookup table's ids that have existed for years.

I extended org.hibernate.type.EnumType, and have it working for one case.

The docs about usertypes (2.4.3.3. Type) state "These annotations are placed at the class or package level. Note that these definitions will be global for the session factory (even at the class level) and that type definition has to be defined before any usage."

The hibernate docs (5.2.3. Custom value types) do not really expand on this. If these defs are global, and I use the same enum with several model objects, should I duplicate the @TypeDef in all of those model objects? Should I pick one at random to make the @TypeDef on?

I really don't see ordinal or string mappings sufficing for the majority of applications going against preexisting database schemas. I think there is a need for out of the box support for mapping an Enum to an arbitrary set of id's as contained in the database. Yes, everyone can extend EnumType and get it working on their own, but it's a common enough problem to include in the framework.


Top
 Profile  
 
 Post subject: Re: Enum clarifications and doc lapses for Annotations Beta
PostPosted: Wed Jan 25, 2006 3:42 pm 
Newbie

Joined: Tue Jan 03, 2006 7:30 am
Posts: 18
Location: Budapest, Hungary
tedberg wrote:
The docs about usertypes (2.4.3.3. Type) state "These annotations are placed at the class or package level. Note that these definitions will be global for the session factory (even at the class level) and that type definition has to be defined before any usage."

The hibernate docs (5.2.3. Custom value types) do not really expand on this. If these defs are global, and I use the same enum with several model objects, should I duplicate the @TypeDef in all of those model objects? Should I pick one at random to make the @TypeDef on?


You may pick one -- that's using the annotation at "class level". Or you may put it in a package-info.java file in some package, which holds annotations for that package -- this would be the "package level" option. (See http://java.sun.com/docs/books/jls/third_edition/html/packages.html#7.4.1.1. Sun's JDK follows that recommendation.)

Duplicating the definition would result in some error message I think... and it's ugly as hell. Don't do that.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 25, 2006 4:05 pm 
Regular
Regular

Joined: Wed Jun 30, 2004 4:02 pm
Posts: 64
I tried defining the @TypeDef once at class level and using it in two classes in the same package. That does not work. I then duplicated the @TypeDef in the second class. That does work, but yes is ugly.

I will move that to the package level, but there are cases of an enum being used across packages. Is the answer to duplicate the @TypeDef in those 2 packages where the enum is used?

If we are defining the @TypeDef more than once, what does the comment about it being global within the session factory mean? Is it just that, while it is defined twice, the second declaration is ignored when it comes to the session factory?

Incidentally, this is working well on all of my concrete classes. In the few places where I wanted to do this on an abstract class, it fails.

http://forum.hibernate.org/viewtopic.php?t=941175&highlight=determine+type+columns is the closest match I can find in the forums.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 25, 2006 8:52 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
http://opensource2.atlassian.com/projects/hibernate/browse/ANN-233

you want a generic way to map arbitrary ids to an arbitreary enum type. Can you do that through a user type, wo too puch parameter to inject? If yes I'll introduce it in the code base, if not then that's not possible.

putting them at the package level does guaranty that the type will be available for all entities. Not only package related ones.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 25, 2006 9:23 pm 
Regular
Regular

Joined: Wed Jun 30, 2004 4:02 pm
Posts: 64
This thread relates to Jira 231: http://opensource2.atlassian.com/projects/hibernate/browse/ANN-231

Yes, we need a good way to map java 5 Enums to a lookup table in the database, using whatever integer primary keys that may exist.

I do have something working now that solves this problem in my application. The ugliness and burden of knowing the values of the primary keys fell on my Enum classes. (It was worse than I'd hoped because I cannot create an abstract BaseEnum object. Perhaps in Java 6 I will be able to do that.)

First I created an interface that all database backed enums need to implement.

Code:
public interface PersistableEnum {
    int getDatabaseId();

    PersistableEnum getEnumFromDatabaseId(int databaseId);
}


The second method really could be static, but then I couldn't add it to my interface.

The Enum looks like:

Code:
public enum AgentStatus implements PersistableEnum, Selectable {

    PENDING_SIGNATURE(-2),  // Pending agent signature.  The default status of all agents.
    PENDING(-1),            // Pending activation.
    ENABLED(1),             // TThe normal status of an agent that recieves commissions.
    DISABLED(2),            // The status of an agent with no new customer aquisitions for 3 months.  No commission is paid.
    DELETED(3),             // The status of a deleted agent.  A deleted agent's customers are transferred to the IN-HOUSE agent.
    REJECTED(4);            // The status of a rejected or black-listed agent.

    private int databaseId;

    AgentStatus(int databaseId) {
        this.databaseId = databaseId;
    }

    public int getDatabaseId() {
        return databaseId;
    }

    public PersistableEnum getEnumFromDatabaseId(int databaseId) {
        for (PersistableEnum p : AgentStatus.values()) {
            if (p.getDatabaseId() == databaseId) {
                return p;
            }
        }
        throw new IllegalArgumentException("Database ID(" + databaseId + ")");
    }

    }
}


My Agent model object starts with:

Code:
@TypeDefs({
@TypeDef(
        name = "agentType",
        typeClass = EnumUserType.class,
        parameters = {@Parameter(name = "enumClass", value = "com.viz.web.model.AgentType")}
),
@TypeDef(
        name = "agentStatus",
        typeClass = EnumUserType.class,
        parameters = {@Parameter(name = "enumClass", value = "com.viz.web.model.AgentStatus")}
)
        })

@Entity
@Table(name = "agent")
@SequenceGenerator(
        name = "agent_seq",
        sequenceName = "agent_seq"
)


And defines the property as:

Code:
@NotNull
    @Type(type = "agentStatus")
    @Column(name = "status_id")
    public AgentStatus getStatus() {
        return status;
    }

    public void setStatus(AgentStatus status) {
        this.status = status;
    }


EnumUserType is: (Just threw this together. Probably needs some cleanup)


Code:
public class EnumUserType extends EnumType {
    private static Log log = LogFactory.getLog(EnumUserType.class);

    private static Map<Class,Object[]> enumValues = new HashMap<Class, Object[]>();
    private int sqlType = Types.INTEGER; //before any guessing

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {

        Class<? extends PersistableEnum> enumClass = super.returnedClass();
        Object object = rs.getObject(names[0]);
        if (rs.wasNull()) {
            log.debug("Returning null as column " + names[0]);
            return null;
        }

        if (object instanceof Number) {
            Object[] values = enumValues.get(enumClass);
            if (values == null) {
                try {
                    Method method = null;
                    method = enumClass.getDeclaredMethod("values", new Class[0]);
                    values = (Object[]) method.invoke(null, new Object[0]);
                    enumValues.put(enumClass, values);
                }
                catch (Exception e) {
                    throw new HibernateException("Error while accessing enum.values(): " + enumClass, e);
                }
            }
            int databaseId = ((Number) object).intValue();
            PersistableEnum p = (PersistableEnum) values[0];
            PersistableEnum myEnum = p.getEnumFromDatabaseId(databaseId);

            log.debug("Returning '" + databaseId + "' as column " + names[0]);
            return myEnum;
        } else {
            throw new IllegalArgumentException("Data type returned is not a number. (EnumUserType");
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        //if (!guessed) guessType( st, index );
        if (value == null) {
            log.debug("Binding null to parameter: " + index);
            st.setNull(index, sqlType);
        } else {
            //boolean isOrdinal = isOrdinal(sqlType);
            boolean isOrdinal = true; // We're always using a numeric mapping.
            if (isOrdinal) {
                //int ordinal = ((Enum) value).ordinal();
                int ordinal = ((PersistableEnum) value).getDatabaseId();
                log.debug("Binding '" + ordinal + "' to parameter: " + index);
                st.setObject(index, new Integer(ordinal), sqlType);
            } else {
                String enumString = ((Enum) value).name();
                log.debug("Binding '" + enumString + "' to parameter: " + index);
                st.setObject(index, enumString, sqlType);
            }
        }
    }


}



So, what I have works great. Alas, it is not pretty, and violates the idea of having a seperation of concerns.

My hope is someone will know how to achieve the same effect with a more elegant solution. Perhaps a seperate mapping of db values to enum values, stored in properties or an xml file?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 26, 2006 5:19 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
As told on the bug report, I'm -1 on this one.

_________________
Emmanuel


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

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.