Эффективность стратегии наследования типа Hibernate для каждого подкласса
Я думаю о макете таблицы для иерархии классов, управляемой Hibernate, и, безусловно, таблица для метода подкласса поражает меня как наиболее подходящего в общем смысле. Однако, анализируя логику, у меня есть некоторые сомнения относительно ее производительности, особенно в том, что количество шкал подклассов.
Чтобы дать очень короткий (и классический) пример, скажем, у вас есть следующие классы:
public abstract class Animal {
int pkey;
String name;
}
public class Dog extends Animal {
long numSlippersChewed; // int is not large enough...
}
public class Cat extends Animal {
short miceCaught; // ... but here int is far bigger than required :-)
}
(Я возвращаю геттеры, сеттеры и сопоставления Hibernate и т.д., просто предположим, что они являются основным очевидным случаем).
Таблицы базы данных для этих объектов имеют смысл, вы получаете хорошую денормализацию и так далее. Однако, какой запрос делает Hibernate, чтобы вытащить отдельное животное? Я могу подумать, по крайней мере, о двух случаях, когда это может произойти:
- Некоторая другая сущность, имеющая взаимно однозначное (или множественное) отображение, такое как поле
pet
класса Human
. Это сохранит ключ pkey, поэтому, когда Hibernate выбирает объект Human, ему нужно будет также получить соответствующий объект Animal
. Когда задан pkey животного, какой запрос будет использовать Hibernate для извлечения и развязывания фактических данных Animal, учитывая, что он может находиться в таблицах Cat
или Dog
?
- HQL, например
from Animal where name='Rex'
(пусть имена предполагаются уникальными). Это похоже на вышесказанное в том, что оно позволяет вам идентифицировать строку в таблице суперкласса, но вы не знаете, какую таблицу подкласса проверять для получения дополнительной информации. Предоставляет ли HQL запрос на выпуск from
абстрактного класса? (Использование подкласса конкретного материала работает хорошо, например, from Cat where miceCaught > 5
).
Я могу подумать о двух способах, которые это может быть сделано в SQL, и ни один из них не выглядит симпатичным. Один из них - запустить запрос exists
для каждой таблицы подкласса для заданного ключа p, а затем загрузить из таблицы, которая вернула хит. В качестве альтернативы, Hibernate может выполнить какой-то ужасный союзный запрос, соединяющий во всех таблицах, - по существу, имитируя схему таблицы за иерархию, поскольку в результирующий набор будут включены атрибуты для всех возможных подклассов с отдельными выборами из таблиц подклассов, возвращающих null
для нерелевантных аргументы. В последнем случае, вероятно, даже потребуется добавить столбец синтетического дискриминатора, чтобы Hibernate мог знать, какая таблица подкласса фактически вернула строку и, следовательно, какой класс Java они должны быть проанализированы.
Вещи становятся более волосатыми, если у вас есть подтипы конкретных типов:
public class Greyhound extends Dog {
float lifetimeRacingWinnings;
}
Теперь для заданного животного pkey могут быть допустимые строки в таблицах Dog
и Greyhound
, что означает, что мой первый подход к проверке вручную класса, который соответствует pkey, получает намного сложнее.
Причина, по которой я так беспокоюсь, заключается в том, что я хочу использовать этот подход в иерархии классов с примерно 70 классами с максимальной цепочкой вложенности из 4-5 уровней, поэтому выполнение запроса объединения на все это возможно иметь ужасную производительность. Есть ли у Hibernate какие-либо трюки в рукаве, чтобы сохранить это относительно результативным? Или загружает ссылку на один из этих классов с помощью pkey, который займет много времени?
Ответы
Ответ 1
Вы увидите, что Hibernate записывает запрос для неизвестного типа животных с помощью ряда операторов LEFT JOIN
, по одному на подкласс. Таким образом, запрос будет замедляться по мере увеличения количества подклассов и будет пытаться вернуть все более широкий набор результатов. Таким образом, вы правы, он не очень хорошо масштабируется с помощью иерархии больших классов.
С помощью HQL да, вы можете напрямую запросить подкласс и получить доступ к его свойствам. Затем будет отображаться один INNER JOIN
.
Я не пробовал это с несколькими уровнями наследования. Если вы не указали это выше, предложите попробовать и посмотрите - вы можете включить SQL-отладочный вывод, чтобы увидеть, что отправляется в базу данных, или просто профиль вашей базы данных.
Ответ 2
После Дэвид М полезный ответ Я решил бросить скелетный тест.
Я создал абстрактный суперкласс, ADTestA
и 25 конкретных подклассов в трехуровневой иерархии (я ожидаю, что вы сможете угадать их имена). Каждый класс имел одно целое поле с именем, соответствующим его букве - так, например, class ADTestG
имеет одно поле int g
в дополнение к полю b
, которое он наследует от своего непосредственного родителя ADTestB
, и поля pkey
и a
из верхнего суперкласса верхнего уровня.
Выдача запроса HQL from ADTestA where pkey=1
привела к следующему SQL:
select adtesta0_.pkey as pkey0_, adtesta0_.a as a0_, adtesta0_1_.b as b1_,
adtesta0_2_.c as c2_, adtesta0_3_.d as d3_, adtesta0_4_.e as e4_,
adtesta0_5_.f as f5_, adtesta0_6_.g as g6_, adtesta0_7_.h as h7_,
adtesta0_8_.i as i8_, adtesta0_9_.j as j9_, adtesta0_10_.k as k10_,
adtesta0_11_.l as l11_, adtesta0_12_.m as m12_, adtesta0_13_.n as n13_,
adtesta0_14_.o as o14_, adtesta0_15_.p as p15_, adtesta0_16_.q as q16_,
adtesta0_17_.r as r17_, adtesta0_18_.s as s18_, adtesta0_19_.t as t19_,
adtesta0_20_.u as u20_, adtesta0_21_.v as v21_, adtesta0_22_.w as w22_,
adtesta0_23_.x as x23_, adtesta0_24_.y as y24_, adtesta0_25_.z as z25_,
case
when adtesta0_6_.pkey is not null then 6
when adtesta0_7_.pkey is not null then 7
when adtesta0_8_.pkey is not null then 8
when adtesta0_9_.pkey is not null then 9
when adtesta0_10_.pkey is not null then 10
when adtesta0_11_.pkey is not null then 11
when adtesta0_12_.pkey is not null then 12
when adtesta0_13_.pkey is not null then 13
when adtesta0_14_.pkey is not null then 14
when adtesta0_15_.pkey is not null then 15
when adtesta0_16_.pkey is not null then 16
when adtesta0_17_.pkey is not null then 17
when adtesta0_18_.pkey is not null then 18
when adtesta0_19_.pkey is not null then 19
when adtesta0_20_.pkey is not null then 20
when adtesta0_21_.pkey is not null then 21
when adtesta0_22_.pkey is not null then 22
when adtesta0_23_.pkey is not null then 23
when adtesta0_24_.pkey is not null then 24
when adtesta0_25_.pkey is not null then 25
when adtesta0_1_.pkey is not null then 1
when adtesta0_2_.pkey is not null then 2
when adtesta0_3_.pkey is not null then 3
when adtesta0_4_.pkey is not null then 4
when adtesta0_5_.pkey is not null then 5
when adtesta0_.pkey is not null then 0
end as clazz_
from ADTestA adtesta0_
left outer join ADTestB adtesta0_1_ on adtesta0_.pkey=adtesta0_1_.pkey
left outer join ADTestC adtesta0_2_ on adtesta0_.pkey=adtesta0_2_.pkey
left outer join ADTestD adtesta0_3_ on adtesta0_.pkey=adtesta0_3_.pkey
left outer join ADTestE adtesta0_4_ on adtesta0_.pkey=adtesta0_4_.pkey
left outer join ADTestF adtesta0_5_ on adtesta0_.pkey=adtesta0_5_.pkey
left outer join ADTestG adtesta0_6_ on adtesta0_.pkey=adtesta0_6_.pkey
left outer join ADTestH adtesta0_7_ on adtesta0_.pkey=adtesta0_7_.pkey
left outer join ADTestI adtesta0_8_ on adtesta0_.pkey=adtesta0_8_.pkey
left outer join ADTestJ adtesta0_9_ on adtesta0_.pkey=adtesta0_9_.pkey
left outer join ADTestK adtesta0_10_ on adtesta0_.pkey=adtesta0_10_.pkey
left outer join ADTestL adtesta0_11_ on adtesta0_.pkey=adtesta0_11_.pkey
left outer join ADTestM adtesta0_12_ on adtesta0_.pkey=adtesta0_12_.pkey
left outer join ADTestN adtesta0_13_ on adtesta0_.pkey=adtesta0_13_.pkey
left outer join ADTestO adtesta0_14_ on adtesta0_.pkey=adtesta0_14_.pkey
left outer join ADTestP adtesta0_15_ on adtesta0_.pkey=adtesta0_15_.pkey
left outer join ADTestQ adtesta0_16_ on adtesta0_.pkey=adtesta0_16_.pkey
left outer join ADTestR adtesta0_17_ on adtesta0_.pkey=adtesta0_17_.pkey
left outer join ADTestS adtesta0_18_ on adtesta0_.pkey=adtesta0_18_.pkey
left outer join ADTestT adtesta0_19_ on adtesta0_.pkey=adtesta0_19_.pkey
left outer join ADTestU adtesta0_20_ on adtesta0_.pkey=adtesta0_20_.pkey
left outer join ADTestV adtesta0_21_ on adtesta0_.pkey=adtesta0_21_.pkey
left outer join ADTestW adtesta0_22_ on adtesta0_.pkey=adtesta0_22_.pkey
left outer join ADTestX adtesta0_23_ on adtesta0_.pkey=adtesta0_23_.pkey
left outer join ADTestY adtesta0_24_ on adtesta0_.pkey=adtesta0_24_.pkey
left outer join ADTestZ adtesta0_25_ on adtesta0_.pkey=adtesta0_25_.pkey
where adtesta0_.pkey=1
Это не очень красиво и соответствует эффективному моделированию таблицы для каждой иерархии, которую, я надеюсь, можно было бы избежать.
Итак, похоже, что подобные запросы будут очень дорогими. Я буду думать о том, как часто они понадобятся (сравнивая, скажем, с тем, что я хочу экземпляр ADTestP
и прошу одного из них сразу с места в карьер, который только соединяется в требуемые родительские таблицы). У меня есть чувство, однако, что это будет неизбежно со ссылками на сущности; другими словами, взаимно однозначное отображение из поля типа ADTestA
всегда будет включать именно такой поиск.
(С другой стороны, альтернативные стратегии не являются сияющими маяками надежды: переход по трассе "таблица за иерархию" и буквально сотни столбцов в одной таблице не очень эффективен...)
Ответ 3
Пока вы получаете доступ к своей БД только через Hibernate, и у вас либо нет важных данных, либо готовы написать небольшую миграцию script, вы должны иметь возможность принимать решение о таблице для каждого подкласса/иерархии довольно поздно в своем процесс развития. Что красота ORM, она абстрагирует структуру базы данных...
С другой стороны, я большой поклонник "предпочитаю композицию над наследованием" (Предпочитаю композицию над наследованием?), и я довольно сомневаюсь в том, что модель с 70 классами более 4-5 уровней не могут быть упрощены... но я позволю вам подумать о себе над этим, ведь я не знаю, какой пробой вы пытаетесь справиться.