1

我有以下三个实体:-

Parent.java:

@实体@Getter公司@设置器@ToString(目标字符串)公共类父类{@身份证@GeneratedValue(策略=GenerationType.INDITY)private Long parentId;private字符串字段;私有字符串字段NotInProjection;@一对多(mappedBy=“parent”)私人列表<儿童>儿童;}

子.java:

@实体@Getter公司@设置程序@到字符串公共课儿童{@身份证@GeneratedValue(策略=GenerationType.INDITY)私人Long childId;私有字符串字段;私有字符串字段NotInProjection;@多对一@JoinColumn(name=“parent_id”)私人父母;@一对多(mappedBy=“父亲”)私有列表<GrandChild>grandChildren;}

孙子.java:

@实体@Getter公司@设置器@ToString(目标字符串)公务舱孙子{@身份证@GeneratedValue(策略=GenerationType.INDITY)私人Long grandChildId;私有字符串字段;私有字符串字段NotInProjection;@多对一@JoinColumn(name=“父id”)私生子父亲;}

它们之间的关系如下:

父级--1-N-<子级--1-N-<孙子级

我希望将我的结果投影到以下dto类中:-

加入Dto.java:

@等于和哈希码@所有ArgsConstructor类JoinedDto{private String parentField;私有列表<ChildWithGrandChildDto>childrenAndGrandChilldren;}

ChildWithGrandChildDto.java:(在dto内部使用)

@所有ArgsConstructor@等于和哈希码带孙子女的班级{private字符串childField;私有列表<String>grandChildFields;}

我希望hibernate继续使用单个特定的最小查询,只包含必需的字段和连接,如下所示:-

选择p1_0.父亲id,p1_0.字段,c1_0.儿童id,c1_0.字段,gc1_0.字段父级p10左连接子c1 0在p1_0.parent_id=c1_0.parent_id上左连接grand_child gc1_0级关于c1_0.child_id=gc1_0.父亲_id

在上面的查询中,我们可以看到字段字段_非_投影不返回表的,因为我们不想在dto中使用它们。

在dto中父id子id不存在,但我在上面的查询中返回它们,以便在下面的映射方法中使用它们。但对于需要作为dto发送的实际数据,它们是多余的,如果可能的话,不应包含在上述查询中。

我现在正在使用以下代码实现它:-

在我的存储库中,我使用基于接口的投影来获取原始行的列表,这些行是使用联接查询返回的

公共接口ParentRepository扩展了JpaRepository<Parent,Long>{@查询(“SELECT p.parentId as parentId,”+“p.field作为parentField,”+“c.childId作为childId,”+“c.field AS childField,”+“gc.field AS grandChildField”+“从父级p”+“左加入p.childrens c”+“LEFT JOIN c.grandChildren gc”)列表<JoinedRow>findAllJoinedRows();接口JoinedRow{长getParentId();字符串getParentField();长的getChildId();字符串getChildField();字符串getGrandChildField();}}

内部返回数据示例列表<JoinedRow>:

返回数据示例

现在我使用以下自定义帮助器方法将这些原始行手动映射到dto已加入D(开头提到):-

//将投影原始表行列表接口转换为嵌套JoinedDto对象列表private List<JoinedDto>mapToJoinedD到Stream(List<ParentRepository.JoinedRow>joinedRows){返回joinedRows.流().collect(Collectors.groupingBy(ParentRepository.JoinedRow::getParentId,Collectors.groupingBy(p->p.getChildId()==null-1:p.获取儿童ID())).entrySet().流().map(this::rowEntryToDto).collect(Collectors.toList());}private JoinedD到rowEntryToDto(条目<长,映射<长,列表<JoinedRow>>>p){字符串parentField=p.getValue().values().iterator().next().get(0).getParentField();列表<ChildWithGrandChildDto>childrenWithGrand Children=p.getValue().entrySet().流().filter(q->q.getKey()!=-1).map(条目::getValue).map(q->new ChildWithGrandChildDto(q.get(0).getChildField(),q.stream()).map(ParentRepository.JoinedRow::getGrandChildField).collect(收集器.toList())).collect(收集器.toList());JoinedDto dto=新的JoinedD to(parentField,childrenWithGrandChildren);返回dto;}

我可以使用上面的helper方法映射到加入的数据流()将从存储库返回的联接原始表行映射到dto列表,如下所示:-

测试数据库关系应用程序测试.java:

@数据JpaTest@AutoConfigureTestDatabase(replace=AutoConfigureTestDatabase.replace.NONE)类TestDbRelationshipsApplicationTests{@自动连线私有ParentRepository;@测试无效joinTest(){列表<ParentRepository。JoinedRow>l=parentRepository.findAllJoinedRws();列表<JoinedDto>dtos=mapToJoinedD到流(l);System.out.println(dtos);}}

有什么方法可以省略将行转换为dto所需的这些帮助器方法,而直接使用jpa/hibernate将从这个极简连接查询返回的数据映射到我的dto中吗?就像方法一样,它映射到实体对象,我希望它映射到我的自定义dto对象,保持上面指定的查询最小化。

谢谢您。

我的PostgreSQL表脚本:

