Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QueryDsl projection ElementCollection

Tags:

java

jpa

querydsl

I'm trying to figure out how to do a DTO projection of an Entity with a list of Enums (@ElementCollection). Unfortunately, the QueryDsl Documentation is lacking and here I only find results for version 3 which are not applicable for version 4.

@Entity
public class User {
    private String username;

    @Enumerated(EnumType.STRING)
    @ElementCollection(targetClass = Permission.class)
    private Set<Permission> permissions;
}

And I'd like a DTO with a set/list/array of either Permission-Enums or simply Strings (will be converted to JSON anyway). A simple constructor expression doesn't work:

List<UserDto> users = new JPAQueryFactory(eM).select(
            Projections.constructor(UserDto.class,
                    QUser.user.username, QUser.user.permissions))
            .from(QUser.user)
            .fetch();

Gives me org.hibernate.QueryException: not an entity

All the examples with .transform() I've seen use a groupBy and return a Map. I'm generating these queries dynamically and I want a List of DTOs, not sometimes a List of DTO and sometimes a Map.

EDIT:

Something like that if I were to write a native PostgreSQL query:

select id, username, array_remove(array_agg(up.permissions), null) as permissions
from users u
left join users_permissions up on up.uid = u.id
group by u.id;

EDIT 2:

I guess this is what I'd have to do with JPQL? :puke:

List<UserDto> users = (List<UserDto>) eM.getEntityManager().createQuery(
                "SELECT u.id, u.username, u.tenantId, u.fullname, u.active, u.localeKey, perms " +
                        "FROM User u " +
                        "LEFT JOIN u.permissions perms")
                .unwrap(org.hibernate.query.Query.class)
                .setResultTransformer(
                        new ResultTransformer() {
                            private Map<Long, UserDto> res = new HashMap<>();

                            @Override
                            public Object transformTuple(Object[] tuple, String[] aliases) {
                                UserDto u = res.get(tuple[0]);
                                if (u == null) {
                                    u = new UserDto((Long) tuple[0], (String) tuple[1], "", (String) tuple[2], (String) tuple[3], (boolean) tuple[4], (String) tuple[5], EnumSet.of((Permission) tuple[6]));
                                    res.put(u.getId(), u);
                                } else {
                                    u.getPermissions().add((Permission) tuple[6]);
                                }

                                return null;
                            }

                            @Override
                            public List<UserDto> transformList(List tuples) {
                                return new ArrayList<>(res.values());
                            }
                        })
                .getResultList();
like image 409
Benjamin Maurer Avatar asked Feb 06 '26 20:02

Benjamin Maurer


1 Answers

OK, I finally figured it out. In this case you actually have to use a transformer, which makes sense, as you want to aggregate several rows.

I've had to dig through QueryDsl's unit tests. The static imports actually make it tricky if you're not using an IDE, but read it on Github like me. I almost had the Solution, but I used Expressions.set(), instead of GroupBy.set():

EnumPath<Permission> perm = Expressions.enumPath(Permission.class, "perm");

List<UserDto> users = new JPAQueryFactory(eM.getEntityManager())
                .selectFrom(QUser.user)
                .leftJoin(QUser.user.permissions, perm)
                .transform(
                        groupBy(QUser.user.id)
                        .list(Projections.constructor(UserDto.class,
                                QUser.user.id, QUser.user.username, Expressions.stringTemplate("''"), QUser.user.tenantId,
                                QUser.user.fullname, QUser.user.active, QUser.user.localeKey, GroupBy.set(perm))));

And this is much nicer on the eye then the JPQL/Hibernate-ResultTransformer version.

like image 91
Benjamin Maurer Avatar answered Feb 09 '26 12:02

Benjamin Maurer