19

I'm using JPQL and want to receive some normal parameters and a collection in a Constructor Expression to directly create the DTO-objects. But if the Collection is empty, I always get a error because he doesnt find the right constructor:

The DTO-Class looks the following:

public class DTO {
    private long id;
    private String name;
    private Collection<Child> children;

    public DTO (long id, String name, Collection<Child> children){
    this.id = id;
    this.name = name;
    this.children= children;
    }
}

The Child-Class:

public class Child {
    private String name;
    private int age;
}

And now the Constructor Expression looks the following:

return (List<DTO>) getEm().createQuery("SELECT DISTINCT NEW de.DTO(p.id, p.name, p.childs) 
                                          FROM Parent p").getResultList();

The current problem is, that in case the Collection p.childs is empty, it says that it doesnt find the right constructor, it needs (long, String, Child) instead of (long, String, Collection).

Do you have any kind of solution or is it simply not possible to use a Collection in Constructor Expression?

Oh and one more thing: If I easily create two constructors (..., Collection childs AND ..., Child childs) I get no results, but no error too... in my eyes not really satisfiyng :-/

Florian
  • 191
  • 1
  • 4
  • I think you've forgotten to post your `Parent` class. Also the plural form of `child` is `children`. – Behrang May 14 '11 at 11:37
  • have you tried adding condition to query - "WHERE p.childs IS NOT EMPTY AND SIZE(p.childs) <> 0" – Nayan Wadekar May 14 '11 at 12:15
  • I was also looking for something similar, and the closest thing I could find to what you're asking (w/o relying on an extra library) was the post titled [How to fetch a one-to-many DTO projection with JPA and Hibernate](https://vladmihalcea.com/one-to-many-dto-projection-hibernate/) by @VladMihalcea - This is a copy of my comment on [Q46681780](https://stackoverflow.com/questions/46681780/) since it looked pretty close to this one. Apologies if this is seen as spamming or something... – OzgurH Aug 08 '21 at 23:28

4 Answers4

18

The JPA spec (Version 2.0, 2.1 and 2.2 at least) doesn't allow the use of collections as parameters in constructor expressions. Section 4.8 defines a constructor expression like this:

constructor_expression ::=
        NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::=
        single_valued_path_expression |
        scalar_expression |
        aggregate_expression |
        identification_variable

A single_valued_path_expression is what it sounds like - a property expression that points to a scalar of some sort (such as p.id), a scalar_expression is also an expression that points to scalar, an aggregate_expression is the application of a function like sum which reduces a multi-valued expression to a scalar one, and an identification_variable is a reference to the type you're querying over. None of which can be collection-valued.

So, if your JPA provider lets you use collection-valued parameter in a constructor expression, it's because it's going above and beyond the spec. Which is very nice of it, but not something you can necessarily rely on!

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • Hey Tom Anderson, thank you very much for this information. I'm just using Eclipse Link so i guess, without using Hibernate or something else, that also acts on a higher layer, i won't be sucessful with this. @Comments: Yeah right, i forgot the parent class but i also only has an id, a name and a Collection with or without children. So the conditions like IS NOT EMPTY wouldn't meet my objective, i also need parents without kids. Thanks for your help, too. – Florian May 14 '11 at 19:08
  • 1
    Same in JPA 2.1, see chapter 4.14 BNF, page 211. – Arend v. Reinersdorff Jul 03 '17 at 07:11
4

Try,

public DTO (long id, String name, Object children)
Tiny
  • 27,221
  • 105
  • 339
  • 599
James
  • 17,965
  • 11
  • 91
  • 146
2

I had a similar problem and tried "Object" in DTO constructor as James suggested, but a child object is passed in and it seems that it is only the first child instead of the expected List/Array of children.

I ended up with a "normal" query creating all the DTOs in a for loop.

TypedQuery<Parent> query = em.createQuery("SELECT p FROM Parent p", Parent class);
        List<Parent> list = query.getResultList();
        List<DTO> result = new ArrayList<>();
        for (Parent p : list)
        {
            DTO dto = new DTO();
            //set dto props and fill collection
            result.add(obj);
        }
        return result;
smakks
  • 69
  • 4
0

That's not possible out of the box like others have answered already, but I think this is a perfect use case for Blaze-Persistence Entity Views.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(Parent.class)
public interface ParentDto {
    @IdMapping
    Long getId();
    String getName();
    Set<ChildDto> getChildren();

    @EntityView(Child.class)
    interface ChildDto {
        @IdMapping
        Long getId();
        String getName();
        int getAge();
    }
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

ParentDto a = entityViewManager.find(entityManager, ParentDto.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<ParentDto> findAll(Pageable pageable);

The best part is, it will only fetch the state that is actually necessary!

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58