7.1 Defining a User Type

     

7.1 Defining a User Type

In all of these scenarios, the task is to teach Hibernate a new way to translate between a particular kind of in-memory value and its persistent database representation.

Hibernate lets you provide your own logic for mapping values in situations that need it, by implementing one of two interfaces: net.sf.hibernate.UserType or net.sf.hibernate.CompositeUserType .

It's important to realize that what is being created is a translator for a particular kind of value, not a new kind of value that knows how to persist itself. In other words, in our ZIP code example, it's not the ZIP code property that would implement UserType . Instead, we'd create a new class implementing UserType , and in our mapping document specify this class as the Java type used to map the ZIP code property. Because of this, I think the terminology of 'user types' is a little confusing.

Let's look at a concrete example. In Chapter 6 we saw how to use Hibernate's built-in enumeration support to persist a typesafe enumeration to an integer column, and we had to work around the fact that many object-oriented enumerations have no natural integer representation. While we can hope that Java 1.5 will allow Hibernate to resolve this tension in a universal way, we don't have to wait for that to happen, nor do we necessarily have to make the kind of compromises we did in Example 6-2. We can define our own custom value type that persists the SourceMedia class on its own terms. Later in the chapter we'll look at a more complex example involving multiple properties and columns .

7.1.1 How do I do that?

We'll work with the verson of SourceMedia.java shown in Example 6-1. Our custom type will allow this class to be persisted without any changes from its original form. In other words, the design of our data classes can be dictated by the needs and semantics of the application alone, and we can move the persistence support into a separate class focused on that sole purpose. This is a much better division of labor.

We'll call our new class SourceMediaType . Our next decision is whether it needs to implement UserType or CompositeUserType . The reference documentation doesn't provide much guidance on this question, but the API documentation confirms the hint contained in the interface names : the CompositeUserType interface is only needed if your custom type implementation is to expose internal structure in the form of named properties that can be accessed individually in queries (as in our ZIP code example). For SourceMedia , a simple UserType implementation is sufficient. The source for a mapping manager meeting our needs is shown in Example 7-1.

Example 7-1. SourceMediaType.java, our custom type mapping handler
 package com.oreilly.hh; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import net.sf.hibernate.UserType; import net.sf.hibernate.Hibernate; import net.sf.hibernate.HibernateException; import net.sf.hibernate.type.Type; /**  * Manages persistence for the {@link SourceMedia} typesafe enumeration.  */ public class SourceMediaType implements UserType {   /**     * Indicates whether objects managed by this type are mutable.     *     * @return <code>false</code>, since enumeration instances are immutable     * singletons.     */    public boolean isMutable() {        return false;     }         /**     * Return a deep copy of the persistent state, stopping at     * entities and collections.     *     * @param value the object whose state is to be copied.     * @return the same object, since enumeration instances are singletons.     * @throws ClassCastException for non {@link SourceMedia} values.     */          public Object deepCopy(Object value) {         return (SourceMedia)value;    }    /**     * Compare two instances of the class mapped by this type for persistence     * "equality".     *     * @param x first object to be compared.     * @param y second object to be compared.     * @return <code>true</code> iff both represent the same SourceMedia type.     * @throws ClassCastException if x or y isn't a {@link SourceMedia}.     */    public boolean equals(Object x, Object y) {       // We can compare instances, since SourceMedia are immutable singletons       return (x == y);    }      /**     * Determine the class that is returned by {@link #nullSafeGet}.     *     * @return {@link SourceMedia}, the actual type returned     * by {@link #nullSafeGet}.     */   public Class returnedClass() {        return SourceMedia.class;    }    /**     * Determine the SQL type(s) of the column(s) used by this type mapping.     *     * @return a single VARCHAR column.     */     public int[] sqlTypes() {        // Allocate a new array each time to protect against callers changing        // its contents.        int[] typeList = {           Types.VARCHAR        };        return typeList;     }          /**      * Retrieve an instance of the mapped class from a JDBC {@link ResultSet}.      *      * @param rs the results from which the instance should be retrieved.      * @param names the columns from which the instance should be retrieved.      * @param owner the entity containing the value being retrieved.      * @return the retrieved {@link SourceMedia} value, or <code>null</code>.      * @throws HibernateException if there is a problem performing the mapping.      * @throws SQLException if there is a problem accessing the database.      */          public Object nullSafeGet(ResultSet rs, String[] names, Object owner)        throws HibernateException, SQLException    {      // Start by looking up the value name      String name = (String) Hibernate.STRING.nullSafeGet(rs, names[0]);      if (name == null) {          return null;      }      // Then find the corresponding enumeration value      try {          return SourceMedia.getInstanceByName(name);      }      catch (java.util.NoSuchElementException e) {         throw new HibernateException("Bad SourceMedia value: " + name, e);    }   }   /**    * Write an instance of the mapped class to a {@link PreparedStatement},    * handling null values.    *    * @param st a JDBC prepared statement.    * @param value the SourceMedia value to write.    * @param index the parameter index within the prepared statement at which    *      this value is to be written.    * @throws HibernateException if there is a problem performing the mapping.    * @throws SQLException if there is a problem accessing the database.    */   public void nullSafeSet(PreparedStatement st, Object value, int index)       throws HibernateException, SQLException   {     String name = null;     if (value != null)           name = ((SourceMedia)value).getName();     Hibernate.STRING.nullSafeSet(st, name, index);   } } 

All of the methods in this class are required by the UserType interface. Our implementations are quite brief and straightforward, as befits the simple mapping we've undertaken. The first three methods don't need any discussion beyond what's in the JavaDoc and inline comments.

The sqlTypes() method reports to Hibernate the number of columns that will be needed to store values managed by this custom type and the SQL types. We indicate that our type uses a single VARCHAR column.

Since the API specifies that this information is to be returned as an array, safe coding practices dictate that we create and return a new array on each call, to protect against malicious or buggy code that might manipulate the contents of the array. (Java has no support for immutable arrays. It would have been slightly preferable if the UserType interface declared this method to return a Collection or List , since these can be immutable.)


In nullSafeGet() we translate database results into the corresponding MediaSource enumeration value. Since we know we stored the value as a string in the database, we can delegate the actual retrieval to Hibernate's utility method for loading strings from database results. You'll be able to do something like this in most cases. Then it's just a matter of using the enumeration's own instance lookup capability.

Mapping the other direction is handled by nullSafeSet() . Once again we can rely on built-in features of the enumeration to translate from a MediaSource instance to its name, and then use Hibernate's utilities to store this string in the database.

In all the methods dealing with values, it's important to write your code in a way that will not crash if any of the arguments are null , as they often will be. The ' nullSafe ' prefix in some method names is a reminder of this, but even the equals() method must be careful. Blindly delegating to x.equals(y) would blow up if x is null .




Hibernate. A Developer's Notebook
Hibernate: A Developers Notebook
ISBN: 0596006969
EAN: 2147483647
Year: 2003
Pages: 65
Authors: James Elliott

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net