The four relationship annotations map foreign keys and join tables onto object references — and the two things interviewers actually test are who owns the foreign key and what the default fetch type is. Get those two right and the rest follows.
@ManyToOne — the owning side
The most common and the simplest: the child holds the FK.
@Entity
class Order {
@ManyToOne(fetch = FetchType.LAZY) // override the EAGER default!
@JoinColumn(name = "customer_id")
Customer customer;
}
@ManyToOne is the owning side — @JoinColumn names the FK column on orders. Its default fetch is EAGER, which is almost always wrong (see the fetch-types question) — override it to LAZY.
@OneToMany — the inverse side
@Entity
class Customer {
@OneToMany(mappedBy = "customer", cascade = ALL, orphanRemoval = true)
List<Order> orders = new ArrayList<>();
}
mappedBy = "customer" says "the Order.customer field owns this relationship; I'm just the inverse view." Without mappedBy, JPA assumes a join table. @OneToMany defaults to LAZY (good). Use cascade to propagate persist/remove, and orphanRemoval to delete children removed from the collection.
@OneToOne
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
Profile profile;
Also defaults to EAGER — override to LAZY. Note a nullable lazy @OneToOne on the inverse (mappedBy) side can't be proxied and stays eager unless you use bytecode enhancement; the owning side with the FK lazies cleanly.
@ManyToMany
@ManyToMany
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
Set<Role> roles = new HashSet<>();
Uses a join table. Defaults to LAZY.
Fetch defaults — memorise this
@ManyToOne → EAGER (override to LAZY)
@OneToOne → EAGER (override to LAZY)
@OneToMany → LAZY
@ManyToMany → LAZY