Back to Entity Mapping: Keys, Columns & Annotations
Q4Question 4 of 8

What is @Column and @Transient?

Key Concepts
  • @Column customizes field-to-column mapping with attributes like name, nullable, length, unique, and columnDefinition.
  • Without @Column, Hibernate uses the field name as the column name with default settings (nullable=true, length=255).
  • @Transient (JPA annotation) excludes a field from persistence entirely, creating no database column.
  • Java's transient keyword excludes a field from serialization only, and Hibernate still maps it to a database column.
  • @PostLoad is the correct callback for computing @Transient derived fields after an entity is loaded.
  • @Column(updatable=false) makes a column write-once, set only on INSERT and never updated.
  • Always use explicit @Column annotations in production code to enforce constraints like NOT NULL and VARCHAR length.

Definition

@Column customizes how a Java field maps to a database column, @Transient (JPA) excludes a field from persistence entirely, and Java's transient keyword excludes from serialization only while Hibernate still maps it.
  • @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.Transient that 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).
  • precision and scale: 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).
JAVA
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)
}
TEXT
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.

JAVA
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
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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 |

JAVA
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!
}
TEXT
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.

JAVA
// 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.
JAVA
// 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
JAVA
// 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.

JAVA
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();
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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.

JAVA
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!
}
TEXT
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.

JAVA
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
}
TEXT
@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

DIAGRAM
  @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.

---

Share
Use ← → arrow keys to navigate