简体   繁体   English

如何处理具有实体关系的Spring Boot / Spring Data投影(嵌套投影)

[英]How to handle Spring Boot/ Spring Data projections with entity relationships (nested projection)

I'm trying to get nested projections working in Spring Boot. 我正在尝试让嵌套的投影在Spring Boot中工作。 I have 2 entities, Parent and Child , wheras Parent has a unidirectional @OneToMany relationship to Child . 我有2个单位, ParentChild ,wheras Parent有一个单向@OneToMany到关系Child

Here are the classes: (using Lombok-Annotations) 这些是类:(使用Lombok注释)

@Entity
@Data @NoArgsConstructor
public class Parent {

    @Id
    @GeneratedValue
    private long id;
    private String basic;
    private String detail;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Child> children;

    public Parent(String basic, String detail, List<Child> children) {
        this.basic = basic;
        this.detail = detail;
        this.children = children;
    }
}
@Entity
@Data @NoArgsConstructor
public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private long id;
    private String basic;
    private String detail;

    public Child(String basic, String detail) {
        this.basic = basic;
        this.detail = detail;
    }
}

When im fetching the data without projecting i get the following: 当即时获取数据而不进行投影时,我得到以下信息:

[
    {
        "id": 1,
        "basic": "parent-basic-1",
        "detail": "parent-detail-1",
        "children": [
            {
                "id": 1,
                "basic": "child-basic-1",
                "detail": "child-detail-1"
            },
            {
                "id": 2,
                "basic": "child-basic-2",
                "detail": "child-detail-2"
            }
        ]
    },
    {
        "id": 2,
        "basic": "parent-basic-2",
        "detail": "parent-detail-2",
        "children": [
            {
                "id": 3,
                "basic": "child-basic-3",
                "detail": "child-detail-3"
            },
            {
                "id": 4,
                "basic": "child-basic-4",
                "detail": "child-detail-4"
            }
        ]
    }

and the goal would be the following: 目标是:

    {
        "id": 1,
        "basic": "parent-basic-1",
        "children": [1,2]
    },
    {
        "id": 2,
        "basic": "parent-basic-2",
        "children": [3,4]
    }

However it seems completly impossible to achive this. 但是,要实现这一目标似乎是完全不可能的。

  1. So far I've tried Constructor Projection : 到目前为止,我已经尝试过构造器投影
@Value
public class ParentDto {
    long id;
    String basic;
    // wanted to get it to work with just Child instead of ChildDto first, before getting ChildDto to work
    Collection<Child> children; 

    public ParentDto(long id, String basic, Collection<Child> children) {
        this.id = id;
        this.basic = basic;
        this.children = children;
    }
}
    // Constructor Projection in Repository
    @Query("select new whz.springbootdemo.application.constructor_projection.ParentDto(p.id, p.basic, p.children) from Parent p")
    List<ParentDto> findAllConstructorProjected();

but that leads to the following error: 但这导致以下错误:

could not prepare statement; SQL [select parent0_.id as col_0_0_, parent0_.basic as col_1_0_, . as col_2_0_ from parent parent0_ inner join parent_children children1_ on parent0_.id=children1_.parent_id inner join child child2_ on children1_.children_id=child2_.id]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
  1. Trying Dynamic Projection : 尝试动态投影
    // Dynamic Projection in Repository
    List<ParentDto> findAllDynamicProjectionBy();

leads to the following error: 导致以下错误:

org.hibernate.hql.internal.ast.QuerySyntaxException:
Unable to locate appropriate constructor on class [whz.springbootdemo.application.constructor_projection.ParentDto].
Expected arguments are: <b>long, java.lang.String, whz.springbootdemo.application.child.Child</b>
[select new whz.springbootdemo.application.constructor_projection.ParentDto(generatedAlias0.id, generatedAlias0.basic, children) from whz.springbootdemo.application.parent.Parent as generatedAlias0 left join generatedAlias0.children as children]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [whz.springbootdemo.application.constructor_projection.ParentDto]. Expected arguments are: long, java.lang.String, whz.springbootdemo.application.child.Child [select new whz.springbootdemo.application.constructor_projection.ParentDto(generatedAlias0.id, generatedAlias0.basic, children) from whz.springbootdemo.application.parent.Parent as generatedAlias0 left join generatedAlias0.children as children]

which basically tells me that a join is executed, but the values arent grouped by the id of the parent, thus resulting in x rows, where x is the number of childs the parents has, each with the parents basic information and one of its childs information. 这基本上告诉我执行了一个联接,但是值arent由父代的id分组,因此导致x行,其中x是父代的子代数,每个子代都有父子的基本信息和一个子代。信息。

  1. The only thing "working" is Interface Projection : 唯一起作用的是Interface Projection
    // Interface Projection in Repository
    List<ParentDtoInterface> findAllInterfaceProjectedBy();
public interface ParentDtoInterface {
    long getId();
    String getBasic();
    List<ChildDtoInterface> getChildren();
}

public interface ChildDtoInterface {
    long getId();
}

It results in: 结果是:

[
    {
        "id": 1,
        "children": [
            {
                "id": 1
            },
            {
                "id": 2
            }
        ],
        "basic": "parent-basic-1"
    },
    {
        "id": 2,
        "children": [
            {
                "id": 3
            },
            {
                "id": 4
            }
        ],
        "basic": "parent-basic-2"
    }
]

Now my problem with Interface-Projection is, that it will not just load the expected properties, but all properties, but jackson will only serialize those that the Interface provides, cause it uses the Class/Interface-Definition. 现在,我对Interface-Projection的问题是,它不仅会加载预期的属性,而且会加载所有属性,但是Jackson只会序列化Interface提供的属性,因为它使用了Class / Interface-Definition。

Parent loaded: (sql log; see line 4, detail information is loaded) 父加载:(SQL日志;请参见第4行,详细信息已加载)

    select
        parent0_.id as id1_1_,
        parent0_.basic as basic2_1_,
        parent0_.detail as detail3_1_ 
    from
        parent parent0_

Also Interface Projection seems to be really slow (see this Stackoverflow question ) and i still would have to unpack the children cause they are given as [{id:1},{id:2}] but i really need [1,2]. 接口投影似乎也真的很慢(请参阅此Stackoverflow问题 ),我仍然必须解压缩子级,因为它们的名称为[{id:1},{id:2}],但我确实需要[1,2] 。 I know i can do this with @JsonIdentityReference(alwaysAsId = true) but thats just a workaround. 我知道我可以使用@JsonIdentityReference(alwaysAsId = true)来做到这一点,但这只是一种解决方法。

Also I'm abit confused why the data is loaded in n+1 queries - 1 for the parents, and another n (where n is the number of parents) for each parents childs: 我也有点困惑为什么为什么要在n + 1个查询中加载数据-父母为1个,每个父母孩子为n个(其中n是父母的数量):

    select
        parent0_.id as id1_1_,
        parent0_.basic as basic2_1_,
        parent0_.detail as detail3_1_ 
    from
        parent parent0_

   select
        children0_.parent_id as parent_i1_2_0_,
        children0_.children_id as children2_2_0_,
        child1_.id as id1_0_1_,
        child1_.basic as basic2_0_1_,
        child1_.detail as detail3_0_1_ 
    from
        parent_children children0_ 
    inner join
        child child1_ 
            on children0_.children_id=child1_.id 
    where
        children0_.parent_id=?

//... omitting further child queries

I have tried @OneToMany(fetch=FetchType.LAZY) and @Fetch(FetchType.JOINED) - both give the same result as above. 我已经尝试过@OneToMany(fetch=FetchType.LAZY)@Fetch(FetchType.JOINED) -都给出与上述相同的结果。

So the main question is: Is there any way to achive projection with Spring Boot for nested entities, so that only the needed data is loaded in as little as possible queries and in a best case scenario I can adjust it so that instead of having to load List children i can just load List childIds (maybe through a Jpa query that groups the joined rows by parentid and lets be extract needed data from the Child?). 因此,主要问题是:使用Spring Boot可以对嵌套实体进行投影以获得任何方式,以便在尽可能少的查询中仅加载所需的数据,并且在最佳情况下,我可以对其进行调整,而不必加载List子项我可以仅加载List子项(也许通过Jpa查询,该查询通过父ID对连接的行进行分组,然后从Child中提取所需的数据?)。

Im using Hibernate and an In-Memory Database. 我正在使用休眠和内存数据库。

Thanks in regards for any answer or tip! 感谢您的任何回答或提示!

Edit: To clarify: I'm not trying to find a way to serialize the data in the wanted format - this i already can achive. 编辑:澄清:我不是想找到一种方法来序列化所需格式的数据-这我已经可以实现。 The main focus is on only loading the neccessary information from the database. 主要重点是仅从数据库加载必要的信息。

this will always fetch the children but could give you the result you want. 这将始终获取孩子,但可以给您想要的结果。

public interface SimpleParentProjection {

    String getBasic();

    String getDetail();

    @Value("#{T(SimpleParentProjection).toId(target.getChildren())}")
    String[] getChildren();

    static String[] toId(Set<Child> childSet) {
        return childSet.stream().map(c -> String.valueOf(c.getId())).toArray(String[]::new);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM