Setup is DB2 9.5, Java 1.7, Hibernate 4.1.4(tried 4.3.8 as well), Struts 1.3 application. There are multiple columns in the database set to VARCHAR(1) that are being used to store "Y", "N", or null values. Null is not desirable but is due to old data being loaded, the application will always send "Y" or "N" back to the ORM layer. The idea was to convert these to primitive booleans where null would be considered false as primitive booleans are simple to work with in Java.
First thought was to use Hibernate's @Type(type="yes_no") annotation but that returns a Boolean class because the column may be null and the primitive boolean cannot be null. Too bad there wasn't another attribute where null would default to false.
Second thought was to use JPA 2.1 @Converter and @Convert annotations using the AttributeConverter class. There is also the autoApply=true attribute that would apply this converter to all booleans in the project. This seemed like the best solution. So we decided to test it out on one column. This resulted in the following class:
Code:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class BooleanToStringConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean value) {
return (value != null && value) ? "Y" : "N";
}
@Override
public Boolean convertToEntityAttribute(String value) {
return (value == null ? false : "Y".equals(value));
}
}
and the following annotation in a pojo:
Code:
@Convert(converter=BooleanToStringConverter.class)
private Boolean active;
On Hibernate 4.1.4, this results in the following exception:
Code:
org.hibernate.exception.GenericJDBCException: [jcc][1093][10419][3.64.96] Invalid data conversion: Parameter instance Y is invalid for the requested conversion to short. ERRORCODE=-4220, SQLSTATE=null
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:54)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
at org.hibernate.engine.jdbc.internal.proxy.AbstractResultSetProxyHandler.continueInvocation(AbstractResultSetProxyHandler.java:108)
at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
at com.sun.proxy.$Proxy57.getShort(Unknown Source)
at org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor$2.doExtract(SmallIntTypeDescriptor.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.hql.QueryLoader.list(QueryLoader.java:470)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:355)
at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:195)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1248)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
Debugging some of this code and it looks like Hibernate is trying to convert "Y" to TINYINT, or thinks it is a TINYINT, and then trying to convert it to a Java short. Looking at the object being fetched it says that the column is org.hibernate.type.Boolean. It never reaches the code in the BooleanToStringConverter class.
Switching to Hibernate 4.3.8 and it works if that database column is not null but when it is null it gives this exception:
Code:
org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type setter of ************************************
at org.hibernate.property.DirectPropertyAccessor$DirectSetter.set(DirectPropertyAccessor.java:126)
at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:713)
at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:362)
at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:4718)
at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:188)
at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:144)
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:244)
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:215)
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:140)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:138)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:100)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:693)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:92)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1897)
at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:558)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:260)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:554)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:142)
at org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork(AbstractPersistentCollection.java:171)
at org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork(AbstractPersistentCollection.java:156)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:260)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:155)
at org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:160)
While debugging this one, I can see it does reach the BooleanToStringConverter class and successfully assigns a false value to the object's property. A little later while stepping through the code, Hibernate then tries to assign null to the property which causes the exception. It's like Hibernate doesn't know that the JPA converter has already converted and set the value.
I did run across a few posts about AttributeConverter being broken with Hibernate Envers but not much else. Is this a bug in Hibernate?