Practices
Use wrapper types
For example you should use the wrapper (boxed) type Double instead of primitive type double because JPA needs to distinguish between:
- “Unset”/null (field not assigned, e.g., new object not yet persisted)
- Zero value (e.g., 0.0)
Primitive types (like double) cannot be null — they always have a default value (e.g., 0.0), which can cause unintended behaviour in persistence and validation.
Annotations
@ManyToMany
Specifies that this is a many-to-many association between the current entity and another.
@ManyToOne
Defines a foreign key relationship between the current entity and the Event entity.
mappedBy
mappedBy attribute is used in bidirectional relationships to indicate which side of the relationship owns the mapping. It tells JPA: “This field is mapped by the other side’s field.”
Example
Think about this example relationship@OneToMany ↔ @ManyToOne. Let’s say each Event has one organiser (User), and each User can organise many events.
In Event (owning side):
@ManyToOne
@JoinColumn(name = "organiser_id") // foreign key column in the events table
private User organiser;In User (inverse side):
@OneToMany(mappedBy = "organiser")
private List<Event> organisedEvents;Here’s what mappedBy = "organiser" means:
- The
Userentity does not own the relationship. - The owning side is the
Event.organiserfield, which has the@JoinColumn. - JPA will use the foreign key
organiser_iddefined in theEventtable.
cascade
cascade (set via cascade = CascadeType.X) controls how persistence operations applied to a parent entity are automatically propagated to its related child entities.
| CascadeType | Description |
|---|---|
ALL | Apply all cascade operations (PERSIST, MERGE, REMOVE, etc.) |
PERSIST | When parent is persisted, also persist the children |
MERGE | When parent is merged, also merge the children |
REMOVE | When parent is removed, also remove the children |
REFRESH | When parent is refreshed, also refresh children from DB |
DETACH | When parent is detached from persistence context, detach children too |
Example
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
private List<Ticket> tickets;With this setting:
- If you
entityManager.persist(event), allticketsin theevent.getTickets()list will be persisted automatically. - If you
entityManager.remove(event), allticketswill be removed automatically.
fetch
The fetch attribute controls when related data is loaded from the database.
| Fetch Type | Behavior |
|---|---|
LAZY | Loads related entity only when accessed |
EAGER | Loads related entity immediately with the parent, via a join |
Example
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organiser_id")
private User organiser;FetchType.LAZY` tells JPA: “Don’t load the organiser from the database when I load the Event. Only fetch it later, when I actually access it.”
Event event = entityManager.find(Event.class, eventId);
// At this point, event.organiser is NOT loaded
System.out.println(event.getName()); // OK
System.out.println(event.getOrganiser()); // Now JPA queries the database to fetch the organiserExample
@Entity
public class Ticket {
@Id
private UUID id;
@ManyToOne
@JoinColumn(name = "event_id")
private Event event;
}Conventions
The Many side of a One-To-Many relationship typically owns the relationship and should be responsible for managing the foreign key in the database.
Consider the below example that an event can have many organisers (User).
class User {
@OneToMany(mappedBy = "organiser")
private List<Event> organisedEvents;
}
class Event {
@ManyToOne
@JoinColumn(name = "organiser_id")
private User organiser;
}In JPA, the side that has the foreign key column in its table is the owning side.
- The
Eventtable has a columnorganiser_idthat refers toUser.id. - Therefore,
Eventis the owning side. - The
@JoinColumnis placed onEvent.organiser.