MathJax

SyntaxHighlighter

Highlight

Custom CSS

Tuesday, April 18, 2006

Hibernate: Wrapper classes and unsaved-values

It's a good idea for your domain model objects in Java to make use of wrapper classes such as Integer and Double instead of using regular old int or double. With Java 5.0's auto boxing and unboxing features, you can use wrapper classes pretty much the same way you could use primitives.

So what's the big deal about using wrapper classes in your domain model objects? Consider the following code:
// Message.java
public class Message {
    private int id;
    private MessageType type;
    // Getters and setters omitted
}

// MessageType.java
public class MessageType {
    private int id;
    private String value;
    // Getters and setters omitted
}

Your database schema:

create table Message (
    id int(11) not null auto_increment,
    type_id int(11) not null,
    primary key(id)
)

create table MessageType (
    id int(11) not null auto_increment,
    value varchar(255) not null,
    primary key(id)
)

insert into MessageType values (0, 'Important Message');

If you try to innocently persist a Message with the following code, you will likely receive a HibernateException.

Message msg = new Message();
MessageType type = new MessageType();
type.setId(0);   // This is an Important Message!
msg.setType(type);
session.save(msg);

The code gives this exception:
org.hibernate.TransientObjectException: object references an unsaved 
transient instance - save the transient instance before flushing: 
MessageType

So what happened here? Hibernate uses an attribute called unsaved-value in your mapping definition to determine whether or not an object was freshly created (unsaved), or a transient instance that was loaded or saved in a previous session. In this case, because that value wasn't set in the mapping definition, the default of 0 was used. If you couple that with the fact that the default initialization of an int value is also 0, Hibernate will always assume that MessageType is unsaved.

Unfortunately, the value of 0 really means something in the database! It's the id of a record with the type name "Important Message". There are two solutions to this problem. The first is to use wrapper classes like so:

// In MessageType.java:
public class MessageType {
  private Integer id;
  private String value;
  // Getters and setters omitted
}

<!-- In MessageType.hbm.xml: -->
<id name="id" column="id" unsaved-value="null">
...
</id>

The default initialization of an object is null, so the unsaved-value should be null as well. The previous code will work without a problem. The other solution is to initialize the id to some value other than 0 (and has no meaning), so as -1. Then set the unsaved-value as such.
Post a Comment