Enums and Transient Fields

Mapping Enums

public enum ProductStatus { ACTIVE, OUT_OF_STOCK, DISCONTINUED }

@Entity
@Table(name = "products")
public class Product {
    // ...
    @Enumerated(EnumType.STRING)               // store the NAME, not the position
    @Column(length = 20, nullable = false)
    private ProductStatus status = ProductStatus.ACTIVE;
}
-- EnumType.STRING (PostgreSQL / H2)
status varchar(20) not null check (status in ('ACTIVE','OUT_OF_STOCK','DISCONTINUED'))

-- EnumType.ORDINAL (the default if you only write the field!)
status smallint not null check (status between 0 and 2)

Note: On MySQL, Hibernate 6.2+ maps EnumType.STRING to a native column type enum('ACTIVE','OUT_OF_STOCK','DISCONTINUED').

Why ORDINAL Is Dangerous

Example

ORDINAL stores the position (0, 1, 2). If a developer later inserts a new constant in the middle, every existing row silently changes meaning:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Version 1 of the app saved these ordinals
        List<Integer> storedOrdinals = List.of(
            StatusV1.ACTIVE.ordinal(), StatusV1.OUT_OF_STOCK.ordinal(), StatusV1.DISCONTINUED.ordinal());
        System.out.println("Stored in DB: " + storedOrdinals);

        // Version 2 added PRE_ORDER in the middle of the enum
        for (int i = 0; i < storedOrdinals.size(); i++) {
            int ord = storedOrdinals.get(i);
            System.out.printf("row %d: saved as %-13s read back as %s%n",
                i + 1, StatusV1.values()[ord], StatusV2.values()[ord]);
        }

        // With EnumType.STRING the name survives the change
        String stored = StatusV1.DISCONTINUED.name();
        System.out.println("STRING mapping reads back: " + StatusV2.valueOf(stored));
    }
}

enum StatusV1 { ACTIVE, OUT_OF_STOCK, DISCONTINUED }
enum StatusV2 { ACTIVE, PRE_ORDER, OUT_OF_STOCK, DISCONTINUED }

Output

Stored in DB: [0, 1, 2]
row 1: saved as ACTIVE        read back as ACTIVE
row 2: saved as OUT_OF_STOCK  read back as PRE_ORDER
row 3: saved as DISCONTINUED  read back as OUT_OF_STOCK
STRING mapping reads back: DISCONTINUED

Tip: Always write @Enumerated(EnumType.STRING). It costs a few bytes per row and saves you from silent data corruption.

@Transient: Fields That Are Not Stored

@Entity
@Table(name = "cart_items")
public class CartItem {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId;
    private Integer quantity;
    private BigDecimal unitPrice;

    @Transient                               // no column; calculated in Java
    private BigDecimal lineTotal;

    @Transient
    public BigDecimal getLineTotal() {
        return unitPrice.multiply(BigDecimal.valueOf(quantity));
    }
}

Use jakarta.persistence.Transient for derived values, temporary UI flags, or values loaded from another service. The Java keyword transient also excludes a field from persistence, but it also excludes it from Java serialization, so the annotation is the clearer choice.