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>
.
我正在尝试弄清楚如何使用枚举列表 (@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>
.