CREATE TABLE父级(parent_id大序列主键,字段VARCHAR(255),字段_非_投影VARCHAR(255));CREATE TABLE子级(child_id大序列主键,字段VARCHAR(255),字段_非_投影VARCHAR(255),parent_id BIGINT REFERENCES父级(parent_id));创建表格grand_child(grand_child_id大序列主键,字段VARCHAR(255),字段_非_投影VARCHAR(255),父id BIGINT REFERENCES子级(child_id));--插入父表插入父项(字段,字段_非_投影)值(“父字段1”,“父字段不在投影1中”),(“父字段2”,“父不在投影2中”),(“父字段3”,“父字段不在投影3中”);--插入子表INSERT INTO子(字段,字段_非_投影,父_ id)值(“子字段1-1”,“子字段不在投影1-1中”,(SELECT parent_id FROM parent WHERE字段=“父字段1”),(“子字段1-2”,“子不在投影1-2中”,(SELECT parent_id FROM parent WHERE字段=“父字段1”),(“子字段2-1”,“子不在投影2-1中”,(SELECT parent_id FROM parent WHERE字段=“父字段2”),(“子字段3-1”,“子不在投影3-1中”,(从父WHERE字段中选择parent_id=“父字段3”),(“子字段3-2”,“子不在投影3-2中”,(SELECT parent_id FROM parent WHERE字段=“父字段3”);--插入grand_child表插入grand_child(字段,字段not_in_prjection,父亲_id)值(“孙子字段1-1-1”,“孙子不在投影1-1-1中”,(SELECT child_id FROM child WHERE字段=“子字段1-1”),(“孙子字段1-1-2”,“孙子不在投影1-1-2中”,(SELECT child_id FROM child WHERE字段=“子字段1-1”),(“孙子字段1-2-1”,“孙子不在投影1-2-1中”,(SELECT child_id FROM child WHERE字段=“子字段1-2”),(“孙子字段2-1-1”,“孙子不在投影2-1-1中”,(SELECT child_id FROM child WHERE字段=“子字段2-1”),(“孙子字段3-1-1”,“孙子不在投影3-1-1中”,(SELECT child_id FROM child WHERE字段=“子字段3-1”),(“孙子字段3-2-1”,“孙子不在投影3-2-1中”,(SELECT child_id FROM child WHERE字段=“子字段3-2”);

1答案1

重置为默认值
0

我遇到的最接近实现我想要的东西是使用嵌套接口与JOIN Fetch进行投影,并对*ToMany Entity字段使用SET而不是List。但它并没有提供我想要的东西,并且增加了很多怪癖。

它只生成2个查询:-

选择p1_0.父亲id,c1_0.父母身份,c1_0.儿童id,c1_0.字段,c1_0.field_not_in_projection项目,gc1_0.父亲id,gc1_0.品牌儿童id,gc1_0.字段,gc1_ 0.field_not_in_,p1_0.字段,p1_0.field_not项目父级p10左连接子c1 0在p1_0.parent_id=c1_0.parent_id上左联接特级儿童gc1_0关于c1_0.child_id=gc1_0.父亲_id
选择gc1_0.父亲id,gc1_0.品牌儿童id,gc1_0.字段,gc1_0.field_not_in_项目grand_child gc1_0级哪里gc1_0.父亲id=?

但我们可以看到它选择了不需要的字段(fieldnotinprojection等)

我在存储库中使用以下方法来实现它:-

@查询(“SELECT p”+“从父级p”+“LEFT JOIN获取p.childrens c”+“LEFT JOIN获取c.grandChildren”)列表<JoinedDtoNew>findAllProjectedByNew();

它返回以下ProjectionInterface的列表:-

接口JoinedD到New{字符串getField();列表<ChildDto>getChildren();接口ChildDto{字符串getField();列表<GrandChildDto>getGrandChillren();GrandChildDto接口{字符串getField();}}}

而不是已加入D,我直接使用此接口,其中不返回孙儿田而是一个单独的界面孙子Dto其为了与投影的兼容性而封装该场。

但它抛出了这个异常:-

org.hibernate.loader。MultipleBagFetchException:无法同时提取多个行李

要避免此异常,我需要在实体中使用Set而不是List,如下所示:-

Parent.java:

公共类父类{//现有代码。。。@一对多(mappedBy=“父对象”)private Set<Child>children//更改为集合而不是列表@覆盖public int hashCode(){return Objects.hash(children,field,fieldNotInProjection,parentId);}@覆盖公共布尔值等于(对象obj){if(this==obj)返回true;如果(obj==空)返回false;if(getClass()!=obj.getClass())返回false;父-其他=(父)对象;return Objects.equals(children,other.children)&&Objects.equils(field,other.field)&&Objects.equals(fieldNotInProjection,other.fieldNotInsProjection)&&Objects.equals(parentId,other.parentId);}}

子.java:

公共课儿童{//现有代码。。。@一对多(mappedBy=“父亲”)私有集<GrandChild>grandChildren;//更改为设置列表指令@覆盖public int hashCode(){return Objects.hash(childId,field,fieldNotInProjection,grandChildren);}@覆盖公共布尔值等于(对象obj){if(this==obj)返回true;如果(obj==空)返回false;if(getClass()!=obj.getClass())返回false;其他子项=(子项)对象j;return Objects.equals(childId,other.childId)&&Objects.equils(field,other.field)&&Objects.equals(fieldNotInProjection,other.fieldNotInsProjection)&&Objects.equals(孙子女,其他孙子女);}}

然而,正如Vlad Mihalcea在他的回答作为最糟糕的解决方案。。。

但也许他的意思是只针对来自同一个表和多个表的联接,而不是来自同一表和另一表的联接以及来自另一个表的联结,因为这可能不会引起笛卡尔积问题。

但是,它也不是特定于此特定查询的,而是修改实体,可能会影响与其相关的所有查询。我查看了问题中的其他解决方案,但它们似乎已被弃用/生成更多查询。

由于所有这些怪癖,我无法将其标记为答案,但我会发布它,以防有人可以改进它并提供实际的解决方案,或者如果它对某人有帮助。

您的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.