Home > Blockchain >  Multiple custom constructors - Hibernate JPA TypedQuery
Multiple custom constructors - Hibernate JPA TypedQuery

Time:09-17

Is it possible to write a TypedQuery which contains multiple custom constructors? For example if you want to create objects which contain an id and another object, such as:

TypedQuery<ChapterWithBookId> query = em.createQuery(
  "SELECT new "   ChapterWithBookId.class.getName()   "(book.id, new example.Chapter(chapter.id, "
    "chapter.size"
    ")) "
    "FROM "   Book.class.getName()   " AS book JOIN "   Chapter.class.getName()   " AS chapter WHERE "
    "book.id IN :ids", ChapterWithBookIds.class)
  .setParameter("ids", ids);

Disregarding whether this example makes practical sense, this query should return a list of objects of type ChapterWithBookId, where in that list there is an object for each chapter for all books which have an id, contained in the list of ids set as a parameter.

CodePudding user response:

That's right, you can't have two constructor calls in JPQL - it's not Java, but a custom language and only supports "SELECT NEW".

There are several ways to remedy this:

.1. Have your constructor build the second object as well - admittedly an ugly hack:

public ChapterWithBookId(Long bookId, Long chapterId, Long size) {
    this.bookId = bookId;
    this.chapter = new Chapter(chapterId, size);
}

.2. Just return the result as a tuple, iterate over the list of tuples and build objects (also ugly).

.3. If you use Hibernate, use Hibernate's ResultTransformer:

List<ChapterWithBookId> chapterWithBookDtos = entityManager
.createQuery(
    "SELECT book.id, chapter.id, chapter.size "
      "FROM "   Book.class.getName()   " AS book JOIN "   Chapter.class.getName()   " AS chapter WHERE "
    "book.id IN :ids", ChapterWithBookIds.class)
  .setParameter("ids", ids);)
.unwrap( org.hibernate.query.Query.class )
.setResultTransformer(
    new ResultTransformer() {
        @Override
        public Object transformTuple(
            Object[] tuple,
            String[] aliases) {
            return new ChapterWithBookId(
                (Long) tuple[0],
                new Chapter((Long) tuple[1], (Long) tuple[2])
            );
        }
 
        @Override
        public List transformList(List collection) {
            return collection;
        }
    }
)
.getResultList();

Here a general article about your options: Hibernate’s ResultTransformer in Hibernate 4, 5 & 6

Blog post about why ResultTransformer also improves query efficiency by Vlad Mihalcea.

And Vlad also offers the hibernate-types library, which has a much nice ListResultTransformer.

I definitely recommend the last two articles and everything Vlad has written - blog and book.

CodePudding user response:

As it's defined in the JPA specification (see section 4.8 SELECT Clause):

The SELECT clause has the following syntax:

select_clause ::= SELECT [DISTINCT] select_item {, select_item}*

select_item ::= select_expression [ [AS] result_variable]

select_expression ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable |
OBJECT(identification_variable) |
constructor_expression

constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )

constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable

aggregate_expression ::=
{ AVG | MAX | MIN | SUM } ([DISTINCT] state_valued_path_expression) |
COUNT ([DISTINCT] identification_variable | state_valued_path_expression |
single_valued_object_path_expression) |
function_invocation

So, as you can see the constructor_item can not be constructor_expression. But you can easy refactor your class ChapterWithBookId constructor to construct example.Chapter by id and size and, IMHO, it will make the JPQL much readable.

  • Related