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 User entity does not own the relationship.
  • The owning side is the Event.organiser field, which has the @JoinColumn.
  • JPA will use the foreign key organiser_id defined in the Event table.

cascade

cascade (set via cascade = CascadeType.X) controls how persistence operations applied to a parent entity are automatically propagated to its related child entities.

CascadeTypeDescription
ALLApply all cascade operations (PERSIST, MERGE, REMOVE, etc.)
PERSISTWhen parent is persisted, also persist the children
MERGEWhen parent is merged, also merge the children
REMOVEWhen parent is removed, also remove the children
REFRESHWhen parent is refreshed, also refresh children from DB
DETACHWhen 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), all tickets in the event.getTickets() list will be persisted automatically.
  • If you entityManager.remove(event), all tickets will be removed automatically.

fetch

The fetch attribute controls when related data is loaded from the database.

Fetch TypeBehavior
LAZYLoads related entity only when accessed
EAGERLoads 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 organiser

Example

@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 Event table has a column organiser_id that refers to User.id.
  • Therefore, Event is the owning side.
  • The @JoinColumn is placed on Event.organiser.