Entity mapping is the foundation of working with Hibernate/JPA. It defines how Java objects are mapped to database tables.
Basic Annotations: Where an Entity Begins
Any mapping starts with declaring a class as an entity and defining its primary key.
-
@Entity: Marks the class as an entity that will be mapped to a database table. This is a mandatory annotation. -
@Table: Allows you to explicitly specify the table name. If omitted, Hibernate will default to the class name (e.g., forUser, the table will beUser). -
@Id: Defines the field that serves as the primary key. -
@GeneratedValue: Specifies that the primary key value will be generated automatically. TheGenerationType.IDENTITYstrategy (database auto-increment) is one of the most common and straightforward. -
@Column: Used for fine-tuning the column: you can specify its name, length, precision, forbidnullvalues, etc.
Example of a simple entity:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "full_name", nullable = false)
private String name;
// ... getters and setters
}
Relationship Types: From One-to-One to Many-to-Many
In JPA, there are four main types of relationships. The key concept to understand is the owning side. It is on the owning side that the relationship is defined in the database (e.g., which table will hold the foreign key or the join table).
-
@ManyToOne(Many-to-One): The most common and basic relationship. It maps directly to a foreign key in the table. Important: By default, JPA usesFetchType.EAGERfor this, which is almost always undesirable.
@Entity
public class Book {
// ...
@ManyToOne(fetch = FetchType.LAZY) // Explicitly specify lazy loading!
@JoinColumn(name = "publisher_id") // Name of the foreign key column
private Publisher publisher;
}
-
@OneToMany(One-to-Many): The inverse side of@ManyToOne. In JPA, this relationship is almost always bidirectional and must be the non-owning (inverse) side. For this, themappedByattribute is used, pointing to the field in the child entity that owns the relationship.
@Entity
public class Publisher {
// ...
@OneToMany(mappedBy = "publisher", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books = new ArrayList<>();
}
@OneToOne(One-to-One): A relationship where one record in table A corresponds to exactly one record in table B. Like@ManyToOne, it defaults toEAGERfetching. To enable lazy loading on the child side (where the foreign key resides), you must explicitly use@JoinColumn.@ManyToMany(Many-to-Many): Requires an intermediate join table. The owning side defines this table using the@JoinTableannotation.
// Owning side
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
// Inverse (non-owning) side
@Entity
public class Course {
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
Cascades (CascadeType) and orphanRemoval
These mechanisms determine how operations performed on one entity affect its related entities.
-
CascadeType: Defines which operations (persist, remove, update) should be cascaded to related entities.-
CascadeType.PERSIST: When the parent is saved, new child entities are automatically saved as well. -
CascadeType.REMOVE: When the parent is deleted, all related child entities are also deleted. -
CascadeType.ALL: Applies all operations. Use with caution, only if the child entity cannot exist without the parent.
-
orphanRemoval = true: A more specific and powerful tool. It deletes a child entity if it is "unlinked" from the parent (e.g., removed from theList<Book>collection inPublisher), even if the parent entity itself is not deleted. This is ideal for modeling strict "composition" relationships.
Fetch Strategies (FetchType): LAZY vs EAGER
This is one of the most critical topics for application performance.
-
FetchType.LAZY(Lazy): Hibernate loads the related entity only upon the first access to it. This is the recommended default approach for all relationships, especially for collections (@OneToMany,@ManyToMany), as it prevents the N+1 query problem and excessive data loading. -
FetchType.EAGER(Eager): Hibernate loads the related entity immediately, along with the parent. While this might seem convenient, in practice, it often leads to loading massive amounts of unnecessary data and severe performance issues.
The golden rule: Always explicitly specify fetch = FetchType.LAZY for all @ManyToOne and @OneToOne relationships, as EAGER is their default value.
Best Practices and Common Pitfalls
- Always use
FetchType.LAZYfor@ManyToOneand@OneToOne. This is the single most important rule for avoiding performance bottlenecks. - Define the owning side correctly. For
@OneToMany, usemappedByon the "one" side, and@JoinColumnon the "many" (owning) side. - Be cautious with
CascadeType.ALL. Apply it only for true compositions, where the child object has no business meaning without the parent. - Use
orphanRemoval = truefor clean deletion of child entities when they are removed from a collection. - Don't forget
@JsonIgnoreProperties(or@JsonManagedReference/@JsonBackReference) when serializing bidirectional relationships to JSON to avoid infinite recursion. - To load lazy relationships, use
JOIN FETCHor@EntityGraph, rather than relying onOpenSessionInView, which is widely considered an anti-pattern.
Top comments (0)