Help

Inactive Bloggers
24. Jun 2004, 23:28 CET, by Gavin King

This is /just great/ ...

JDK 1.5 changes the method signature of javax.naming.spi.ObjectFactory.getObjectInstance() from this:

public Object getObjectInstance(Object reference, Name name, Context ctx, Hashtable env)

to this:

public Object getObjectInstance(Object reference, Name name, Context ctx, Hashtable<String, ?> env)

AFAICT, this means that is now impossible to write an ObjectFactory that compiles in both JDK 1.4 and 1.5. Ugh.

24. Jun 2004, 21:12 CET, by Gavin King

Hibernate is great at representing strongly-typed, static object models. Not all applications are like this. Metadata-driven applications define entity type information in the database. Both the object model and the relational model support dynamic addition of new types, and perhaps even redefinition of existing types. Actually, most complex applications contain a mix of both static models and dynamic models.

Suppose that our system allowed supports various types of item, each with specialized attributes. If there were a static, predefined set of item types, we would probably model this using inheritance. But what if new types may be defined dynamically by the user - with the type definitions stored in the database?

We could define an ItemType class representing the definition of an item type. Each ItemType instance would own a collection of ItemTypeAttribute instances, each representing a named attribute that applies to that particular item type. ItemType and ItemTypeAttribute define the /meta-model/.

Item instances would each have a unique ItemType and would own a collection of ItemAttributeValue instances, representing concrete values of the applicable ItemTypeAttributes.

http://hibernate.sourceforge.net/metadata.gif

The mappings for the metamodel classes is quite straightforward. The only really interesting thing is that ItemType and ItemTypeAttribute are perfect examples of classes that should have second-level caching enabled: updates are infrequent, there are relatively few instances, instances are shared between many users and many instances of the Item class.

The mappings for ItemType and ItemTypeAttribute might look like this:

<class name="ItemType">
    <cache usage="nonstrict-read-write"/>
    <id name="id">
        <generator class="native"/>
     </id>
    <property name="name" 
            not-null="true" 
            length="20"/>
    <property name="description" 
            not-null="true" 
            length="100"/>
    <set name="attributes" 
            lazy="true" 
            inverse="true">
        <key column="itemType"/>
        <one-to-many class="ItemTypeAttribute"/>
    </set>
</class>

<class name="ItemTypeAttribute">
    <cache usage="nonstrict-read-write"/>
    <id name="id">
        <generator class="native"/>
    </id>
    <property name="name" 
             not-null="true" 
             length="20"/>
    <property name="description" 
             not-null="true" 
             length="100"/>
    <property name="type" 
             type="AttributeTypeUserType" 
             not-null="true"/>
    <many-to-one name="itemType" 
             class="ItemType" 
             not-null="true"/>
</class>

We do not enable proxies for these classes, since we expect that instances will always be cached. We'll leave the definition of the custom type AttributeTypeUserType to the you!

The mappings for Item and ItemAttributeValue are also straightforward:

<class name="Item" 
        lazy="true">
    <id name="id">
        <generator class="native"/>
    </id>
    <many-to-one name="type" 
        class="ItemType" 
        not-null="true" 
        outer-join="false"/>
    <set name="attributeValues" 
            lazy="true" 
            inverse="true">
        <key column="item"/>
        <one-to-many class="Item"/>
    </set>
</class>

<class name="ItemAttributeValue" 
        lazy="true">
    <id name="id">
        <generator class="native"/>
    </id>
    <many-to-one name="item" 
        class="Item" 
        not-null="true"/>
    <many-to-one name="type" 
        class="ItemTypeAttribute" 
        not-null="true" 
        outer-join="false"/>
    <property name="value" type="AttributeValueUserType">
        <column name="intValue"/>
        <column name="floatValue"/>
        <column name="datetimeValue"/>
        <column name="stringValue"/>
    </property>
</class>

