What does the referencedColumnName attribute of JPA's @JoinColumn do, when would you set it, and what does it cost?
answer
- Names the TARGET column the FK points at
- Default = target's primary key
- Required for composite-key @JoinColumns
- Target column must be unique
- Non-PK reference = no free proxy, weaker cache use
basics
~20 sIt names which column of the target table the foreign key points at, instead of the default primary key. Use it for legacy schemas keyed on a natural unique column. Cost: the target column needs a unique constraint, and the association no longer resolves through the primary key.
solid answer
~50 sBy default a `@JoinColumn` references the **primary key** of the target entity's table. `referencedColumnName` overrides that, pointing the FK at some other column: ```java @ManyToOne @JoinColumn(name = "country_code", referencedColumnName = "iso_code") private Country country; ``` Legitimate uses are narrow: a legacy or third-party schema whose foreign keys are built on a natural key, or a composite-key target where every part must be named explicitly via `@JoinColumns` (there, `referencedColumnName` is effectively mandatory). The costs are real. The referenced column **must be unique** or the association is not well defined. Hibernate can no longer resolve the target purely from the FK value through normal id lookup, so it may need an extra select rather than returning a proxy, and second-level cache lookups keyed on the id do not help. If the natural key ever changes value, every referencing row must change too. Default to referencing the primary key; treat `referencedColumnName` as a schema-integration tool.
code
java · 13 lines@Entity
class Country {
@Id @GeneratedValue private Long id;
@Column(name = "iso_code", unique = true, nullable = false, length = 2)
private String isoCode;
}
@Entity
class Address {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country;
}go deeper
Know that it names the column in the target table that the foreign key points at, and that the default is the target's primary key.
Add the uniqueness requirement and the composite-key case where it is mandatory.
Explain the runtime costs — lost proxy-without-query, weaker second-level cache use, mutable-natural-key hazard — and frame it as a legacy-integration tool.
Discuss schema ownership: model on immutable surrogate keys in schemas you own, describe reality faithfully in ones you don't, and account for the query-shape consequences in hot paths.
## What it controls A foreign key has two ends: a column in the referencing table and a column in the referenced table. `@JoinColumn(name = ...)` names the first. `referencedColumnName` names the second. Omit it and JPA uses the target entity's **primary key column**, which is the overwhelmingly common case and the one every optimisation is tuned for. ```java // default: orders.customer_id -> customers.id (the PK) @ManyToOne @JoinColumn(name = "customer_id") private Customer customer; // explicit: orders.customer_code -> customers.external_code @ManyToOne @JoinColumn(name = "customer_code", referencedColumnName = "external_code") private Customer customer; ``` ## The three real reasons to set it **1. Legacy schemas keyed on natural columns.** Older systems commonly join on a business code — `iso_code`, `swift_code`, `employee_number`. You do not own that schema and cannot re-key it, so the mapping must describe reality. **2. Composite primary keys.** When the target's PK spans several columns, you need one `@JoinColumn` per part inside `@JoinColumns`, and each must state which target column it pairs with. Positional matching is not defined, so `referencedColumnName` is required: ```java @ManyToOne @JoinColumns({ @JoinColumn(name = "order_id", referencedColumnName = "order_id"), @JoinColumn(name = "line_no", referencedColumnName = "line_no") }) private OrderLine line; ``` **3. A join table whose FK targets a non-PK column** — the same reasoning inside `@JoinTable`'s `joinColumns` / `inverseJoinColumns`. ## What it costs **Uniqueness is your responsibility.** The referenced column must have a unique constraint. Without it the FK can match several rows and the association's meaning collapses — Hibernate will not check this for you, and the failure appears as a nondeterministic result or a "more than one row" error. **Loss of id-based resolution.** Normally a lazy to-one association is trivially a proxy: the FK value *is* the target's identifier, so Hibernate constructs a proxy without touching the database. When the FK holds a non-PK value, Hibernate does not know the target's identifier, so it generally must issue a select to resolve it before it can give you an entity — turning a free proxy into a query. This also means the second-level cache, which is keyed by identifier, cannot serve the lookup directly. **Cache and identity friction.** The persistence context keys managed entities by identifier. A non-PK-referencing association still has to end up with an entity keyed by its PK, so there is an extra mapping hop, and provider support for the more exotic cases (non-PK reference plus lazy plus caching) has historically been uneven. **Mutable natural keys are a trap.** If `external_code` can ever be updated, every referencing row must be updated in lockstep, or you get orphaned references. Primary keys are conventionally immutable precisely to avoid this; natural keys often are not. **DDL and constraint generation.** Some databases will not create a foreign key to a non-unique column at all, so the mapping and the migration can disagree until the constraint exists. ## The judgement The question is really about schema ownership. If you own the schema, model the association on the primary key and expose the natural key as an ordinary column (optionally with a unique constraint and a finder query). If you are integrating with a schema you do not own, `referencedColumnName` is the correct and intended tool — describe what is there rather than fighting it. A useful middle path when you control the code but not the schema: keep the association mapped on the natural key for correctness, and avoid relying on lazy to-one proxies across it in hot paths, since each one may cost a select. Prefer explicit joins or projections in queries that traverse it in bulk.
- Why can a lazy @ManyToOne mapped with referencedColumnName cost an extra SELECT that a normal one does not?With a primary-key reference the FK value is the target's identifier, so Hibernate can build a proxy immediately with no database access. When the FK holds a non-PK value, the identifier is unknown, so Hibernate generally has to query the target table to resolve the row before it can return an entity. The identifier-keyed second-level cache cannot short-circuit that lookup either.
- What must be true of the referenced column for the mapping to be sound?It must be unique — ideally backed by a unique constraint or unique index — because a foreign key that can match multiple rows does not define a single associated entity. It should also be effectively immutable: if the value changes, every referencing row must be updated in step or the references become orphaned.
Referencing the PK is addressing mail by an unchanging customer number; referencedColumnName is addressing it by a job title that must be unique and must never change hands.
saying these in an interview costs you the question
- Referencing a non-unique column and expecting the association to work deterministically
- Assuming referencedColumnName names a column in the referencing table rather than the target
- Omitting referencedColumnName in a composite-key @JoinColumns and relying on positional matching
- Using a natural key reference on a mutable business code
- Reaching for it in a greenfield schema instead of referencing the primary key