ClassCastException specifying type="character" in hbm.xml

Description

I have a class containing a field of type java.lang.Character.
I map this to an Oracle char(1 byte) field using an hmb.xml file as follows:

<property name="IntroducedFlag" column="INTRODUCED" type="character" />

At runtime this results in the following exception:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Character
at org.hibernate.type.descriptor.java.CharacterTypeDescriptor.unwrap(CharacterTypeDescriptor.java:34)
at org.hibernate.type.descriptor.sql.VarcharTypeDescriptor$1.doBind(VarcharTypeDescriptor.java:52)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:91)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:282)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:277)
at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1873)
at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1844)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1716)
at org.hibernate.loader.Loader.doQuery(Loader.java:801)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
at org.hibernate.loader.Loader.doList(Loader.java:2533)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)
at org.hibernate.loader.Loader.list(Loader.java:2271)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:119)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1716)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347)

It works fine in Hibernate 3.5.6 but not in 3.6.0 onwards.

Also probably related to this issue:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010175

Environment

Hibernate 3.6.5, Oracle 10g

Activity

Show:
Tom Jordahl
August 9, 2011, 5:07 PM

I am also seeing this error after upgrading from Hibernate 3.5.3 to 3.6.6.

It causes a regression in our test suites.

Hibernate 3.6.6 Stack trace:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Character
at org.hibernate.type.descriptor.java.CharacterTypeDescriptor.unwrap(CharacterTypeDescriptor.java:34)
at org.hibernate.type.descriptor.sql.VarcharTypeDescriptor$1.doBind(VarcharTypeDescriptor.java:52)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:91)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278)
at org.hibernate.param.NamedParameterSpecification.bind(NamedParameterSpecification.java:67)
at org.hibernate.loader.hql.QueryLoader.bindParameterValues(QueryLoader.java:571)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1716)
at org.hibernate.loader.Loader.doQuery(Loader.java:801)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
at org.hibernate.loader.Loader.doList(Loader.java:2533)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)
at org.hibernate.loader.Loader.list(Loader.java:2271)
at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:452)
at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:363)
at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1268)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)

I'll take a look at making a patch if I can. I am suspicious of the code in VarcharTypeDescriptor.java:
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setString( index, javaTypeDescriptor.unwrap( value, String.class, options ) );
}
};
}

It appears that is is trying to upwrap the character type as a string and this is what is trigger the cast exception, but I need to dig in to to figure out why the templated classes being used here are throwing the cast exception before any code (i.e. unwrap) is getting called.

Developer eyes on this (Steve Ebersole?) would be very helpful as this is blocking our upgrade to 3.6.

Tom Jordahl
August 9, 2011, 8:46 PM

Here is a test case to reproduces the issue.

DB Schema for Table Person (HSQL syntax):
CREATE TABLE PEOPLEPERSON(PERSONID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,FIRSTNAME VARCHAR(255),LASTNAME VARCHAR(255),SEX CHAR(1)

Query query = session.createQuery("select Person_alias from Person Person_alias where Person_alias.sex = :sex");
query.setParameter("sex", "M");
result = query.list();

Hibernate 3.5 was loose enough to convert the String parameter of length 1 to a Character if and when it was needed. The 3.6 problem comes from the code in VarcharTypeDescriptor.java (actually the overridden doBind() function), which wants to convert what it thinks is a Character type to a string:
VarcharTypeDescriptor.java: ~line 52
st.setString( index, javaTypeDescriptor.unwrap( value, String.class, options ) );

In this instance, the "value" type is String, and the "javaTypeDescriptor" is an instance of CharacterTypeDescriptor, which has the following signature for unwrap():
CharacterTypeDescriptor.java: ~line 54
public <X> X unwrap(Character value, Class<X> type, WrapperOptions options) {

The class cast come from the VM as a String "value" is passed in to this function, and the signature expects a Character.

Arguably this is "user error" because I am passing in an incorrect type (String) for a query parameter that should be a Character. I would say that this should be fixed for the following reasons:

  • This is breaking backwards compatibility, 3.5 worked. 3.6 doesn't.

  • This is a very common "mistake" made by users

  • The system has more than enough information to do the right thing here. When the query parameters are constructed, the NamedParameterDescriptor class has the expectedType and should be able to convert the "value" passed in to doBind() to this expected type.

As an alternative, the code that tries to bind the string parameter can detect if the "value" is already of the correct type and NOT call unwrap(). As an example:
CharacterTypeDescriptor.java: ~line 54
if (value instanceof String)
st.setString( index, value );
else
st.setString( index, javaTypeDescriptor.unwrap( value, String.class, options ) );

In our system, we do not have the type information for the query parameters available to us, so as a workaround here is user code that will detect this situation and convert the query param to the right type:

query.setParameter(paramName, paramValue);
if (paramValue instanceof String && ((String) paramValue).length() == 1)
{
// Work around Hibernate bug https://hibernate.onjira.com/browse/HHH-6313
// Hibernate 3.5 allowed Strings for character type arguments, 3.6+ is more strict.
if (query instanceof QueryImpl)
{
Type t = ((QueryImpl) query).getParameterMetadata().getNamedParameterExpectedType(paramName);
if (t.getName().equals(CharacterType.INSTANCE.getName()))
{
query.setCharacter(paramName,((String) paramValue).charAt(0));
}
}
}

This code isn't so great, as it has to drop down to QueryImpl to get the job done, but it does work around the issue.

Hibernate developer thoughts welcome!

Brett Meyer
April 7, 2014, 5:48 PM

In an effort to clean up, in bulk, tickets that are most likely out of date, we're transitioning all ORM 3 tickets to an "Awaiting Test Case" state. Please see http://in.relation.to/Bloggers/HibernateORMJIRAPoliciesAndCleanUpTactics for more information.

If this is still a legitimate bug in ORM 4, please provide either a test case that reproduces it or enough detail (entities, mappings, snippets, etc.) to show that it still fails on 4. If nothing is received within 3 months or so, we'll be automatically closing them.

Thank you!

Brett Meyer
July 8, 2014, 3:11 PM

Bulk rejecting stale issues. If this is still a legitimate issue on ORM 4, feel free to comment and attach a test case. I'll address responses case-by-case. Thanks!

Assignee

Unassigned

Reporter

Martin

Fix versions

None

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

Affirmative

Pull Request

None

backportDecision

None

Affects versions

Priority

Major
Configure