Notice that we must explicitly set outer-join="false" to prevent Hibernate from outer join fetching the associated objects which we expect to find in the cache.

Finally, we need to define the custom type AttributeValueUserType, that takes the value of an ItemAttributeValue and stores it in the correct database column for it's type.

public class AttributeValueUserType implements UserType {

    public int[] sqlTypes() {
        return new int[] { Types.BIGINT, Types.DOUBLE, Types.TIMESTAMP, Types.VARCHAR };
    }
    
    public Class returnedClass() { return Object.class; }
    
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) 
        throws HibernateException, SQLException {
        
        Long intValue = (Long) Hibernate.LONG.nullSafeGet(rs, names[0], owner);
        if (intValue!=null) return intValue;
        
        Double floatValue = (Double) Hibernate.DOUBLE.nullSafeGet(rs, names[1], owner);
        if (floatValue!=null) return floatValue;
        
        Date datetimeValue = (Date) Hibernate.TIMESTAMP.nullSafeGet(rs, names[2], owner);
        if (datetimeValue!=null) return datetimeValue;
        
        String stringValue = (String) Hibernate.STRING.nullSafeGet(rs, names[3], owner);
        return stringValue;
        
    }
    
    public void nullSafeSet(PreparedStatement st, Object value, int index) 
        throws HibernateException, SQLException {
        
        Hibernate.LONG.nullSafeSet( st, (value instanceof Long) ? value : null, index );
        Hibernate.DOUBLE.nullSafeSet( st, (value instanceof Double) ? value : null, index+1 );
        Hibernate.TIMESTAMP.nullSafeSet( st, (value instanceof Date) ? value : null, index+2 );
        Hibernate.STRING.nullSafeSet( st, (value instanceof String) ? value : null, index+3 );
    }
    
    public boolean equals(Object x, Object y) throws HibernateException {
        return x==null ? y==null : x.equals(y);
    }
    
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }
    
    public boolean isMutable() {
        return false;
    }
    
}

Thats it!

UPDATE: I don't know what I was thinking! Of course, we need to be able to query the attributes of our items, so AttributeValueUserType should be a /composite/ custom type!

public interface CompositeUserType {
    
    public String[] getPropertyNames() {
        return new String[] { "intValue", "floatValue", "stringValue", "datetimeValue" };
    }
    
    public Type[] getPropertyTypes() {
        return new Type[] { Hibernate.LONG, Hibernate.DOUBLE, Hibernate.STRING, Hibernate.TIMESTAMP };
    }
            
    public Object getPropertyValue(Object component, int property) throws HibernateException {
        switch (property) {
            case 0: return (component instanceof Long) ? component : null;
            case 1: return (component instanceof Double) ? component : null;
            case 2: return (component instanceof String) ? component : null;
            case 3: return (component instanceof Date) ? component : null;
        }
        throw new IllegalArgumentException();
    }
    
    public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
        throw new UnsupportedOperationException();
    }
    
    public Class returnedClass() {
        return Object.class;
    }
    
    public boolean equals(Object x, Object y) throws HibernateException {
        return x==null ? y==null : x.equals(y);
    }
    
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) 
        throws HibernateException, SQLException {
        
        //as above!
    }
    
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) 
        throws HibernateException, SQLException {
        
        //as above!
    }
    
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }
    
    public boolean isMutable() {
        return false;
    }
    
    public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
        return value;
    }
    
    public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
        return value;
    }
}

Now we can write queries like this one:

from Item i join i.attributeValues value where value.name = 'foo' and value.inValue = 69
24. Jun 2004, 06:23 CET, by Christian Bauer

I've seen three or four ORM tool comparisons in the last three weeks; on some weblogs, on our forum and I've even been part in several decisions.

