Skip to content
Back to skills

Inheritance Mapping Strategies

ASecurity

Mapping a subtype hierarchy onto tables — single table, class table (joined), concrete table per class — and deciding whether the hierarchy should exist at all. Use when an @Inheritance strategy is being chosen, when a single-table mapping is forcing every subtype's columns to be nullable, when a joined mapping's polymorphic query joins six tables to render a list, when adding a subtype requires a migration, when a discriminator column has drifted from the class names, when polymorphic querie...

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
developmentgojavasqlgitapidatabaseperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add robsonkades/agent-skills --skill inheritance-mapping-strategies --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Inheritance Mapping Strategies?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Inheritance Mapping Strategies
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robsonkades-inheritance-mapping-strategies/badge)](https://www.skillsdirectory.com/skills/robsonkades-inheritance-mapping-strategies)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: inheritance-mapping-strategies
description: >
  Mapping a subtype hierarchy onto tables — single table, class table (joined), concrete
  table per class — and deciding whether the hierarchy should exist at all. Use when an
  @Inheritance strategy is being chosen, when a single-table mapping is forcing every
  subtype's columns to be nullable, when a joined mapping's polymorphic query joins six
  tables to render a list, when adding a subtype requires a migration, when a discriminator
  column has drifted from the class names, when polymorphic queries are slow or return the
  wrong rows, when a hierarchy exists only to share three fields, or when composition would
  serve better than subtyping. Does not cover associations, identity and value mapping
  (orm-structural-mapping), where the mapping instructions live (metadata-mapping), the
  runtime fetch behaviour (orm-behavioral-patterns), or the domain question of whether
  subtypes model the business correctly (domain-logic-organization).
---

# Inheritance Mapping Strategies

## Purpose

Choose a subtype mapping with its query cost, its constraint capability and its evolution
cost all on the table. A Java subtype hierarchy does not determine one portable relational
representation: each strategy trades query shape against constraints and schema evolution.
Revisiting a populated mapping can require a migration even when the Java change is small.

Challenge hierarchies introduced only to share fields, but do not infer that a data-oriented
subtype is invalid: substitutability and distinct invariants matter as well as methods.

Inspect Java release/toolchain, persistence API (`javax` versus `jakarta`), provider/version,
dialect, generated SQL and managed migrations first. Guidance is checked against Jakarta
Persistence 3.2; TABLE_PER_CLASS support is optional there. Examples are partial sketches,
not complete entities. Java sealed types require 17, pattern-switch requires 21 without
preview; neither requires upgrading the target project.

## The three strategies

```text
Single table          one table, all subtypes, a discriminator column.
(SINGLE_TABLE)        Subtype-specific columns are commonly nullable;
                      discriminator-aware CHECK constraints can enforce groups.

Class table           one table per class in the hierarchy, joined by the
(JOINED)              shared primary key. Subtype entity loads generally join
                      through mapped levels; projections may not.

Concrete table        one table per concrete entity class, including a concrete
(TABLE_PER_CLASS)     root, each with its own and inherited columns. No table contains
                      all hierarchy rows; polymorphic queries typically use UNIONs
                      or separate queries. A root table alone is not a hierarchy FK target.
```

## Workflow

Reuse the consumer queries, existing schema/constraints, public subtype contracts and change
goal before choosing annotations. Keep a mapping that already satisfies them. Ask only about
unresolved query, integrity or rollout requirements that could change the recommendation;
continue inspecting SQL and mappings while those answers are pending.

1. **Check the hierarchy's contract.** Inspect substitutability, identity and invariants as
   well as behaviour. Field-only differences invite comparison with composition but do not
   alone disprove a subtype or justify breaking existing consumers.
2. **Count the shape.** How many subtypes, how many columns each, how many are
   subtype-specific? Measure populated row width, indexes, constraints and query plans; column count alone
   does not determine cost.
3. **Establish the query mix.** Include selected fields, root versus subtype queries, write
   paths and incoming references. Compare a projection or index within the existing mapping
   with changing storage; polymorphic entity reads often favour single table, while joined
   can simplify subtype-local constraints. Check the actual provider SQL and database plan.
4. **Establish the database-enforced invariants.** Include required subtype fields and whether
   business keys are unique per subtype or across the hierarchy. Single table may need conditional
   checks; concrete tables may need shared uniqueness enforcement. Price those mechanisms before
   choosing a mapping, independently of generated primary-key uniqueness.
5. **Establish the evolution rate.** A hierarchy that gains a subtype every quarter pays a
   migration per subtype under joined and concrete table, and may require columns, discriminator checks and indexes under single table.
6. **Decide or retain the mapping, and pin discriminator values** where used, so a class
   rename need not migrate data. Verify the relevant reads/writes/constraints and rollout
   contract; stop when the requirement is met. Record what missing evidence or workload
   change would warrant revisiting a conditional recommendation.

## Decision rules

```text
Subtypes only share fields and have no distinct substitution/invariant contract
        → compare composition or mapped-superclass reuse. An enum with embedded
          groups may fit; JSON requires explicit validation/query/index trade-offs.

Few subtypes, few subtype-specific columns, polymorphic queries common,
performance matters
        → SINGLE_TABLE often minimizes joins for polymorphic reads. Confirm
          row width, indexes, predicates and workload before claiming speed.
          Pay with nullable columns and explicit conditional-constraint design.

Many subtype-specific columns, or the database must enforce them, or
subtypes are large and distinct
        → compare JOINED's subtype-local constraints with SINGLE_TABLE checks
          and the actual workload. Joined FKs alone do not prove sibling
          exclusivity or base-row completeness; verify required invariants.
          Base projections need not load every subtype.

Subtypes are genuinely unrelated in storage terms, never queried
polymorphically, and no other table needs a foreign key to the base
        → TABLE_PER_CLASS. Rarely the right answer; an ordinary FK needs a single referenced table;
          hierarchy-wide queries usually require UNIONs or separate queries.

The hierarchy is deep (3+ levels)
        → inspect mapped levels and actual fields/SQL before flattening.
          Depth can add joins; it does not itself add SINGLE_TABLE columns.

Variation is per-tenant or per-configuration, and new variants must ship
without a deploy
        → compare data-driven policy/composition using supported operations.
          Choose relational fields/rows versus JSON or serialized data from
          validation, query/index, update and compatibility needs; configuration
          alone does not implement new behaviour (orm-structural-mapping).
```

## Rules

- **Single table's real cost includes constraint complexity, not just disk.** Columns may remain
  nullable at the column level while discriminator-aware `CHECK` constraints enforce “a
  `CardPayment` must have `card_last4`.” Verify ORM-generated DDL and bulk/import paths, or
  accept that only application code enforces it — and that bulk imports bypass it.
- **Joined entity materialization often joins mapped levels/subtypes**, while base-only
  projections can avoid them; writes span the tables holding the entity state. It is usually acceptable; it becomes painful on a list
  screen that reads the whole hierarchy at volume, and that is a measurable question, not a
  matter of taste (`architecture-and-performance`).
- Concrete table per class has no single base table for an ordinary polymorphic foreign key.
  Alternatives such as separate subtype FKs, a registry/base identity table or application-level
  integrity add complexity; price them before excluding the strategy.
- **Pin stored discriminator values** where used (`@DiscriminatorValue("CARD")`). For a
  STRING discriminator the default is the entity name; an unpinned entity name follows the
  class name. Preserve existing values during renames, including explicit entity/table names.
- Query costs depend on selected attributes, predicates, fetches and provider SQL. A root
  SINGLE_TABLE query need not filter the discriminator at all; fewer joins do not guarantee
  a cheap plan. Inspect plans and representative workload before comparing strategies.
- For mapping reuse without a subtype contract, compare `@MappedSuperclass` (shared
  mapping, no entity-root polymorphism or base table) with composition. Audit fields and
  shared identifiers can fit it; do not remove a legitimate persistent hierarchy merely
  because its fields look similar.
- Adding a subtype may need new columns/checks/indexes (single table), a new mapped table
  and FK (joined), or a concrete table. Locking and scan/rewrite costs depend on the exact
  database/version and DDL; additive does not mean online. Changing
  strategy later usually requires a substantial data migration. It is expensive, not literally
  one-way; use expand/contract, reconciliation and rollback/forward-fix analysis
  (`architecture-decision-making`). When SQL changes mapped state or reclassifies rows,
  include existing persistence contexts, optimistic versions and configured caches in the
  cutover plan; see the schema-evolution reference.
- Choose indexes from predicates and selectivity. Partial/filtered indexes can help sparse
  subtype data where supported and where the query implies their predicate; inspect the
  generated, possibly parameterized SQL and plan.
- If `PremiumCustomer` and `StandardCustomer` differ only in a configurable discount rate
  and have no distinct substitution/invariant contract, one type with a policy may be enough.
  Preserve actual business and consumer distinctions; hand off unresolved domain meaning to
  `domain-logic-organization`.

For a recommendation or no-change result, report query/DDL evidence, the integrity and
migration trade-offs, actual checks and any targeted validation still needed. Without plans
or workload data, keep performance conclusions conditional; a proposed migration is not
completed implementation.

## References

- [Strategy comparison](references/strategy-comparison.md) — the three strategies with
  their DDL, their generated SQL for the queries that matter, query-cost hypotheses,
  constraint enforcement including conditional check constraints, indexing under each, and
  the alternatives to inheritance with the shape that replaces it. Read when choosing a
  strategy or justifying an existing one.
- [Schema evolution](references/schema-evolution.md) — what adding, removing, splitting or
  moving a subtype costs under each strategy; discriminator value management and renames;
  migrating between strategies with an expand/contract sequence; and the read-model options
  when the write-side mapping is right but reads are expensive. Read before changing a
  hierarchy that has data in it.

Files in this skill

  • SKILL.md10.3 KB
  • references/schema-evolution.md9.3 KB
  • references/strategy-comparison.md10.2 KB
  • skill.yaml2.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…