У меня есть иерархия JPA, в которой один объект содержит набор абстрактного типа, реализуемый различным количеством подклассов. Проблема в том, что когда Hibernate пытается загрузить эти классы, он генерирует запрос, который обычно получает главный объект, но (лениво) загружает эти дополнительные объекты с помощью объединения (в подвыборке). Что-то вроде:
select ...
from (
select ...
from subtable1
union
select ...
from subtable2
)
where parent.id = ?
Это чрезвычайно неэффективный запрос (с 10k строк в родительской таблице и 1 из каждого подкласса для всех строк, получение всей иерархии для 100 или около того записей в родительском классе занимает несколько минут, активный запрос занимает до 50 минут).
Чего я не понимаю, так это того, почему они не являются просто левыми соединениями из родительской таблицы (select ... from parent p left join subtable1 ... left join subtable2 ...
), поскольку это принесет все необходимые данные за доли секунды. Можно ли как-нибудь избежать этого глупого запроса на объединение?
Пока что единственное жизнеспособное решение, которое я видел, - это изменить тип наследования с TABLE_PER_CLASS на SINGLE_TABLE, но компромиссы для этого не идеальны (включая невозможность использовать «not null» в столбцах, проблемы с обслуживанием) поскольку расширенные классы будут принадлежать разным группам, которые могут даже работать с разными версиями программного обеспечения и т. д.).
На всякий случай, если кому-то это поможет, я вставлю реальный запрос, сгенерированный Hibernate, с этим отношением (JPQL в качестве комментария в начале), этот конкретный запрос выполнялся самостоятельно за 34 секунды (0,6 секунды). при перезаписи как левый присоединяется):
/* select distinct p
from PersonJpa p left join fetch p.addressOfRecords a
left join fetch a.extensions e left join fetch a.commsType c
left join fetch p.credentials cr left join fetch cr.type t
left join fetch p.serviceProfiles s left join fetch s.extensions ex
where p.id in (?1) */
SELECT DISTINCT personjpa0_.person_id AS person1_902_0_,
addressofr1_.aor_id AS aor1_899_1_,
extensions2_.id AS id903_2_,
commstypej3_.comms_type AS comms1_908_3_,
credential4_.credentials_id AS credenti1_905_4_,
credential5_.credentials_type AS credenti1_904_5_,
servicepro6_.service_profile_id AS service1_901_6_,
extensions7_.id AS id907_7_,
personjpa0_.display_name AS display2_902_0_,
personjpa0_.first_name AS first3_902_0_,
personjpa0_.initials AS initials902_0_,
personjpa0_.last_name AS last5_902_0_,
personjpa0_.title AS title902_0_,
personjpa0_.user_name AS user7_902_0_,
addressofr1_.comms_type AS comms9_899_1_,
addressofr1_.display_form AS display2_899_1_,
addressofr1_.domain AS domain899_1_,
addressofr1_.label AS label899_1_,
addressofr1_.parameters AS parameters899_1_,
addressofr1_.person_id AS person10_899_1_,
addressofr1_.preference AS preference899_1_,
addressofr1_.send_html AS send7_899_1_,
addressofr1_.service_profile_id AS service11_899_1_,
addressofr1_.user_part AS user8_899_1_,
addressofr1_.person_id AS person10_0__,
addressofr1_.aor_id AS aor1_0__,
extensions2_.aor_id AS aor2_903_2_,
extensions2_.resolution_status AS resolution1_910_2_,
extensions2_.certificate_serial_number AS certific1_914_2_,
extensions2_.device_type_name AS device2_914_2_,
extensions2_.provisioned AS provisio3_914_2_,
extensions2_.provisioning_token AS provisio4_914_2_,
extensions2_.provisioning_unique_id AS provisio5_914_2_,
extensions2_.sip_password AS sip6_914_2_,
extensions2_.username AS username914_2_,
extensions2_.voicemail_long_timeout AS voicemail8_914_2_,
extensions2_.voicemail_short_timeout AS voicemail9_914_2_,
extensions2_.clazz_ AS clazz_2_,
extensions2_.aor_id AS aor2_1__,
extensions2_.id AS id1__,
credential4_.algorithm_name AS algorithm3_905_4_,
credential4_.password AS password905_4_,
credential4_.person_id AS person4_905_4_,
credential4_.realm_id AS realm5_905_4_,
credential4_.credentials_type AS credenti6_905_4_,
credential4_.person_id AS person4_2__,
credential4_.credentials_id AS credenti1_2__,
servicepro6_.name AS name901_6_,
servicepro6_.person_id AS person3_901_6_,
servicepro6_.person_id AS person3_3__,
servicepro6_.service_profile_id AS service1_3__,
extensions7_.service_profile_id AS service2_907_7_,
extensions7_.seqprofile_id AS seqprofile1_911_7_,
extensions7_.certificate_port AS certific1_915_7_,
extensions7_.email_aor_id AS email23_915_7_,
extensions7_.emergency_numbers AS emergency2_915_7_,
extensions7_.from_domain AS from3_915_7_,
extensions7_.outbound_domain AS outbound4_915_7_,
extensions7_.pbx_aor_id AS pbx24_915_7_,
extensions7_.provisioning_delivery AS provisio5_915_7_,
extensions7_.provisioning_host AS provisio6_915_7_,
extensions7_.provisioning_port AS provisio7_915_7_,
extensions7_.server_access_call_control_port AS server8_915_7_,
extensions7_.server_access_host AS server9_915_7_,
extensions7_.server_access_number AS server10_915_7_,
extensions7_.server_access_secure_mode AS server11_915_7_,
extensions7_.sip_domain AS sip12_915_7_,
extensions7_.sip_enabled AS sip13_915_7_,
extensions7_.sip_expires AS sip14_915_7_,
extensions7_.sip_proxy AS sip15_915_7_,
extensions7_.sip_realm AS sip16_915_7_,
extensions7_.sip_transport AS sip17_915_7_,
extensions7_.voicemail_long_timeout AS voicemail18_915_7_,
extensions7_.voicemail_message_server AS voicemail19_915_7_,
extensions7_.voicemail_number AS voicemail20_915_7_,
extensions7_.voicemail_short_timeout AS voicemail21_915_7_,
extensions7_.voicemail_userid AS voicemail22_915_7_,
extensions7_.clazz_ AS clazz_7_,
extensions7_.service_profile_id AS service2_4__,
extensions7_.id AS id4__
FROM cdm.cdm_person AS personjpa0_
LEFT OUTER JOIN
cdm.cdm_address_of_record AS addressofr1_
ON personjpa0_.person_id = addressofr1_.person_id
LEFT OUTER JOIN
(SELECT NULL AS device_type_name,
id,
NULL AS provisioning_unique_id,
aor_id,
NULL AS voicemail_long_timeout,
NULL AS username,
NULL AS certificate_serial_number,
NULL AS provisioning_token,
resolution_status,
NULL AS voicemail_short_timeout,
NULL AS provisioned,
NULL AS sip_password,
1 AS clazz_
FROM cdm.sb_aor_details
UNION
SELECT device_type_name,
id,
provisioning_unique_id,
aor_id,
voicemail_long_timeout,
username,
certificate_serial_number,
provisioning_token,
NULL AS resolution_status,
voicemail_short_timeout,
provisioned,
sip_password,
2 AS clazz_
FROM cdm.fmc_remote_number_address_of_record_extension) AS extensions2_
ON addressofr1_.aor_id = extensions2_.aor_id
LEFT OUTER JOIN
cdm.cdm_comms_type AS commstypej3_
ON addressofr1_.comms_type = commstypej3_.comms_type
LEFT OUTER JOIN
cdm.cdm_credentials AS credential4_
ON personjpa0_.person_id = credential4_.person_id
LEFT OUTER JOIN
cdm.cdm_credentials_type AS credential5_
ON credential4_.credentials_type = credential5_.credentials_type
LEFT OUTER JOIN
cdm.cdm_service_profile AS servicepro6_
ON personjpa0_.person_id = servicepro6_.person_id
LEFT OUTER JOIN
(SELECT NULL AS server_access_host,
NULL AS sip_domain,
NULL AS voicemail_message_server,
NULL AS outbound_domain,
NULL AS emergency_numbers,
NULL AS provisioning_delivery,
id,
NULL AS sip_expires,
NULL AS pbx_aor_id,
NULL AS server_access_call_control_port,
NULL AS provisioning_port,
NULL AS email_aor_id,
NULL AS voicemail_number,
NULL AS voicemail_long_timeout,
NULL AS certificate_port,
NULL AS sip_enabled,
NULL AS sip_transport,
NULL AS server_access_number,
service_profile_id,
NULL AS server_access_secure_mode,
NULL AS voicemail_userid,
seqprofile_id,
NULL AS sip_realm,
NULL AS voicemail_short_timeout,
NULL AS provisioning_host,
NULL AS from_domain,
NULL AS sip_proxy,
1 AS clazz_
FROM cdm.sb_service_profile_details
UNION
SELECT server_access_host,
sip_domain,
voicemail_message_server,
outbound_domain,
emergency_numbers,
provisioning_delivery,
id,
sip_expires,
pbx_aor_id,
server_access_call_control_port,
provisioning_port,
email_aor_id,
voicemail_number,
voicemail_long_timeout,
certificate_port,
sip_enabled,
sip_transport,
server_access_number,
service_profile_id,
server_access_secure_mode,
voicemail_userid,
NULL AS seqprofile_id,
sip_realm,
voicemail_short_timeout,
provisioning_host,
from_domain,
sip_proxy,
2 AS clazz_
FROM cdm.fmc_service_profile_extension) AS extensions7_
ON servicepro6_.service_profile_id = extensions7_.service_profile_id
WHERE personjpa0_.person_id IN (1, 2, [continuous ids], 199, 200);