"Missing constructor" error when using multiselect

Hi there,

we just upgraded from Hibernate 3.6 to 5.6 and field projection seems to work a little differently than we were used to.

This is my DAO code:

public List<ENTITY> findByCriteria(final Map<String, Object> criteriaMap, final List<String> fields, final Class<ENTITY> entityClass) {

CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(aliasToBeanClass); // aliasToBeanClass = PmPropertyVOImpl.class
Root root = cq.from(aliasToBeanClass);

if (fields != null) {
	List<Selection> projections = new ArrayList<>();
	for (String field : fields) {
		projections.add(root.get(field));
	}

	if (keys.contains(AbstractBaseProperties.DISTINCT)) {
		cq.multiselect(projections).distinct(true);
		} else {
			cq.multiselect(projections).distinct(false);
		}
        }
}

When I fetch the data like this

cq.where(cb.and(predicates.toArray(new Predicate[0])));
Query query;
if (fields != null) {
	query = session.createQuery(cq);
} else {
	query = session.createQuery(cq);
}
List<ENTITY> entities = query.getResultList();

Just for the sake of debugging i set a breakpoint to the query line following the if-clause. When fields is not null and the programmer calling the findByCriteria method wants just a reduced set of attributes from that table, I get the message


Unable to locate appropriate constructor on class [com.myapp.pm.business.impl.PmPropertyVOImpl]. Expected arguments are: long

My problem is that we want to keep this method dynamic, allowing developers to pass every combination of fields that they want - or no fields at all if they want the entire row. Does that mean I would have to create constructors for every possible combination of fields (which is literally impossible). In the case that led to the error message above, I wanted to fetch the rowguid only. my class PmPropertyVOImpl has a public non-args constructor and getter/setter for every attribute.

Calling multiselect essentially instructs Hibernate to create a dedicates select clause containing just the expressions you pass. This kind of multiselect query usually can only result in the types Object[] and Tuple. So in your case, the type aliasToBeanClass that you pass into cb.createQuery, normally has to be either Object[] or Tuple, but Hibernate also supports implicit constructors i.e. if the type is other than Object[] or Tuple, it just assumes that you want to call a constructor of the result class, with the types matching the selection items.

I don’t know what Hibernate 3 did before, but the thing that you seem to want to achieve, would require that you assign aliases to the projections and then use the AliasToBeanResultTransformer by calling

query.setResultTransformer( Transformers.aliasToBean( aliasToBeanClass ) )

and replacing cb.createQuery( aliasToBeanClass ) with cb.createQuery( Object[].class ).

Ok that sounds promising but I think the resultTransformer is having some trouble in my case. I fetch data that should result in one result line, which is in my List entities, but it looks like the transformer is not able to do its thing, as all values are null. Do you have an idea, what can cause this problem?

You probably didn’t specify the aliases. You need to do something like this:

	for (String field : fields) {
		projections.add(root.get(field).alias(field));
	}

I did that.

if (fields != null) {
			for (String field : fields) {
				Selection s = root.get(field);
				s.alias(field); // field = rowguid, setter in VO = setRowguid()
				projections.add(s);
			}

		}

Can i assume that hibernates looks for a first letter capitalled version of the setter?

For example, if my variable is rowguid, the setter is setRowguid, it should not try to look up setrowguid()

You’ll have to debug into org.hibernate.transform.AliasToBeanResultTransformer#initialize then to understand what is happening.

It seems like somewhere on the way my aliases get lost and are not available for the Transformer.

I will do some more research and keep you guys updated in case I found the error. Thanks for helping me out so far.

I think this is a bug in 5.6, in this ticket the same issue was fixed in 6.0.0 Beta 1

https://hibernate.atlassian.net/browse/HHH-13140

You can workaround this by providing a custom implementation of the AliasToBeanConstructorResultTransformer that injects aliases into the array based on fields array/list you pass to your method.