2

I have a number of classes mapped as an inheritance hierarchy into a single table.

I would like to use the following Criteria query but this results in a ClassCastException since Class cannot be cast to String.

Set<Class> childClasses = new HashSet<>();
childClasses.add(Child1.class);
childClasses.add(Child2.class);

session.createCriteria(Parent.class)
       .add(Restrictions.in("class", childClasses)
       .list();

I note that Hibernate does support specifying a single class using Restrictions.eq("class", childClass) so I can workaround by using a Disjunction'. I also know that this would work if my resriction was based on the discriminator strings for each subclass but I would prefer not to use these.

It is possible to use Criteria in this manner? The accepted answer to this question suggests that it works when the class is a property of the class you are basing your Criteria on but it doesn't seem to work in the case I've shown above.

Community
  • 1
  • 1
Mark
  • 28,783
  • 8
  • 63
  • 92

1 Answers1

1

Looking at the source code, I would say that this is a Hibernate bug.

For example, SimpleExpression (returned by Restrictions.eq) calls criteriaQuery.getTypedValue which handles conversion of Class value to the corresponding discriminator value.

InExpression (returned by Restrictions.in) adds values as they are, without conversion. That's why you get ClassCastException, because later an attempt to cast Class to String is made (obviously the type of your discriminator value is String).

You could avoid using this form until it's fixed (you already suggested the proper workarounds), or, if you would really like to stick to using Class objects directly, then you could implement custom InExpression in your project. For example, something like:

public class ClassInExpression extends InExpression {
    private static final String CLASS = "class";
    private final Collection<Class> values;

    public ClassInExpression(Collection<Class> values) {
        super(CLASS, values.toArray(new Object[values.size()]));
        this.values = values;
    }

    @Override
    public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) {
        if (criteriaQuery.getTypeUsingProjection(criteria, CLASS).isComponentType()) {
            return super.getTypedValues(criteria, criteriaQuery);
        }
        return convertToDiscriminatorValues(criteria, criteriaQuery);
    }

    private TypedValue[] convertToDiscriminatorValues(Criteria criteria, CriteriaQuery criteriaQuery) {
        List<TypedValue> resultList = new ArrayList<TypedValue>();
        for (Object value : values) {
            resultList.add(criteriaQuery.getTypedValue(criteria, CLASS, value));
        }
        return resultList.toArray(new TypedValue[resultList.size()]);
    }
}

Then you would use it instead of Restrictions.in:

session.createCriteria(Parent.class)
    .add(new ClassInExpression(childClasses))
    .list()
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • And the defect was rejected since Criteria is deprecated in Hibernate 5. – Mark Feb 27 '16 at 21:22
  • @Mark I submitted the [pull request](https://github.com/hibernate/hibernate-orm/pull/1277), it has been accepted. The fix will be available on both 5.1 and 5.0. – Dragan Bozanovic Mar 08 '16 at 15:18
  • Thanks for that. I'm using 4.3 for now with the code above but good to know it will work out of the box if/when we move to 5.x – Mark Mar 08 '16 at 23:08