Tutorials Logic, IN info@tutorialslogic.com

Hibernate Relationships OneToMany, ManyToMany

Relationship Types

Annotation Example DB Representation
@OneToOne User ↔ UserProfile Foreign key in one table
@OneToMany User -> Orders Foreign key in child table
@ManyToOne Order -> User Foreign key in this table
@ManyToMany Student ↔ Course Join table

@OneToOne and @OneToMany / @ManyToOne

@OneToOne and @OneToMany / @ManyToOne
// @OneToOne: User has one UserProfile
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;

    // Owning side: has the foreign key column
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "profile_id", unique = true)
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String bio;
    private String website;

    // Inverse side (mappedBy = field name in User)
    @OneToOne(mappedBy = "profile")
    private User user;
}

Relationship Types - Java Example

Relationship Types - Java Example
// @OneToMany / @ManyToOne: User has many Orders
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;

    // One user has many orders
    // mappedBy = field name in Order that owns the relationship
    @OneToMany(mappedBy = "user",
               cascade = CascadeType.ALL,
               fetch = FetchType.LAZY,
               orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    // Helper method to maintain bidirectional consistency
    public void addOrder(Order order) {
        orders.add(order);
        order.setUser(this);
    }
}

@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private java.math.BigDecimal total;

    // Many orders belong to one user
    // This side owns the relationship (has the FK column)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

Cascade Types and Fetch Types

Cascade Type Description
ALL All operations cascade (PERSIST, MERGE, REMOVE, REFRESH, DETACH)
PERSIST Save cascades to related entities
MERGE Merge cascades to related entities
REMOVE Delete cascades to related entities
REFRESH Refresh cascades to related entities
DETACH Detach cascades to related entities

@ManyToMany with Join Table

@ManyToMany with Join Table
// @ManyToMany: Student can enroll in many Courses
// Course can have many Students
@Entity
public class Student {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    // Owning side: defines the join table
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
                fetch = FetchType.LAZY)
    @JoinTable(
        name = "student_course",           // Join table name
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
}

@Entity
public class Course {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;

    // Inverse side
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

// Usage
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Student student = new Student("Alice");
Course java = new Course("Java Programming");
Course spring = new Course("Spring Framework");

student.getCourses().add(java);
student.getCourses().add(spring);
java.getStudents().add(student);
spring.getStudents().add(student);

session.persist(student);
session.persist(java);
session.persist(spring);

tx.commit();
session.close();

Fetch Types - EAGER vs LAZY

Fetch Types - EAGER vs LAZY
// EAGER: related entities loaded immediately with parent
// Default for @ManyToOne and @OneToOne
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "department_id")
private Department department; // Loaded with Employee in same query

// LAZY: related entities loaded only when accessed
// Default for @OneToMany and @ManyToMany
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders; // Loaded only when orders is accessed

// LAZY loading requires open session when accessing the collection
// Use JOIN FETCH in HQL to avoid N+1 problem:
List<User> users = session.createQuery(
    "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders", User.class)
    .list();
// This loads users AND their orders in a single query

Ownership, Cascades, and Aggregate Boundaries

An association must identify which side writes the foreign key and how both object references stay synchronized in memory.

Cascades and orphan removal cross row-lifecycle boundaries. Apply them only when the related entity is truly owned by the same aggregate; shared references need independent lifecycle rules.

  • Name the owning side explicitly.
  • Keep bidirectional helper methods synchronized.
  • Choose cascade only for lifecycle ownership.
  • Use orphanRemoval for true child objects, not shared references.
  • Plan fetch behavior for each association.

Keep Both Sides Consistent and Queries Bounded

A bidirectional association is two Java references representing one database relationship. Helper methods should update both sides so the in-memory graph does not disagree with the foreign key owner. equals and hashCode must not traverse mutable relationships or force large lazy graphs to load.

Default associations to the smallest useful fetch plan, then select joins or entity graphs per use case. Test serialization and logging so they do not trigger lazy loads or recursion. For large collections, query a bounded page rather than loading every child through the parent.

Before you move on

Hibernate Relationships OneToMany, ManyToMany Mastery Check

5 checks
  • @OneToOne means User ↔ UserProfile; a typical example is Foreign key in one table.
  • @OneToMany means User -> Orders; a typical example is Foreign key in child table.
  • @ManyToOne means Order -> User; a typical example is Foreign key in this table.
  • @ManyToMany means Student ↔ Course; a typical example is Join table.
  • Relationship Types includes User ↔ UserProfile, User -> Orders, Order -> User, and Student ↔ Course.

Hibernate Questions Learners Ask

The owning side is the mapping without mappedBy, usually the @ManyToOne side for a one-to-many relationship. Changes made only to the inverse collection may not update the foreign key.

A lazy collection is represented by a proxy that needs an open persistence context to load its rows. After the Session closes, the proxy cannot execute the query and may throw LazyInitializationException.

ALL includes REMOVE. In a many-to-many association, the related entity is usually shared, so deleting one parent should remove join-table rows rather than delete the shared child.

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.