1

I have functionality which uses RSQL (I use this library) and Specification to make a filter call with where clauses to the db.

The model I query is quite simple: it just contains a bunch of properties without any relationships with other models.

The repository (notice that I use JpaSpecificationExecutor):

public interface BaseRepository<M extends BaseModel> extends JpaRepository<M, Long>, JpaSpecificationExecutor<M>{

}

So I retrieve filter conditions as a string (I get them on runtime) then just create a new specification which will be used to generate the result sql query with where clauses.

In the service I make a call to the repository method findAll with a Specificator:

String filter = "createdDate > 2019-09-04T11:52:59.449";
return repository.findAll(toSpecification(filter), pageable).getContent();  

Now I need to add new functionality which allows to group by some specific fields which I also get on runtime thus I don't know what fields exactly will be used for this.

So I've managed to add group by section to the result sql query by creating a new specification and combining it with the original one:

String filter = "createdDate > 2019-09-04T11:52:59.449";

Specification<M> groupBySpec = new Specification<M>() {

            @Override
            public Predicate toPredicate(Root<M> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

                List<Expression<?>> groupByParams = new ArrayList<>();

                groupByParams.add(root.get("param1"));
                groupByParams.add(root.get("param2"));

                query.groupBy(groupByParams);

                // Here I try to select specific columns
                query.multiselect(root.get("column1"), root.get("column2")).distinct(true);

                return query.getGroupRestriction();
            }
        };


return repository.findAll(groupBySpec.and(toSpecification(filter)), pageable).getContent(); 

But I get a sql exception ERROR: column "columnName" must appear in the GROUP BY clause or be used in an aggregate function.

The result of this because it actually selects all the columns and just ignores my query.multiselect.

I've tried to implement it using just Criteria Api without JPA Specification like this:

public List<Object[]> getAllWithSpecific(){

        CriteriaBuilder cb = em.getCriteriaBuilder();
          CriteriaQuery<Object[]> q = cb.createQuery(Object[].class);
          Root<Model> c = q.from(Model.class);


        List<Expression<?>> groupByParams = new ArrayList<>();

        groupByParams.add(c.get("column1"));
        groupByParams.add(c.get("column2"));

        q.groupBy(groupByParams);
          q.select(cb.array(c.get("column1"), c.get("column2"), cb.count(q.from(Model.class))));

          return em.createQuery(q).getResultList();

    }

And it actually worked, it selected just what I need. But I can't just use it that way because I need this group by functionality to work in combination with filter, pagination functionality I described earlier.

Since JpaRepository works in the terms of the given model, I don't know how to say to it "grab only columns I need, not the whole model"

Also I've tried to add a method to the jpa repository annoted with @Query("actual jpql query") where I can describe what to select:

@Query("select m.column1, m.column2, count(*) from Model m group by m.column1, m.column2")
    List<Object[]> customMethod();

And it works too. So now it understands that I need to grab only specific columns even though it still works in the terms of the same model.

I also know about projections which is actually an interface where I can define columns to select and use it in the repo like this: List<Projection> findAll(). But that's not my case as well as using @Query because I retrieve columns to select\group by during runtime, so it's kinda dynamic, not static.

To summarize: is possible to select specific columns using Jpa Specifications without adding new classes which would describe what to select (that's how @Query works, it just gets everything from jpql query without any additional classes\interfaces and it's fine). Maybe I need to override a jpa repo's method findAll to return List<Object[]> just as it works with @Query and pure Criteria Api?

Coffemanz
  • 863
  • 2
  • 8
  • 17
  • can you add your Model class in the post. – Pawan Maurya Nov 06 '19 at 14:47
  • Related: https://stackoverflow.com/questions/30331767/spring-data-jpa-how-to-get-only-a-list-of-ids-from-findall-method – Jens Schauder Nov 06 '19 at 14:52
  • Just like I mentioned in the question it's quite simple and contains just a bunch of String/Long/Date properties without any relations with other models. – Coffemanz Nov 06 '19 at 14:52
  • @JensSchauder this question is not the same problem, because I don't know which fields to select till actual call and I've described it in my question. So I'm not sure that this is 100% duplicate. – Coffemanz Nov 06 '19 at 14:58
  • What makes you think that you can do what is impossible when you know the fields at compile time becomes possible when you only know it at runtime? Specifications are where clauses. – Jens Schauder Nov 06 '19 at 22:35

0 Answers0