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:
2026-10-24 10:02:28 +00:00
parent 833f3e1c31
commit 807f681ed1
44 changed files with 1533 additions and 0 deletions
+61
View File
@@ -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)
);
+31
View File
@@ -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;
+46
View File
@@ -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