I have the impression that many developers have problems categorizing and evaluating ORM tools, no matter if its Hibernate, Cayenne, PrIdE (I hope that spelling is correct), or some home-made JDBC framework. I got really frustrated at some point, but what brings me to this blog entry is probably a posting made today, by Scott Ferguson. He compares EJB CMP, JDO, and Hibernate. I wasn't really happy with his list of points. Don't get me wrong, I'm not complaining about Scott's conclusions (our precious Hibernate!), in fact, I actually usually listen to Scott. I've even followed Resins development closely several years ago, nearly got it approved for a medium-sized installation (politics...), and even reported and fixed some bugs.

So, this entry, after a long introduction, is about comparing ORM solutions. What all the reviews and articles had in common was a very very obscure criteria schema. In one article, I've seen someone comparing loading and saving a single object and looking at the lines of code that you need for this operation. Next, we hear something like my ORM should work with objects or other vague statements that, in practice, probably not help you decide what you should use.

I did my research for Hibernate in Action, and I think we have found an excellent taxonomy for ORM solutions. Actually, Mark Fussel started to use these categories in 1997, we merely rewrote his list and set it in context to Java application development:

Pure relational

The whole application, including the user interface, is designed around the relational model and SQL-based relational operations. Direct SQL can be fine-tuned in every aspect, but the drawbacks, such as difficult maintenance, lack of portability, and maintainability, are significant, especially in the long run. Applications in this category often make heavy use of stored procedures, shifting some of the work out of the business layer and into the database.

Light object mapping

Entities are represented as classes that are mapped manually to the relational tables. Hand-coded SQL/JDBC is hidden from the business logic using well-known design patterns (such as DAO). This approach is extremely widespread and is successful for applications with a small number of entities, or applications with generic, metadata-driven data models. Stored procedures might have a place in this kind of application.

Medium object mapping

The application is designed around an object model. SQL is generated at build time using a code generation tool, or at runtime by framework code. Associations between objects are supported by the persistence mechanism, and queries may be specified using an object-oriented expression language. Objects are cached by the persistence layer. A great many ORM products and homegrown persistence layers support at least this level of functionality. It's well suited to medium-sized applications with some complex transactions, particularly when portability between different database products is important. These applications usually don't use stored procedures.

Full object mapping

Full object mapping supports sophisticated object modeling: composition, inheritance, polymorphism, and persistence by reachability or a more flexible transitive persistence solution. The persistence layer implements transparent persistence; persistent classes do not inherit any special base class or have to implement a special interface. The persistence layer does not enforce a particular programming model for the domain model implementation. Efficient fetching strategies (lazy and eager fetching) and caching strategies are implemented transparently to the application. This level of functionality can hardly be achieved by a homegrown persistence layer - it's equivalent to months or years of development time.

In my experience, it is quite easy to find the category for a given product. In Hibernate in Action, we also have a list of interesting questions that you should ask if you compare ORM tools:

  • What do persistent classes look like? Are they fine-grained JavaBeans?
  • How is mapping metadata defined?
  • How should we map class inheritance hierarchies?
  • How does the persistence logic interact at runtime with the objects of the business domain?
  • What is the lifecycle of a persistent object?
  • What facilities are provided for sorting, searching, and aggregating?
  • How do we efficiently retrieve data with associations?

In addition, two issues are common to any data-access technology. They also impose fundamental constraints on the design and architecture of an ORM:

  • Transactions and concurrency
  • Cache management (and concurrency)

Find the answers to those questions, and you can compare ORM software. Scott in fact started right with the lifecycle, but he has not given enough information in his article for a real discussion, it's mostly his opinion (which is fine on a weblog).

There are, as always in life, many solutions and not a single product, project, or specification will be perfect in all scenarios. You don't have to try to get to the top of the list and always use Full object mapping (and the appropriate tool). There are very good reasons to use a Light object mapping tool (iBatis, for example) in some situations! In many situations, JDBC and SQL are the best choice. I'm talking about a comparison at the same level, and I've made good experience with the categories and questions I've shown above. Read the book. :)

Thanks for listening