Эффективность стратегии наследования таблиц на подклассы Hibernate - PullRequest
13 голосов
/ 07 января 2010

Я думаю о макете таблицы для иерархии классов, управляемой 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, чтобы вытащить отдельное животное? Я могу вспомнить как минимум два случая, когда это может произойти:

  1. Некоторая другая сущность, имеющая отображение один-к-одному (или один-ко-многим), например поле pet класса Human. Это будет хранить pkey, поэтому, когда Hibernate выбирает объект Human, ему также нужно будет извлечь соответствующий объект Animal. Когда задан ключ животного, какой запрос (и) будет использовать Hibernate для извлечения и снятия с нуля фактических данных Animal, учитывая, что они могут находиться в таблицах Cat или Dog?
  2. HQL, такой как from Animal where name='Rex' (предположим, имена уникальны). Это похоже на вышесказанное в том, что оно позволяет вам идентифицировать строку в таблице суперкласса, но вы не знаете, какую таблицу подкласса нужно проверить для получения дополнительной информации. Позволяет ли HQL даже выдавать запрос from абстрактный класс? (Использование специфичных для подклассов вещей работает хорошо, например, from Cat where miceCaught > 5).

Я могу придумать два способа, которыми это можно сделать в SQL, и ни один из них не выглядит симпатичным. Один из них - запустить запрос exists для каждой таблицы подклассов для данного ключа и затем загрузить из таблицы, которая вернула попадание. В качестве альтернативы Hibernate мог бы выполнить какое-то ужасное объединение запросов объединения во всех таблицах - по сути, имитируя схему таблицы на иерархию, в которой результирующий набор включал бы атрибуты для всех возможных подклассов с отдельными выборками из таблиц подклассов, возвращая null для неактуальных аргументы. В последнем случае, возможно, даже потребуется добавить синтетический столбец дискриминатора, чтобы Hibernate мог знать, какая таблица подклассов действительно вернула строку и, следовательно, в какой класс Java они должны быть проанализированы.


Вещи становятся более опасными, если у вас есть подтипы конкретных типов:

public class Greyhound extends Dog {
   float lifetimeRacingWinnings;
}

Теперь для данного животного pkey могут быть допустимые строки в таблицах Dog и Greyhound, что означает, что мой первый подход ручной проверки класса, соответствующего pkey, получает много жестче.

Причина, по которой я так обеспокоен, заключается в том, что я захочу использовать этот подход в иерархии классов, включающей около 70 классов с максимальной цепочкой вложений 4-5 уровней, поэтому выполнение запроса объединения по всем этим вероятно иметь ужасную производительность. Есть ли у Hibernate какие-то хитрости, чтобы сохранить это относительно быстрым? Или загрузка ссылки на один из этих классов pkey займет много времени?

Ответы [ 3 ]

8 голосов
/ 07 января 2010

Вы обнаружите, что Hibernate пишет запрос для неизвестного типа животных с серией операторов LEFT JOIN, по одному на подкласс.Таким образом, запрос будет замедляться по мере увеличения числа подклассов и будет пытаться вернуть более широкий набор результатов.Так что вы правы, он плохо масштабируется с большими иерархиями классов.

С HQL, да, вы можете напрямую запросить подкласс и получить доступ к его свойствам.Затем он будет обработан одним INNER JOIN.

. Я не пробовал этого с несколькими уровнями наследования.Если вышесказанное еще не оттолкнуло вас, предложите попробовать и посмотреть - вы можете включить отладочный вывод SQL, чтобы посмотреть, что отправляется в базу данных, или просто профилировать вашу базу данных.

3 голосов
/ 07 января 2010

После Полезный ответ Дэвида М. Я решил собрать скелетный тест.

Я создал абстрактный суперкласс, ADTestA и 25 конкретных подклассов в трехуровневой иерархии (я думаю, вы можете угадать их имена). Каждый класс имел одно целочисленное поле с именем, соответствующим его букве - так, например, класс 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 всегда будет включать именно этот вид поиска.

(С другой стороны, альтернативные стратегии также не являются лучами надежды; идти по пути таблицы на иерархию и иметь буквально сотни столбцов в одной таблице тоже не очень эффективно ...)

1 голос
/ 07 января 2010

Пока вы обращаетесь к своей БД только через Hibernate и у вас нет важных данных или вы готовы написать небольшой скрипт миграции, вы сможете принять решение о таблице на подкласс / иерархию довольно поздно в процессе разработки. , В этом прелесть ORM, он абстрагирует структуру базы данных ...

С другой стороны, я большой поклонник "предпочитаю композицию наследованию" ( Предпочитаю композицию наследованию? ), и я весьма сомневаюсь, что модель с 70 классами на 4-5 уровнях не может быть упрощено ... но я позволю вам подумать над этим, в конце концов, я не знаю, с какой проблемой вы пытаетесь соваться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...