Я пытаюсь заставить вложенные проекции работать в Spring Boot.У меня есть 2 сущности, Parent
и Child
, где Parent
имеет однонаправленное отношение @OneToMany
к Child
.
Вот классы: (с использованием аннотаций 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;
}
}
Когда я получаю данные без проецирования, я получаю следующее:
[
{
"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"
}
]
}
, и цель будет следующей:
{
"id": 1,
"basic": "parent-basic-1",
"children": [1,2]
},
{
"id": 2,
"basic": "parent-basic-2",
"children": [3,4]
}
Однако это кажется полностьюневозможно достичь этого.
- До сих пор я пробовал Проекция конструктора :
@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();
, но это приводит к следующемуошибка:
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
Попытка
Динамическая проекция :
// Dynamic Projection in Repository
List<ParentDto> findAllDynamicProjectionBy();
приводит к следующей ошибке:
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]
, которая в основном говорит мне, что соединение выполнено,но значения не группируются по идентификатору родителя, в результате чего получается x строк, где x - это число детей, которых имеют родители, у каждого из которых есть базовая информация о родителях и одна из данных о своих дочерних элементах.
Единственное, что «работает» - это
Интерфейсное проектирование :
// Interface Projection in Repository
List<ParentDtoInterface> findAllInterfaceProjectedBy();
public interface ParentDtoInterface {
long getId();
String getBasic();
List<ChildDtoInterface> getChildren();
}
public interface ChildDtoInterface {
long getId();
}
Это приводит к:
[
{
"id": 1,
"children": [
{
"id": 1
},
{
"id": 2
}
],
"basic": "parent-basic-1"
},
{
"id": 2,
"children": [
{
"id": 3
},
{
"id": 4
}
],
"basic": "parent-basic-2"
}
]
Теперь моя проблемаИнтерфейс-проекция заключается в том, что он будет загружать не только ожидаемые свойства, но и все свойства, но Джексон будет сериализовать только те, которые предоставляет интерфейс, поскольку он использует определение класса / интерфейса.
Загружен родительский элемент:(журнал sql; см. строку 4, загружена подробная информация)
select
parent0_.id as id1_1_,
parent0_.basic as basic2_1_,
parent0_.detail as detail3_1_
from
parent parent0_
Кроме того, проекция интерфейса кажется очень медленной (см. этот вопрос Stackoverflow ), и мне все равно придется распаковатьдети, потому что они указаны как [{id: 1}, {id: 2}], но мне действительно нужно [1,2].Я знаю, что могу сделать это с @JsonIdentityReference(alwaysAsId = true)
, но это всего лишь обходной путь.
Также я немного запутался, почему данные загружаются в n + 1 запросах - 1 для родителей, а другой n (где nчисло родителей) для каждого из родителей childs:
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
Я пытался @OneToMany(fetch=FetchType.LAZY)
и @Fetch(FetchType.JOINED)
- оба дают тот же результат, что и выше.
Так чтоГлавный вопрос: есть ли способ добиться проекции с помощью Spring Boot для вложенных сущностей, чтобы в минимально возможных запросах загружались только необходимые данные, и в лучшем случае я могу настроить их так, чтобы вместо загрузки Listдети, я могу просто загрузить List childIds (может быть, с помощью запроса Jpa, который группирует объединенные строки по parentid и позволяет извлекать необходимые данные из Child?).
Я использую Hibernate и базу данных в памяти.
Спасибо за любой ответ или совет!
Редактировать: Чтобы уточнить: я не пытаюсь найти способ сериализации данных в требуемом формате - этого я уже могу достичь,Основное внимание уделяется только загрузке необходимой информации из базы данных.