Search code examples
spring-boothibernatejpaprojectionhibernate-search

Hibernate Search 6 combine projections not working


I have implemented Hibernate Search and am currently having issues with Projection. All relevant data are indexed and accordingly I try to project them to the DTO. as the documentation (https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#search-dsl-projection-composite), i have tried the following

  searchSession.search(Building.class)
                .select(f -> f.composite(BuildingDto::new,
                        f.field("id", String.class),
                        f.field("name", String.class),
                        f.field("street", String.class),
                        f.field("zip", String.class),
                        f.field("town", String.class)))
                .where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
                .sort(f -> f.field("id").desc())
                .fetch(20);

While running the build I get following error-message:

java: method composite in interface org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory<R,E> cannot be applied to given types;
  required: org.hibernate.search.engine.search.projection.dsl.ProjectionFinalStep<?>[]
  found: BuildingDto::new,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#1 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#2 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#3 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#4 of ?,java.lang.String>,org.hibernate.search.engine.search.projection.dsl.FieldProjectionValueStep<capture#5 of ?,java.lang.String>
  reason: varargs mismatch; bad return type in method reference
      BuildingDto cannot be converted to org.hibernate.search.engine.search.projection.SearchProjection<java.lang.Object>

BuildingDto has the following constructors:


    public BuildingDto (){}
    public BuildingDto (String id, String name,String street, String zip, String town) {
       //setting vars
    }

Solution

  • EDIT: Starting with Hibernate Search 6.2, you can annotate your DTO contructor with @ProjectionConstructor:

        @ProjectionConstructor
        public BuildingDto (String id, String name,String street, String zip, String town) {
           //setting vars
        }
    

    Make sure to compile your code with the -parameters compiler flag.

    Then, you can simply do this:

      searchSession.search(Building.class)
                    .select(BuildingDto.class)
                    .where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
                    .sort(f -> f.field("id").desc())
                    .fetch(20);
    

    More information here.


    Original answer (Hibernate Search 6.1 and below):

    As explained in the reference documentation, type-safe composite projections are only supported up to three inner projections at the moment.

    If you need more, your "transformer" function that creates the DTO will need to accept a List<?> and do some casts:

      searchSession.search(Building.class)
                    .select(f -> f.composite(list -> new BuildingDto(
                                    (String) list.get(0),
                                    (String) list.get(1),
                                    (String) list.get(2),
                                    (String) list.get(3),
                                    (String) list.get(4)
                            ),
                            f.field("id", String.class),
                            f.field("name", String.class),
                            f.field("street", String.class),
                            f.field("zip", String.class),
                            f.field("town", String.class)))
                    .where(f -> f.wildcard().fields("id", "name", "town", "street", "zip").matching(search))
                    .sort(f -> f.field("id").desc())
                    .fetch(20);
    

    Yes that's ugly, but we're working on better solutions.