OneToOne

Order and Payment

Every ShopEase order has at most one payment. The table that holds the foreign key is the owning side. Here payments.order_id points to the order.

@Entity
@Table(name = "payments")
public class Payment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "order_id", unique = true)    // unique makes it truly one-to-one
    private Order order;                             // owning side

    private BigDecimal amount;
    private String gatewayRef;                       // e.g. Razorpay payment id
}

// in Order:
@OneToOne(mappedBy = "order", fetch = FetchType.LAZY)
private Payment payment;                             // inverse side

Sharing the Primary Key with @MapsId

A cleaner one-to-one design: the payment uses the same id as its order. @MapsId copies the order id into the payment primary key, so there is no separate order_id column and no extra index.

@Entity
@Table(name = "payments")
public class Payment {

    @Id
    private Long id;                 // no @GeneratedValue: taken from the order

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId                          // payments.id = orders.id (also the FK)
    private Order order;

    private BigDecimal amount;
}
create table payments (
    amount decimal(38,2),
    order_id bigint not null,   -- primary key AND foreign key
    primary key (order_id)
);

The Lazy OneToOne Trap

Note: @OneToOne and @ManyToOne are EAGER by default. Always write fetch = FetchType.LAZY. Even then, the inverse side (Order.payment with mappedBy) is loaded eagerly by Hibernate: the orders table has no column that tells Hibernate whether a payment exists, so it must query. Prefer mapping one-to-one only on the owning side, or use @MapsId and load the payment with paymentRepository.findById(orderId).