Definition
- @Column -- customizes field-to-column mapping with attributes:
name(column name),nullable(NOT NULL constraint),length(VARCHAR size),unique(unique constraint),precision/scale(DECIMAL precision),columnDefinition(raw DDL),updatable(allow updates),insertable(allow inserts). - @Transient (JPA) -- annotation from
jakarta.persistence.Transientthat excludes a field from persistence entirely, so Hibernate creates no column and never reads or writes the value. - transient (Java keyword) -- excludes a field from Java serialization (
ObjectOutputStream) but Hibernate still maps it to a database column, which is a common source of confusion. - @PostLoad -- JPA lifecycle callback that fires after an entity is loaded from the database, ideal for computing @Transient derived fields.
At National Bank of India, @Column is like a form template that says "this field goes in column holder_name, maximum 100 characters, cannot be blank." @Transient (JPA) is like writing a note on the form that says "DO NOT store this in the database" for a calculated field like totalBalance that is computed on the fly. Java's transient keyword is like saying "DO NOT photocopy this field" when sending the form to another branch (serialization), but the vault (database) still stores it.
Understanding the Concepts
What is @Column?
@Column customizes how a Java field maps to a database column. Without it, Hibernate uses default settings based on the field name and type. In production code, you should always use explicit @Column annotations to enforce constraints at the database level.
name: the database column name (default: field name converted by naming strategy).nullable: whether NULL is allowed (default: true).length: VARCHAR length (default: 255).unique: adds a UNIQUE constraint (default: false).precisionandscale: for DECIMAL types (e.g., precision=15, scale=2 for money).columnDefinition: raw DDL string for the column type.updatable: whether the column is included in UPDATE statements (default: true).insertable: whether the column is included in INSERT statements (default: true).
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id", length = 20)
private String accountId; // VARCHAR(20)
@Column(name = "holder_name", nullable = false, length = 100)
private String holderName; // VARCHAR(100) NOT NULL
@Column(nullable = false, precision = 15, scale = 2)
private BigDecimal balance; // DECIMAL(15,2) NOT NULL
@Column(name = "pan_number", unique = true, length = 10)
private String panNumber; // VARCHAR(10) UNIQUE
@Column(columnDefinition = "TEXT")
private String address; // TEXT (raw DDL overrides Hibernate default)
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt; // set on INSERT, never updated
@Column(insertable = false, updatable = false)
private LocalDateTime lastModified; // DB-generated (trigger/computed)
}
Generated DDL:
CREATE TABLE accounts (
account_id VARCHAR(20) PRIMARY KEY,
holder_name VARCHAR(100) NOT NULL,
balance DECIMAL(15,2) NOT NULL,
pan_number VARCHAR(10) UNIQUE,
address TEXT,
created_at TIMESTAMP,
last_modified TIMESTAMP
);
What happens without @Column?
Without @Column, Hibernate applies default mappings based on the field name and type. The Spring Boot naming strategy converts camelCase to snake_case. All fields are nullable with default lengths, which is too permissive for production.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
public class Trade {
@Id
private String tradeId; // column: trade_id (camelCase -> snake_case)
private String symbol; // column: symbol (same name)
private BigDecimal amount; // column: amount, type: DECIMAL
// All nullable, no length constraint, no uniqueness
}
Default mapping:
tradeId -> trade_id VARCHAR(255) NULL
symbol -> symbol VARCHAR(255) NULL
amount -> amount DECIMAL(19,2) NULL
Problems:
- tradeId allows NULL (should be NOT NULL)
- symbol has no length limit (could store 255 chars)
- amount allows NULL (should be NOT NULL for money)
Always use @Column with explicit constraints in production.
What is @Transient in JPA?
@Transient from jakarta.persistence.Transient excludes a field from persistence entirely. Hibernate ignores the field: no column is created, no value is read from or written to the database. This is used for calculated or derived fields that are computed in Java code.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id")
private String accountId;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "savings_balance", precision = 15, scale = 2)
private BigDecimal savingsBalance;
@Column(name = "current_balance", precision = 15, scale = 2)
private BigDecimal currentBalance;
@Transient // jakarta.persistence.Transient
private String fullName;
// NO column created! Hibernate ignores this field.
// Computed: fullName = firstName + " " + lastName
@Transient
private BigDecimal totalBalance;
// NO column created!
// Computed: totalBalance = savingsBalance + currentBalance
@PostLoad
public void computeDerivedFields() {
this.fullName = firstName + " " + lastName;
this.totalBalance = savingsBalance.add(currentBalance);
}
}
Database table has only persisted columns:
account_id, first_name, last_name, savings_balance, current_balance
fullName and totalBalance exist only in Java memory.
They are computed by @PostLoad after every database load.
How is JPA @Transient different from Java's transient keyword?
They are completely different concepts that share a similar name. JPA @Transient excludes from database persistence. Java's transient keyword excludes from Java serialization (ObjectOutputStream). Confusing them is one of the most common JPA bugs.
| Aspect | JPA @Transient | Java transient keyword |
|---|---|---|
| Purpose | Exclude from database mapping | Exclude from serialization |
| Scope | Hibernate/JPA ignores the field | ObjectOutputStream skips the field |
| Database column | No column created | Column IS created |
| Syntax | @Transient private String x; | private transient String x; |
| Package | jakarta.persistence.Transient | Java language keyword |
| Use case | Computed fields (fullName, total) | Cache, temporary state |
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
public class Account {
@Id private String accountId;
private BigDecimal balance;
// JPA @Transient: NO database column
@Transient
private BigDecimal totalBalance;
// Correct for computed fields
// Java transient: Hibernate STILL creates a column!
private transient String cachedReport;
// transient keyword skips serialization
// BUT Hibernate still maps it to "cached_report" column
// This is probably NOT what you want!
}
Database columns created:
account_id (from @Id)
balance (normal field)
cached_report (transient keyword does NOT prevent column!)
Missing column:
totalBalance (@Transient prevented the column - correct)
What are common mistakes with @Column and @Transient?
The most frequent mistakes involve confusing JPA @Transient with Java's transient keyword and omitting important @Column constraints.
// WRONG: Using Java transient to prevent DB mapping
private transient BigDecimal computedValue;
// Hibernate STILL creates a "computed_value" column!
// ObjectOutputStream skips it, but JPA does not.
// CORRECT: Use JPA @Transient
@Transient
private BigDecimal computedValue;
// No column created. Hibernate ignores it completely.
// WRONG: No constraints on money field
private BigDecimal balance;
// Default: nullable, no precision/scale defined
// Could store NULL or lose precision
// CORRECT: Explicit constraints
@Column(nullable = false, precision = 15, scale = 2)
private BigDecimal balance;
// NOT NULL, DECIMAL(15,2) - correct for banking
// WRONG: updatable=false on a field that needs updates
@Column(name = "balance", updatable = false)
private BigDecimal balance;
// Balance can never be updated after initial INSERT!
// UPDATE SET balance=? is silently omitted from SQL.
// CORRECT: updatable=false only for write-once fields
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
// Created timestamp is set once and never changes.
How do you use @Column(updatable=false) and @Column(insertable=false)?
These attributes control whether Hibernate includes the column in UPDATE and INSERT statements. They are essential for write-once fields, database-generated values, and read-only computed columns.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
@Entity
@Table(name = "transactions")
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@Column(name = "account_id", nullable = false)
private String accountId;
@Column(precision = 15, scale = 2, nullable = false)
private BigDecimal amount;
// Write-once: set on INSERT, never updated
@Column(name = "created_at", updatable = false, nullable = false)
private Instant createdAt;
// DB-generated: never written by Hibernate
@Column(name = "audit_hash", insertable = false, updatable = false)
private String auditHash;
// audit_hash is computed by a database trigger
// Hibernate reads it but never writes it
@PrePersist
public void setCreationTimestamp() {
this.createdAt = Instant.now();
}
}
INSERT SQL (insertable=true fields only):
INSERT INTO transactions (id, account_id, amount, created_at)
VALUES (?, ?, ?, ?)
-- audit_hash is NOT in the INSERT (insertable=false)
UPDATE SQL (updatable=true fields only):
UPDATE transactions SET account_id=?, amount=? WHERE id=?
-- created_at is NOT in the UPDATE (updatable=false)
-- audit_hash is NOT in the UPDATE (updatable=false)
How do you use @PostLoad for computed @Transient fields?
@PostLoad is a JPA lifecycle callback that fires after an entity is loaded from the database. It is the correct place to compute @Transient derived fields so they are available immediately after loading.
package com.bank.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id")
private String accountId;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "savings_balance", precision = 15, scale = 2)
private BigDecimal savingsBalance;
@Column(name = "current_balance", precision = 15, scale = 2)
private BigDecimal currentBalance;
@Column(name = "hold_amount", precision = 15, scale = 2)
private BigDecimal holdAmount;
@Transient
private String fullName;
@Transient
private BigDecimal totalBalance;
@Transient
private BigDecimal availableBalance;
@PostLoad
public void computeTransientFields() {
this.fullName = firstName + " " + lastName;
this.totalBalance = savingsBalance.add(currentBalance);
this.availableBalance = totalBalance.subtract(holdAmount);
}
}
Flow:
1. Hibernate loads: SELECT * FROM accounts WHERE account_id = 'SAV2001'
2. Fields populated: firstName="Rajesh", lastName="Kumar",
savingsBalance=150000, currentBalance=50000, holdAmount=10000
3. @PostLoad fires: computeTransientFields()
fullName = "Rajesh Kumar"
totalBalance = 200000
availableBalance = 190000
4. Entity is ready to use with all derived fields populated.
What about @Column with @Enumerated?
@Column works with @Enumerated to control how Java enums are stored. By default, enums are stored as ordinal integers (0, 1, 2), which is fragile because reordering enum constants changes the stored values. Always use EnumType.STRING for safety.
package com.bank.model;
import jakarta.persistence.*;
public enum AccountStatus {
ACTIVE, FROZEN, CLOSED, DORMANT
}
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id")
private String accountId;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private AccountStatus status;
// Stored as 'ACTIVE', 'FROZEN', etc.
// Safe: reordering enum constants does not change stored values
// WRONG: EnumType.ORDINAL (default)
// @Enumerated(EnumType.ORDINAL)
// Stored as 0, 1, 2, 3
// Fragile: inserting a new constant shifts all ordinals!
}
With EnumType.STRING:
INSERT INTO accounts (account_id, status) VALUES ('SAV2001', 'ACTIVE')
With EnumType.ORDINAL (default, avoid):
INSERT INTO accounts (account_id, status) VALUES ('SAV2001', 0)
If you later add PENDING before ACTIVE, ACTIVE becomes 1
and all existing 0 values now mean PENDING instead of ACTIVE!
What about the @Formula annotation?
Hibernate's @Formula annotation provides an alternative to @Transient for computed fields that can be expressed as SQL. Instead of computing in Java, the formula is embedded in the SELECT statement.
package com.bank.model;
import jakarta.persistence.*;
import org.hibernate.annotations.Formula;
import java.math.BigDecimal;
@Entity
@Table(name = "accounts")
public class Account {
@Id
@Column(name = "account_id")
private String accountId;
@Column(name = "savings_balance", precision = 15, scale = 2)
private BigDecimal savingsBalance;
@Column(name = "current_balance", precision = 15, scale = 2)
private BigDecimal currentBalance;
@Formula("savings_balance + current_balance")
private BigDecimal totalBalance;
// Not a column. Hibernate embeds in SELECT:
// SELECT account_id, savings_balance, current_balance,
// (savings_balance + current_balance) AS formula_0_
// FROM accounts
}
@Formula vs @Transient + @PostLoad:
@Formula: computed in SQL, part of every SELECT, read-only
@Transient + @PostLoad: computed in Java, more flexible, can use
Java logic that SQL cannot express
Visual Overview
@Column mapping:
┌─────────────────────────────────────────────────────┐
│ Java Entity Field Database Column │
│ ───────────────── ────────────── │
│ @Column(name="account_id") account_id VARCHAR(20) │
│ @Column(nullable=false) NOT NULL constraint │
│ @Column(length=100) VARCHAR(100) │
│ @Column(unique=true) UNIQUE constraint │
│ @Column(precision=15, DECIMAL(15,2) │
│ scale=2) │
│ @Column(updatable=false) excluded from UPDATE │
│ @Column(insertable=false) excluded from INSERT │
└─────────────────────────────────────────────────────┘
@Transient vs transient:
┌─────────────────────────────────────────────────────┐
│ @Transient (JPA) │
│ private BigDecimal totalBalance; │
│ -> NO column. Hibernate ignores completely. │
│ │
│ transient (Java keyword) │
│ private transient String cachedReport; │
│ -> Column IS created! Serialization skips it only. │
└─────────────────────────────────────────────────────┘
@PostLoad flow:
┌──────────┐ SELECT ┌──────────┐ @PostLoad ┌──────────────┐
│ Database │ ────────> │ Hibernate │ ────────────> │ Entity ready │
│ │ │ populates │ │ with computed │
│ │ │ fields │ │ @Transient │
└──────────┘ └──────────┘ │ fields │
└──────────────┘
Key Takeaway
@Column is the primary tool for customizing field-to-column mapping in JPA. It controls the column name, nullability, length, uniqueness, precision, and whether the column participates in INSERT and UPDATE statements. Without @Column, Hibernate uses defaults that are too permissive for production: all fields are nullable and VARCHAR lengths default to 255. In our National Bank of India system, every entity uses explicit @Column annotations to enforce NOT NULL on money fields, VARCHAR limits on names and PAN numbers, and write-once semantics on creation timestamps.
@Transient from jakarta.persistence excludes a field from persistence entirely, so Hibernate creates no column and never reads or writes the value. This is used for derived fields like fullName computed from firstName and lastName, or totalBalance computed from savings and current balances. The @PostLoad callback fires after every database load, making it the correct place to populate these computed fields.
The distinction between JPA @Transient and Java's transient keyword is a common interview trap. JPA @Transient excludes from database mapping: no column is created. Java's transient keyword excludes from serialization only: Hibernate still maps the field to a database column. Using the wrong one creates either a missing column or an unwanted column, both of which are bugs that may not surface until production.
Remember one sentence: @Column controls how fields map to columns with constraints, @Transient (JPA) excludes fields from the database, and Java's transient keyword only affects serialization while Hibernate still maps the field.
---