Search code examples
javaspringmigrationquery-builder

Spring migration 2.7 to 3.0.6: problems in Specifications (queryBuilder)


I have a complex query written with a QueryBuilder to create a Specification that will be passed to the repository.findAll(Specification<...>) function to filter results.

The query worked fine in Spring 2.7, but after the migration it stopped to work. I have been experimenting with the code and I have found that if I comment the line builder.in(equip.get("id")).value(subqueryEquip), the query works (of course, I need this line of code for filtering).

The error that throws in console is:

class org.hibernate.metamodel.mapping.internal.BasicAttributeMapping cannot be cast to class org.hibernate.metamodel.mapping.EntityValuedModelPart (org.hibernate.metamodel.mapping.internal.BasicAttributeMapping and org.hibernate.metamodel.mapping.EntityValuedModelPart are in unnamed module of loader 'app')

I suspect that the in with a Subquery is now giving errors.

So the function in question (the one that returns the predicate) is:

public static Specification<Usuari> filterByLaboratorisAndEquipsAssignatsToUser(UsuariFilterParams usuariFilterParams, UsuariLogat usuariLogat) {
        return (root, query, builder) -> {
            List<Predicate> predicates = buildListOfPredicates(root, builder, usuariFilterParams);

            query.select(root.get("id")).distinct(true);

            Subquery<Equip> subqueryEquip = buildSubqueryEquip(query, builder, usuariLogat.getId());

            Join<Usuari, Equip> equip = root.join("equips", JoinType.LEFT);
            Join<Usuari, Rol> rol = root.join("rol");

            predicates.add(builder.and(
                    builder.notEqual(root.get("id"), usuariLogat.getId()),
                    builder.or(
                            builder.in(equip.get("id")).value(subqueryEquip),
                            builder.equal(rol.get("id"), (RolsEnum.ROLE_USUARI_LABORATORI.getId()))
                    ),
                    builder.greaterThan(rol.get("id"), usuariLogat.getFirstRole().getId())));

            query.where(builder.and(predicates.toArray(new Predicate[0])));
            return query.getRestriction();
        };
    }

And this generate the subquery used in previous function:

    private static Subquery<Equip> buildSubqueryEquip(CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, long usuariLogatId) {
        Subquery<Equip> subqueryEquip = query.subquery(Equip.class);
        Root<Equip> subEquip = subqueryEquip.from(Equip.class);

        Join<Equip, Usuari> subUsuariEquip = subEquip.join("usuaris");
        return subqueryEquip.select(subEquip).where(criteriaBuilder.equal(subUsuariEquip.get("id"), usuariLogatId));
    }

Did you experiment this error while migrating to 3.0.6? How did you solve?


Solution

  • After continuing my research trough the migration guides of Springboot and Hibernate, I found the solution here:

    https://docs.jboss.org/hibernate/orm/6.0/migration-guide/migration-guide.html#query-criteria-copy

    where it is said:


    The same is true for the left and right hand side types of a comparison predicate. Queries like

    Root<Participation> root = criteria.from( Participation.class );
    Root<Submission> rootSubQuery = subQuery.from( Submission.class );
    subQuery.select( rootSubQuery.join( "submitters" ) );
    
    // left hand side of type Submission.id and right hand side of Submissions types
    criteria.where( root.get( "id" ).in( subQuery ) );
    

    will be considered invalid and should be changed into

    Root<Participation> root = criteria.from( Participation.class );
    Root<Submission> rootSubQuery = subQuery.from( Submission.class );
    subQuery.select( rootSubQuery.join( "submitters" ) );
    
    // left hand side of type Submission.id and right hand side of Submissions types
    criteria.where( root.in( subQuery ) );
    

    So in my case the line:

    builder.in(equip.get("id")).value(subqueryEquip),
    

    has to be replaced for:

    equip.in(subqueryEquip),