Using Hibernate version 4.1.4 Final, suppose we have a field like so:
Code:
@Column(name="ACTIVE")
@Type(type="yes_no")
private Boolean active;
Hibernate will successfully convert the strings/chars 'Y' and 'N' to Boolean.TRUE and Boolean.FALSE respectively. It is also NULL safe and will result in a NULL Boolean value. However, database fields with empty strings will throw a java.lang.StringIndexOutOfBoundsException like so:
Code:
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
at java.lang.String.charAt(Unknown Source)
at org.hibernate.type.descriptor.java.BooleanTypeDescriptor.wrap(BooleanTypeDescriptor.java:115)
at org.hibernate.type.descriptor.java.BooleanTypeDescriptor.wrap(BooleanTypeDescriptor.java:36)
at org.hibernate.type.descriptor.sql.VarcharTypeDescriptor$2.doExtract(VarcharTypeDescriptor.java:66)
at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:65)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:269)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:265)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:238)
at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:357)
at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2704)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1541)
at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1473)
at org.hibernate.loader.Loader.getRow(Loader.java:1373)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:640)
at org.hibernate.loader.Loader.doQuery(Loader.java:850)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:289)
at org.hibernate.loader.Loader.doList(Loader.java:2438)
at org.hibernate.loader.Loader.doList(Loader.java:2424)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2254)
at org.hibernate.loader.Loader.list(Loader.java:2249)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:122)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1622)
at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:374)
and so on...
Hibernate's BooleanTypeDescriptor.class has the following section for converting database values to Boolean values:
Code:
@SuppressWarnings({ "UnnecessaryUnboxing" })
public <X> Boolean wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( Boolean.class.isInstance( value ) ) {
return (Boolean) value;
}
if ( Number.class.isInstance( value ) ) {
final int intValue = ( (Number) value ).intValue();
return intValue == 0 ? FALSE : TRUE;
}
if ( Character.class.isInstance( value ) ) {
return isTrue( ( (Character) value ).charValue() ) ? TRUE : FALSE;
}
if ( String.class.isInstance( value ) ) {
return isTrue( ( (String) value ).charAt( 0 ) ) ? TRUE : FALSE;
}
throw unknownWrap( value.getClass() );
}
The problem is the block of code that deals with Strings. The charAt(0) method will cause an exception for empty strings. That line should instead read:
Code:
return ( !( (String) value).equals("") ) && isTrue( ( (String) value ).charAt( 0 ) ) ) ? TRUE : FALSE;
While it is completely fine to have a CHAR(1) or VARCHAR(1) column and deal with NULL and empty character/string in special ways, if you are using @Type(type="whatever"), you are mapping a Boolean field to the database and as such, a Boolean object cannot have a value of empty string. I believe Hibernate should handle the conversion and return either FALSE or NULL. In my case, FALSE would be preferable as anything other than 'Y' is considered false.
On a side note, why is there no option to default NULL to Boolean.FALSE for the @Type annotation? Then you could map to a primitive, non-nullable boolean field and not have to do NULL checks or Boolean.TRUE.equals(field).