QueryDsl 投影 ElementCollection

QueryDsl projection ElementCollection

我正在尝试弄清楚如何使用枚举列表 (@ElementCollection) 对实体进行 DTO 投影。不幸的是,缺少 QueryDsl 文档,在这里我只能找到版本 3 的结果, 适用于版本 4。

@Entity
public class User {
    private String username;

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

我想要一个带有 set/list/array 权限枚举或简单字符串的 DTO(无论如何都会转换为 JSON)。一个简单的构造函数表达式不起作用:

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

给我 org.hibernate.QueryException: not an entity

我见过的所有 .transform() 示例都使用 groupBy 和 return 地图。我正在动态生成这些查询,我想要一个 DTO 列表,而不是有时是 DTO 列表,有时是 Map。

编辑:

如果我要编写本机 PostgreSQL 查询,则类似:

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;

编辑 2:

我想这就是我与 JPQL 的关系? :呕吐:

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();

好吧,我终于明白了。在这种情况下,您实际上必须使用转换器,这是有道理的,因为您想要聚合多行。

我不得不深入研究 QueryDsl's unit tests。如果您不使用 IDE 但像我一样在 Github 上阅读它,静态导入实际上会使它变得棘手。我几乎有了解决方案,但我使用了 Expressions.set(),而不是 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))));

这比 JPQL/Hibernate-ResultTransformer 版本好看多了。

这是使用 .transform() 方法的完整示例。

import com.querydsl.core.ResultTransformer;
import com.querydsl.core.Tuple;
import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQuery;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor
public class MyDaoImpl implements MyDao {

  @PersistenceContext
  EntityManager entityManager;

  @Override
  public List<Dto> findAll(Pageable pageable, Predicate predicate, OrderSpecifier<?>[] sorting) {
    return buildQuery()
        .where(predicate)
        .limit(pageable.getPageSize())
        .offset(pageable.getOffset())
        .orderBy(sorting)
        .transform(buildDtoTransformer());
  }

  private JPAQuery<Tuple> buildQuery() {
    return new JPAQuery<>(entityManager)
        .from(QEntity.entity)
        .select(buildSelectExpressions());
  }

  private ResultTransformer<List<Dto>> buildDtoTransformer() {
    return GroupBy
        .groupBy(QEntity.entity.id)
        .list(Projections.constructor(Dto.class, buildSelectExpressions()));
  }

  private Expression<?>[] buildSelectExpressions() {
    return new Expression[] {
        QEntity.entity.id,
        QEntity.entity.mnemonic,
        QEntity.entity.label,
        QEntity.entity.lastUpdateDate,
        QEntity.entity.status,
        QEntity.entity.updatedBy,
    };
  }
}

优点

  • .select().transform() 方法的相同表达式数组。这意味着表达式可以在一个地方。

缺点

  • 没有分组。这意味着没有嵌套集合。

  • 表达式顺序和 DTO 构造函数参数顺序应该相同。

    new Expression[] {
       QEntity.entity.id,
       QEntity.entity.mnemonic,
       QEntity.entity.label,
       QEntity.entity.lastUpdateDate,
       QEntity.entity.status,
       QEntity.entity.updatedBy,
    };
    
    public class Dto {
    
       public Dto(Integer id,
                 String mnemonic,
                 String label,
                 LocalDateTime lastUpdateDate,
                 Boolean status,
                 String updatedBy) {
        this.id = id;
        this.mnemonic = mnemonic;
        this.label = label;
        this.lastUpdateDate = lastUpdateDate;
        this.status = status;
        this.updatedBy = updatedBy;
      }
    }
    
  • 创建嵌套对象逻辑应该在 DTO 构造函数中。

结论

您可以对单级简单对象使用.transform()

如果您需要额外的操作(作为构建分层响应结构的分组),您应该制作自己的映射器 class。映射器 class 将接收 Collection<Tuple> 和 returns List<YourDto>Set<YourDto>.