Add inheritance module: SINGLE_TABLE vs JOINED vs TABLE_PER_CLASS on Hibernate 7.4.13.Final
Same payment hierarchy mapped three ways plus @MappedSuperclass, run with plain Hibernate on H2. Tests assert and write the generated DDL, the INSERT/SELECT SQL, row/insert/FK/nullability counts and the three failures (NOT NULL on a SINGLE_TABLE subclass column, IDENTITY with TABLE_PER_CLASS, querying a @MappedSuperclass); Bench writes indicative polymorphic-query timings. No docs folder: depth lives in the post.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
-- DDL generated by Hibernate 7.4.13.Final for H2, strategy JOINED
|
||||
-- tables created: 5
|
||||
|
||||
|
||||
create sequence jn_order_seq start with 1 increment by 50;
|
||||
|
||||
create sequence jn_payment_seq start with 1 increment by 50;
|
||||
|
||||
create table jn_bank_transfer_payment (
|
||||
payment_id bigint not null,
|
||||
bank_code varchar(11),
|
||||
iban varchar(34) not null,
|
||||
primary key (payment_id)
|
||||
);
|
||||
|
||||
create table jn_card_payment (
|
||||
card_last4 varchar(4) not null,
|
||||
payment_id bigint not null,
|
||||
network varchar(16),
|
||||
primary key (payment_id)
|
||||
);
|
||||
|
||||
create table jn_order (
|
||||
id bigint not null,
|
||||
payment_id bigint not null,
|
||||
reference varchar(20) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table jn_payment (
|
||||
amount numeric(12,2) not null,
|
||||
currency varchar(3) not null,
|
||||
id bigint not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table jn_wallet_payment (
|
||||
payment_id bigint not null,
|
||||
provider varchar(24) not null,
|
||||
primary key (payment_id)
|
||||
);
|
||||
|
||||
alter table if exists jn_bank_transfer_payment
|
||||
add constraint FK7mx6hp714jwtc7cbp5oboi9b5
|
||||
foreign key (payment_id)
|
||||
references jn_payment;
|
||||
|
||||
alter table if exists jn_card_payment
|
||||
add constraint FK2pe4xdfpn9xujq97a21nj00nr
|
||||
foreign key (payment_id)
|
||||
references jn_payment;
|
||||
|
||||
alter table if exists jn_order
|
||||
add constraint FKschxw26qcnlfcgeyirv55ttkl
|
||||
foreign key (payment_id)
|
||||
references jn_payment;
|
||||
|
||||
alter table if exists jn_wallet_payment
|
||||
add constraint FK9cf00xcddbti8l83t1s5ptu3h
|
||||
foreign key (payment_id)
|
||||
references jn_payment;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- DDL generated by Hibernate 7.4.13.Final for H2, @MappedSuperclass AuditedBase + Invoice + Receipt
|
||||
|
||||
|
||||
create sequence mp_invoice_SEQ start with 1 increment by 50;
|
||||
|
||||
create sequence mp_receipt_SEQ start with 1 increment by 50;
|
||||
|
||||
create table mp_invoice (
|
||||
version integer not null,
|
||||
created_at timestamp(6) with time zone not null,
|
||||
id bigint not null,
|
||||
number varchar(20) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table mp_receipt (
|
||||
version integer not null,
|
||||
created_at timestamp(6) with time zone not null,
|
||||
id bigint not null,
|
||||
note varchar(60),
|
||||
primary key (id)
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
-- DDL generated by Hibernate 7.4.13.Final for H2, strategy SINGLE_TABLE
|
||||
-- tables created: 2
|
||||
|
||||
|
||||
create sequence st_order_seq start with 1 increment by 50;
|
||||
|
||||
create sequence st_payment_seq start with 1 increment by 50;
|
||||
|
||||
create table st_order (
|
||||
id bigint not null,
|
||||
payment_id bigint not null,
|
||||
reference varchar(20) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table st_payment (
|
||||
amount numeric(12,2) not null,
|
||||
card_last4 varchar(4),
|
||||
currency varchar(3) not null,
|
||||
id bigint not null,
|
||||
bank_code varchar(11),
|
||||
payment_type varchar(12) not null check ((payment_type in ('CARD','BANK','WALLET'))),
|
||||
network varchar(16),
|
||||
provider varchar(24),
|
||||
iban varchar(34),
|
||||
primary key (id),
|
||||
constraint st_payment_subclass_columns check ((payment_type <> 'CARD' or card_last4 is not null) and (payment_type <> 'BANK' or iban is not null) and (payment_type <> 'WALLET' or provider is not null))
|
||||
);
|
||||
|
||||
alter table if exists st_order
|
||||
add constraint FKdi3y406uyk3cm6po29lmjirt5
|
||||
foreign key (payment_id)
|
||||
references st_payment;
|
||||
@@ -0,0 +1,40 @@
|
||||
-- DDL generated by Hibernate 7.4.13.Final for H2, strategy TABLE_PER_CLASS
|
||||
-- tables created: 4
|
||||
|
||||
|
||||
create sequence tp_order_seq start with 1 increment by 50;
|
||||
|
||||
create sequence tp_payment_seq start with 1 increment by 50;
|
||||
|
||||
create table tp_bank_transfer_payment (
|
||||
amount numeric(12,2) not null,
|
||||
currency varchar(3) not null,
|
||||
id bigint not null,
|
||||
bank_code varchar(11),
|
||||
iban varchar(34) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table tp_card_payment (
|
||||
amount numeric(12,2) not null,
|
||||
card_last4 varchar(4) not null,
|
||||
currency varchar(3) not null,
|
||||
id bigint not null,
|
||||
network varchar(16),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table tp_order (
|
||||
id bigint not null,
|
||||
payment_id bigint not null,
|
||||
reference varchar(20) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table tp_wallet_payment (
|
||||
amount numeric(12,2) not null,
|
||||
currency varchar(3) not null,
|
||||
id bigint not null,
|
||||
provider varchar(24) not null,
|
||||
primary key (id)
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
-- strategy JOINED, Hibernate 7.4.13.Final
|
||||
|
||||
== persist 3 payments and 1 order: 7 INSERT statements
|
||||
insert into jn_payment (amount,currency,id) values (?,?,?);
|
||||
insert into jn_card_payment (card_last4,network,payment_id) values (?,?,?);
|
||||
insert into jn_payment (amount,currency,id) values (?,?,?);
|
||||
insert into jn_bank_transfer_payment (bank_code,iban,payment_id) values (?,?,?);
|
||||
insert into jn_payment (amount,currency,id) values (?,?,?);
|
||||
insert into jn_wallet_payment (provider,payment_id) values (?,?);
|
||||
insert into jn_order (payment_id,reference,id) values (?,?,?);
|
||||
|
||||
== select p from JnPayment p (polymorphic, returns 3 rows)
|
||||
select p1_0.id,case when p1_2.payment_id is not null then 1 when p1_1.payment_id is not null then 2 when p1_3.payment_id is not null then 3 end,p1_0.amount,p1_0.currency,p1_1.bank_code,p1_1.iban,p1_2.card_last4,p1_2.network,p1_3.provider
|
||||
from jn_payment p1_0
|
||||
left join jn_bank_transfer_payment p1_1 on p1_0.id=p1_1.payment_id
|
||||
left join jn_card_payment p1_2 on p1_0.id=p1_2.payment_id
|
||||
left join jn_wallet_payment p1_3 on p1_0.id=p1_3.payment_id
|
||||
order by p1_0.id;
|
||||
|
||||
== from JnCardPayment (one subclass, returns 1 row)
|
||||
select cp1_0.payment_id,cp1_1.amount,cp1_1.currency,cp1_0.card_last4,cp1_0.network
|
||||
from jn_card_payment cp1_0
|
||||
join jn_payment cp1_1 on cp1_0.payment_id=cp1_1.id;
|
||||
|
||||
== select o from JnOrder o join fetch o.payment (association to the root)
|
||||
select po1_0.id,po1_0.payment_id,p1_0.id,case when p1_2.payment_id is not null then 1 when p1_1.payment_id is not null then 2 when p1_3.payment_id is not null then 3 end,p1_0.amount,p1_0.currency,p1_1.bank_code,p1_1.iban,p1_2.card_last4,p1_2.network,p1_3.provider,po1_0.reference
|
||||
from jn_order po1_0
|
||||
join jn_payment p1_0 on p1_0.id=po1_0.payment_id
|
||||
left join jn_bank_transfer_payment p1_1 on p1_0.id=p1_1.payment_id
|
||||
left join jn_card_payment p1_2 on p1_0.id=p1_2.payment_id
|
||||
left join jn_wallet_payment p1_3 on p1_0.id=p1_3.payment_id;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- strategy SINGLE_TABLE, Hibernate 7.4.13.Final
|
||||
|
||||
== persist 3 payments and 1 order: 4 INSERT statements
|
||||
insert into st_payment (amount,currency,card_last4,network,payment_type,id) values (?,?,?,?,'CARD',?);
|
||||
insert into st_payment (amount,currency,bank_code,iban,payment_type,id) values (?,?,?,?,'BANK',?);
|
||||
insert into st_payment (amount,currency,provider,payment_type,id) values (?,?,?,'WALLET',?);
|
||||
insert into st_order (payment_id,reference,id) values (?,?,?);
|
||||
|
||||
== select p from StPayment p (polymorphic, returns 3 rows)
|
||||
select p1_0.id,p1_0.payment_type,p1_0.amount,p1_0.currency,p1_0.bank_code,p1_0.iban,p1_0.card_last4,p1_0.network,p1_0.provider
|
||||
from st_payment p1_0
|
||||
order by p1_0.id;
|
||||
|
||||
== from StCardPayment (one subclass, returns 1 row)
|
||||
select cp1_0.id,cp1_0.amount,cp1_0.currency,cp1_0.card_last4,cp1_0.network
|
||||
from st_payment cp1_0
|
||||
where cp1_0.payment_type='CARD';
|
||||
|
||||
== select o from StOrder o join fetch o.payment (association to the root)
|
||||
select po1_0.id,po1_0.payment_id,p1_0.id,p1_0.payment_type,p1_0.amount,p1_0.currency,p1_0.bank_code,p1_0.iban,p1_0.card_last4,p1_0.network,p1_0.provider,po1_0.reference
|
||||
from st_order po1_0
|
||||
join st_payment p1_0 on p1_0.id=po1_0.payment_id;
|
||||
@@ -0,0 +1,35 @@
|
||||
-- strategy TABLE_PER_CLASS, Hibernate 7.4.13.Final
|
||||
|
||||
== persist 3 payments and 1 order: 4 INSERT statements
|
||||
insert into tp_card_payment (amount,currency,card_last4,network,id) values (?,?,?,?,?);
|
||||
insert into tp_bank_transfer_payment (amount,currency,bank_code,iban,id) values (?,?,?,?,?);
|
||||
insert into tp_wallet_payment (amount,currency,provider,id) values (?,?,?,?);
|
||||
insert into tp_order (payment_id,reference,id) values (?,?,?);
|
||||
|
||||
== select p from TpPayment p (polymorphic, returns 3 rows)
|
||||
select p1_0.id,p1_0.clazz_,p1_0.amount,p1_0.currency,p1_0.bank_code,p1_0.iban,p1_0.card_last4,p1_0.network,p1_0.provider
|
||||
from (select amount, card_last4, currency, id, network, null as bank_code, null as iban, null as provider, 1 as clazz_
|
||||
from tp_card_payment
|
||||
union all
|
||||
select amount, null as card_last4, currency, id, null as network, bank_code, iban, null as provider, 2 as clazz_
|
||||
from tp_bank_transfer_payment
|
||||
union all
|
||||
select amount, null as card_last4, currency, id, null as network, null as bank_code, null as iban, provider, 3 as clazz_
|
||||
from tp_wallet_payment) p1_0
|
||||
order by p1_0.id;
|
||||
|
||||
== from TpCardPayment (one subclass, returns 1 row)
|
||||
select cp1_0.id,cp1_0.amount,cp1_0.currency,cp1_0.card_last4,cp1_0.network
|
||||
from tp_card_payment cp1_0;
|
||||
|
||||
== select o from TpOrder o join fetch o.payment (association to the root)
|
||||
select po1_0.id,po1_0.payment_id,p1_0.id,p1_0.clazz_,p1_0.amount,p1_0.currency,p1_0.bank_code,p1_0.iban,p1_0.card_last4,p1_0.network,p1_0.provider,po1_0.reference
|
||||
from tp_order po1_0
|
||||
join (select amount, card_last4, currency, id, network, null as bank_code, null as iban, null as provider, 1 as clazz_
|
||||
from tp_card_payment
|
||||
union all
|
||||
select amount, null as card_last4, currency, id, null as network, bank_code, iban, null as provider, 2 as clazz_
|
||||
from tp_bank_transfer_payment
|
||||
union all
|
||||
select amount, null as card_last4, currency, id, null as network, null as bank_code, null as iban, provider, 3 as clazz_
|
||||
from tp_wallet_payment) p1_0 on p1_0.id=po1_0.payment_id;
|
||||
@@ -0,0 +1,46 @@
|
||||
Hibernate 7.4.13.Final, H2 in memory
|
||||
|
||||
== SINGLE_TABLE
|
||||
INSERT statements to save 3 payments + 1 order: 4
|
||||
tables and row counts:
|
||||
st_order = 1
|
||||
st_payment = 3
|
||||
IS_NULLABLE for card_last4 in st_payment: YES
|
||||
foreign keys on st_order: 1
|
||||
|
||||
discriminator values stored: [BANK, CARD, WALLET]
|
||||
raw INSERT of a CARD row with no card_last4: Check constraint violation: "ST_PAYMENT_SUBCLASS_COLUMNS: "; SQL statement:
|
||||
insert into st_payment (id, amount, currency, payment_type) values (900, 1.00, 'EUR', 'CARD') [23513-240]
|
||||
|
||||
== JOINED
|
||||
INSERT statements to save 3 payments + 1 order: 7
|
||||
tables and row counts:
|
||||
jn_bank_transfer_payment = 1
|
||||
jn_card_payment = 1
|
||||
jn_order = 1
|
||||
jn_payment = 3
|
||||
jn_wallet_payment = 1
|
||||
IS_NULLABLE for card_last4 in jn_card_payment: NO
|
||||
foreign keys on jn_order: 1
|
||||
|
||||
== TABLE_PER_CLASS
|
||||
INSERT statements to save 3 payments + 1 order: 4
|
||||
tables and row counts:
|
||||
tp_bank_transfer_payment = 1
|
||||
tp_card_payment = 1
|
||||
tp_order = 1
|
||||
tp_wallet_payment = 1
|
||||
IS_NULLABLE for card_last4 in tp_card_payment: NO
|
||||
foreign keys on tp_order: 0
|
||||
|
||||
== SINGLE_TABLE with nullable = false on subclass columns
|
||||
IS_NULLABLE: [A_VALUE | NO, B_VALUE | NO, ID | NO, KIND | NO]
|
||||
persist(new SnA()): org.hibernate.exception.ConstraintViolationException: could not execute statement [NULL not allowed for column "B_VALUE"; SQL statement:
|
||||
insert into sn_root (a_value,kind,id) values (?,'A',?) [23502-240]] [insert into sn_root (a_value,kind,id) values (?,'A',?)]
|
||||
|
||||
== TABLE_PER_CLASS with GenerationType.IDENTITY
|
||||
org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: com.ankurm.inheritance.TpcIdentityCard
|
||||
|
||||
== querying a @MappedSuperclass
|
||||
tables: [mp_invoice, mp_receipt]
|
||||
org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'AuditedBase'
|
||||
@@ -0,0 +1,20 @@
|
||||
INDICATIVE ONLY - timings drift between runs and machines; nothing here is asserted.
|
||||
Hibernate 7.4.13.Final, H2 in memory, JDK 25.0.5+8-LTS, 2 vCPU
|
||||
150000 payments (50000 card, 50000 bank transfer, 50000 wallet), read through a StatelessSession, median of 15 runs after 5 warm-ups
|
||||
|
||||
strategy query rows median ms
|
||||
single-table all payments (polymorphic) 150000 247.2
|
||||
single-table one subclass (CardPayment) 50000 82.4
|
||||
single-table count where amount > 500 (root col) 1 18.4
|
||||
single-table first 50 by id (polymorphic) 50 0.3
|
||||
|
||||
joined all payments (polymorphic) 150000 401.5
|
||||
joined one subclass (CardPayment) 50000 107.1
|
||||
joined count where amount > 500 (root col) 1 21.6
|
||||
joined first 50 by id (polymorphic) 50 0.3
|
||||
|
||||
table-per-class all payments (polymorphic) 150000 422.0
|
||||
table-per-class one subclass (CardPayment) 50000 82.1
|
||||
table-per-class count where amount > 500 (root col) 1 56.0
|
||||
table-per-class first 50 by id (polymorphic) 50 165.8
|
||||
|
||||
Reference in New Issue
Block a user