Я выполняю сценарий реальной жизни в нашей базе данных, и поэтому у меня нет места, чтобы изменить его структуру в случае, если это прозвучит как предложение.Вот структура, о которой идет речь:
EntityA {
@Id
.......
@OneToMany(mappedBy = "xxxx", fetch = FetchType.LAZY)
private Set<EntityB> entityB;
@Column(name = "partition", columnDefinition = "nchar", length = 3)
private String partitionKey;
}
EntityB {
@Id
..........
@Column(name = "partition")
private String partitionKey;
@ManyToOne(fetch = FetchType.LAZY) //EAGER is the default for the to-one
@JoinColumn(name = "bbbb")
private EntityA entityA;
@OneToMany(mappedBy = "EntityCPk.entityB", fetch = FetchType.LAZY)
private Set<EntityC> entityC;
@OneToOne(mappedBy = "cccc", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private EntityD entityD;
}
EntityC {
@EmbeddedId
private EntityCPk entityCPk;
@Embeddable
public static class EntityCPk implements Serializable {
@Column( name = "partition")
private String partitionKey;
@ManyToOne
@JoinColumn(name = "xxxx")
private EntityB entityB;
@ManyToOne
@JoinColumn(name = "xxxx")
private EntityE entityE;
}
}
EntityD {
@id
.........
@MapsId
@OneToOne
@JoinColumn(name = "zzzz", columnDefinition = "nchar")
@PrimaryKeyJoinColumn
private EntityB entityB;
}
EntityE {
@id
.........
@OneToMany(mappedBy = "yyyPk.entityE")
private Set<EntityC> entityC;
}
Теперь необходимо выполнить запрос за один раз с объединениями и избегать сценариев 1 + N.Я предполагаю, насколько я видел, что аннотации .LAZY или EAGER «перезаписываются» при использовании аннотации Query в репозитории вместе с опцией FETCH.Итак, вот чего я добился (сущностьXXX и entityYYY не мешают нашему делу, поэтому я просто упомяну их):
Первая попытка с FetchType.LAZY в свойстве EntityB.entityC:
"select a" +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD latest " +
"where a.aProp in ( :props ) " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
Результаты, как и ожидалось.Я получаю 1 запрос, НО я не получаю коллекцию, возвращенную в EntityB.entityC из-за ленивой аннотации и, конечно, она не присутствует в запросе.Если я изменю EntityB.entityC на FetchType.EAGER, то получу 3 ожидаемых запроса.Один является основным и N на entityC в Set.Поэтому я думаю, что следующий шаг - присоединиться к entityC:
Вторая попытка:
"select a " +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD as latest " +
"join bs.entityC as cs " + //please note I am not using FETCH yet
"where a.aProp in ( :props ) " +
"and c.entityCPk.partitionKey = a.partitionKey " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
Результат неожиданный, и я думаю, что о нем также сообщили здесь .Теперь я получаю кратное число (а) всех ссылок на один и тот же объект, равное сумме суммы bs.entityC.Так, если, например, a-1 -> имеет 1-bs -> имеет 17 сс, а a2 -> имеет 1-bs -> имеет 67 сс, я получаю набор результатов из 84 a одинаковых объектов!Это первый вопрос.Почему это происходит?
Вопрос 2 заключается в том, что если я использую FETCH в моем новом соединении, то я все еще не получаю свой 1 запрос, и теперь я не получаю ровно несколько экземпляров A, но несколько экземпляров некоторого видаОболочек со свойством обработчика, которое имеет ссылки на A, обозначаемое как EntityA _ $$ _ jvstbc0_4 @.
Просто чтобы дать некоторое представление о структуре базы данных, я более чем уверен, что эта схема начиналась как многозначная.много отношений с таблицей поиска, являющейся EntityB между EntityA и EntityC.Я могу попытаться решить проблему, используя JoinTable для присоединения EntityC на partitionKey и id EntityB, в то время как EntityA имеет ключ для разделения и его Id для сопоставления на EntityB.Однако я не очень надеюсь на это решение, так как EntityB со временем был заражен другими столбцами, которые нужно выбрать uppon, и я не уверен, как мне это сделать.
ОБНОВЛЕНИЕ 1 : Я вижу, что когда объединение FETCH используется для cs, оно увеличивает результирующий SQL-выбор столбцами, которые необходимы, то есть для заполнения дочерних элементов cs.Запустив запрос вручную, я получаю правильно сумму детей в виде строк.Что имеет смысл в SQL, но hibernate должен был бы агрегировать дополнительные строки на основе их свойств.Правильно, без объединения FETCH Я получаю только строки, равные количеству a.Поэтому, во-вторых, мне нужно как-то указать Hibernate на агрегирование вручную (?)
UPDATE 2 : смена стратегии.Вместо того, чтобы начинать следовать логике SQL, нам лучше ответить на следующий вопрос: какой класс / сущность даст нам гранулярность, которую мы ищем.В предыдущих примерах мы начинали с EntityA, пытаясь ограничить его дочерние элементы, чтобы они соответствовали нашим ожидаемым результатам.Однако, как уже указывалось, это фактически повреждение объектов.Вы не можете «ограничить» детей, потому что все они принадлежат сущности, и, выбирая подмножество их, вы рискуете удалить (?) Данные.Таким образом, подход должен заключаться в том, чтобы получить интересующие нас дочерние объекты, которые указывают на родительские объекты.То, что мы не изменяем данные.Итак, вот запрос, который возвращает правильное количество объекта.Нет явных или необъяснимых кратностей:
"select c " +
"from EntityC c " +
"inner join c.EntityCPk.EntityB.EntityD latest " +
"join latest.EntityB.EntityXXX xxx " +
"join latest.EntityB.EntityYYY yyy " +
"join fetch c.EntityCPk.EntityB " +
"where latest.EntityB.EntityA.Id in ( :param ) " +
"and latest.EntityB.aField between :paramA and paramB "
Таким образом, это, кажется, отвечает на вопрос о множественности предыдущих примеров, поскольку каждая строка основана на «более тонком» дочернем объекте, который разрешает своего родителя через отношение -ToOne.Также нет более опасных псевдонимов в соединении.Есть только еще одна проблема.Это вводит проблему запроса 1 + N для EntityB, от которой я не могу избавиться.