我有一个班级Plan
,其中有一个Activity
列表。 Activity
类引用了单个Plan
。因此,有一个像这样的 OneToMany 关系:
@Entity
public class Plan {
@OneToMany(mappedBy = "Plan")
private List<Activity> activities;
}
@Entity
public class Activity {
@ManyToOne
@JoinColumn(name= "PLAN_ID")
private Plan plan;
}
我需要将它们转换为DTO才能发送到表示层。所以我有一个汇编程序类来简单地将域对象转换为POJO。
public class PlanAssembler {
public static PlanDTO makeDTO(Plan p) {
PlanDTO result = new PlanDTO();
result.setProperty(p.getProperty);
...
for (Activity a: p.getActivity()) {
// Here I need to iterate over each activity to convert it to DTO
// But in ActivityAssembler, I also need PlanDTO
}
正如您所看到的,在PlanAssembler
中,我需要迭代所有活动并将其转换为ActivityDTO
,但麻烦的是,ActivityAssembler
我还需要PlanDTO
构造ActivityDTO
。这将是一个无限循环。我该如何解决这个问题?
请帮忙。
答案 0 :(得分:2)
现在,如果你真的想自己解决问题:
1)在mapper类中,您可以通过使它们单向来定义实现映射器来解决此问题。用像这样的方法
MapPlanWithActivities()
,MapPlan()
,MapActivitiesWithPlan()
和MapActivities()
。通过这种方式,您可以知道您需要哪些数据,并根据您使用的函数知道何时停止递归。
2)另一个(更多)更复杂的解决方案是通过逻辑解决问题并检测循环。例如,您可以像Jackson Library那样为该案例定义注释。为此,你将不得不使用一些java反射。 See Java Reflection here
3)最简单的方法是使用Dozer,如我的评论所述:Dozer
答案 1 :(得分:1)
它不会是一个无限循环,因为你必须使用刚刚在循环之前创建的PlanDTO对象结果。请参阅下面的代码。
注意:我仍然建议您选择一个能够为您完成这些工作的框架。
public class PlanAssembler {
public static PlanDTO makeDTO(Plan p) {
PlanDTO result = new PlanDTO();
result.setProperty(p.getProperty);
...
for (Activity a: p.getActivity()) {
ActivityDTO activityDTO = new ActivityDTO();
// Here I need to iterate over each activity to convert it to DTO
// But in ActivityAssembler, I also need PlanDTO
//Code to convert Activity to ActivityDTO.
activityDTO.setPlan(result);
}
答案 2 :(得分:1)
这是一个非常常见的问题,因此此答案基于我在博客上写的this post。
假设我们具有以下post
和post_comment
表,它们通过post_id
表中的post_comment
外键列构成one-to-many relationship。 / p>
考虑到我们有一个用例,只需要从id
表以及title
和post
中提取id
和review
列post_comment
表中的列中,我们可以使用以下JPQL查询来获取所需的投影:
select p.id as p_id,
p.title as p_title,
pc.id as pc_id,
pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id
运行上面的投影查询时,我们得到以下结果:
| p.id | p.title | pc.id | pc.review |
|------|-----------------------------------|-------|---------------------------------------|
| 1 | High-Performance Java Persistence | 1 | Best book on JPA and Hibernate! |
| 1 | High-Performance Java Persistence | 2 | A must-read for every Java developer! |
| 2 | Hypersistence Optimizer | 3 | It's like pair programming with Vlad! |
但是,我们不想使用基于表格的ResultSet
或默认的List<Object[]>
JPA或Hibernate查询投影。我们想将上述查询结果集转换为List
个对象的PostDTO
,每个这样的对象都有一个comments
集合,其中包含所有关联的PostCommentDTO
对象:
正如我在this article中所述,我们可以使用休眠ResultTransformer
,如以下示例所示:
List<PostDTO> postDTOs = entityManager.createQuery("""
select p.id as p_id,
p.title as p_title,
pc.id as pc_id,
pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id
""")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new PostDTOResultTransformer())
.getResultList();
assertEquals(2, postDTOs.size());
assertEquals(2, postDTOs.get(0).getComments().size());
assertEquals(1, postDTOs.get(1).getComments().size());
PostDTOResultTransformer
将定义Object[]
投影与包含PostDTO
子DTO对象的PostCommentDTO
对象之间的映射:
public class PostDTOResultTransformer
implements ResultTransformer {
private Map<Long, PostDTO> postDTOMap = new LinkedHashMap<>();
@Override
public Object transformTuple(
Object[] tuple,
String[] aliases) {
Map<String, Integer> aliasToIndexMap = aliasToIndexMap(aliases);
Long postId = longValue(tuple[aliasToIndexMap.get(PostDTO.ID_ALIAS)]);
PostDTO postDTO = postDTOMap.computeIfAbsent(
postId,
id -> new PostDTO(tuple, aliasToIndexMap)
);
postDTO.getComments().add(
new PostCommentDTO(tuple, aliasToIndexMap)
);
return postDTO;
}
@Override
public List transformList(List collection) {
return new ArrayList<>(postDTOMap.values());
}
}
aliasToIndexMap
只是一个很小的实用程序,它使我们可以构建一个Map
结构,该结构将列别名与列值位于Object[]
{{1中的索引}}数组:
tuple
public Map<String, Integer> aliasToIndexMap(
String[] aliases) {
Map<String, Integer> aliasToIndexMap = new LinkedHashMap<>();
for (int i = 0; i < aliases.length; i++) {
aliasToIndexMap.put(aliases[i], i);
}
return aliasToIndexMap;
}
是我们将存储所有postDTOMap
实体的位置,最后,这些实体将由查询执行返回。我们使用PostDTO
的原因是,每个子记录的SQL查询结果集中都有父行。
postDTOMap
方法仅在computeIfAbsent
中没有存储现有的PostDTO
引用的情况下,才可以创建PostDTO
对象。
postDTOMap
类具有一个构造函数,可以使用专用的列别名来设置PostDTO
和id
属性:
title
public class PostDTO {
public static final String ID_ALIAS = "p_id";
public static final String TITLE_ALIAS = "p_title";
private Long id;
private String title;
private List<PostCommentDTO> comments = new ArrayList<>();
public PostDTO(
Object[] tuples,
Map<String, Integer> aliasToIndexMap) {
this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
this.title = stringValue(tuples[aliasToIndexMap.get(TITLE_ALIAS)]);
}
//Getters and setters omitted for brevity
}
的构建方式类似:
PostCommentDTO
就是这样!
使用public class PostCommentDTO {
public static final String ID_ALIAS = "pc_id";
public static final String REVIEW_ALIAS = "pc_review";
private Long id;
private String review;
public PostCommentDTO(
Object[] tuples,
Map<String, Integer> aliasToIndexMap) {
this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
this.review = stringValue(tuples[aliasToIndexMap.get(REVIEW_ALIAS)]);
}
//Getters and setters omitted for brevity
}
,可以将SQL结果集转换为分层DTO投影,使用起来非常方便,尤其是当它需要作为JSON响应编组时:
PostDTOResultTransformer
答案 3 :(得分:0)
这是Blaze-Persistence Entity Views的完美用例。
我创建了该库,以允许在JPA模型和自定义接口定义的模型之间轻松映射,例如类固醇上的Spring Data Projections。这个想法是您以自己喜欢的方式定义目标结构,并通过JPQL表达式将属性(获取器)映射到实体模型。由于属性名称用作默认映射,因此大多数情况下您不需要显式映射,因为80%的用例都是将DTO作为实体模型的子集。
模型的映射看起来像下面的一样简单
@EntityView(Plan.class)
interface PlanDTO {
@IdMapping
Long getId();
String getName();
List<ActivityDTO> getActivities();
}
@EntityView(Activity.class)
interface ActivityDTO {
@IdMapping
Long getId();
String getName();
}
查询是将实体视图应用于查询的问题,最简单的方法就是按ID查询。
PlanDTOdto = entityViewManager.find(entityManager, PlanDTO.class, id);
但是Spring Data集成允许您像使用Spring Data Projections一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/#spring-data-features
这样做的最大好处是,这种方法只会获取您通过实体视图中的getter定义定义的内容,而其他方法通常会获取过多的数据和/或需要大量的样板。