# @ManyToOne vs @OneToMany in Hibernate JPA: Ownership, Fetch Types, and Query Impact

> Understand Hibernate JPA's @ManyToOne vs @OneToMany. Learn about ownership, fetching, and how these relationships impact your JPQL queries and database performance.

- Repository: [Spring/spring-boot](https://github.com/spring-projects/spring-boot)
- Tags: deep-dive
- Published: 2026-02-19

---

**In Hibernate JPA, `@ManyToOne` represents the owning side of a relationship with eager fetching by default, storing the foreign key in its table, while `@OneToMany` is the inverse side with lazy fetching that requires `mappedBy` to reference the owning field, fundamentally changing how you write JPQL queries and optimize database performance.**

In Spring Boot applications using Hibernate JPA, understanding the distinction between `@ManyToOne` and `@OneToMany` annotations is critical for designing efficient database schemas and avoiding performance pitfalls. These two annotations represent bidirectional views of the same relationship, but they differ fundamentally in ownership, default fetch strategies, and query behavior. The Spring Boot smoke test data JPA module provides concrete implementations demonstrating these patterns in production code.

## Core Architectural Differences

### Relationship Ownership and Database Schema

The primary distinction lies in which entity owns the foreign key column. **`@ManyToOne`** always represents the **owning side** of the relationship. This means the table for the entity annotated with `@ManyToOne` contains the foreign key column that references the related entity. Conversely, **`@OneToMany`** represents the **inverse (non-owning) side**; it does not contain a foreign key column in its table. Instead, it must use the `mappedBy` attribute to point to the field name in the owning entity that manages the relationship. Failure to specify `mappedBy` results in JPA creating an unnecessary join table, effectively treating the relationship as two separate unidirectional associations.

### Default Fetch Strategies

Fetch strategy represents another critical difference that directly impacts application performance. **`@ManyToOne`** defaults to **`FetchType.EAGER`**, meaning Hibernate loads the related entity immediately when the parent entity is retrieved, potentially causing unnecessary database hits if the related data isn't used. **`@OneToMany`** defaults to **`FetchType.LAZY`**, where the collection is represented by a proxy and only loaded when explicitly accessed in the code. This lazy behavior is generally preferred for collections to avoid loading large datasets into memory unnecessarily.

## Impact on JPA Queries

### Querying from the Many Side

When querying from the entity annotated with `@ManyToOne`, you can filter directly on the foreign key property. This translates to efficient SQL with a simple `WHERE` clause on the foreign key column. For example, to find all reviews for a specific hotel:

```java
List<Review> reviews = entityManager
    .createQuery("SELECT r FROM Review r WHERE r.hotel.id = :hotelId", Review.class)
    .setParameter("hotelId", 1L)
    .getResultList();

```

This query leverages the fact that the `review` table contains the `hotel_id` foreign key, allowing the database to filter efficiently without requiring joins.

### Querying from the One Side

Querying from the `@OneToMany` side requires different strategies to avoid performance issues. Because the collection is lazy by default, simply retrieving the parent entity does not load the children:

```java
// This does NOT load reviews - generates single SELECT for Hotel only
Hotel hotel = entityManager.find(Hotel.class, 1L);

```

Accessing `hotel.getReviews()` after this point would trigger a separate SQL query, leading to the N+1 problem if performed in a loop. To load the collection efficiently in a single query, use `JOIN FETCH`:

```java
Hotel hotel = entityManager
    .createQuery("SELECT h FROM Hotel h LEFT JOIN FETCH h.reviews WHERE h.id = :hotelId", Hotel.class)
    .setParameter("hotelId", 1L)
    .getSingleResult();

```

This generates a single SQL statement with a join, eagerly loading the reviews collection and avoiding the N+1 select issue.

## Real-World Implementation in Spring Boot

The Spring Boot smoke test data JPA module demonstrates these patterns in production code. In the `Hotel` and `Review` domain model, the relationship is mapped bidirectionally with clear ownership:

The `Review` entity represents the **owning side** using `@ManyToOne`:

```java
// src/smoke-test/spring-boot-smoke-test-data-jpa/src/main/java/smoketest/data/jpa/domain/Review.java
@Entity
public class Review {
    
    @ManyToOne(optional = false)
    private Hotel hotel;
    
    // ... other fields
}

```

As implemented in `spring-projects/spring-boot`, this annotation indicates that the `review` table contains the foreign key column referencing `hotel`. The `optional = false` constraint ensures every review must be associated with a hotel.

The `Hotel` entity represents the **inverse side** using `@OneToMany`:

```java
// src/smoke-test/spring-boot-smoke-test-data-jpa/src/main/java/smoketest/data/jpa/domain/Hotel.java
@Entity
public class Hotel {
    
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "hotel")
    private Set<Review> reviews = new HashSet<>();
    
    // ... other fields
}

```

According to the Spring Boot source code, the `mappedBy = "hotel"` attribute references the `hotel` field in the `Review` class, establishing the bidirectional link without creating a separate join table. The explicit `FetchType.LAZY` declaration follows best practices for collections, preventing accidental loading of all reviews when retrieving a hotel.

## Summary

- **@ManyToOne** represents the **owning side** of a relationship, storing the foreign key in its table and defaulting to **EAGER** fetching.
- **@OneToMany** represents the **inverse side**, requires the `mappedBy` attribute to reference the owning field, and defaults to **LAZY** fetching to prevent performance issues.
- Querying from the `@ManyToOne` side allows direct foreign-key filtering without joins, while querying from the `@OneToMany` side requires `JOIN FETCH` to avoid N+1 select problems.
- The Spring Boot `Hotel` and `Review` entities in `spring-boot-smoke-test-data-jpa` demonstrate proper bidirectional mapping with clear ownership and appropriate fetch strategies.

## Frequently Asked Questions

### Can I use @OneToMany without mappedBy?

No, omitting `mappedBy` on `@OneToMany` causes JPA to treat the relationship as unidirectional and creates a separate join table to manage the association. This results in unnecessary database tables and inefficient queries. Always specify `mappedBy` when mapping the inverse side of a bidirectional relationship to ensure the foreign key is managed by the owning side.

### Why does @ManyToOne default to EAGER fetching while @OneToMany defaults to LAZY?

The default strategies reflect common performance patterns and data access patterns. A `@ManyToOne` association typically references a single parent entity (like a Hotel for a Review), which is often needed immediately when displaying the child entity, so EAGER loading reduces subsequent queries. Conversely, `@OneToMany` collections can contain large numbers of entities, making LAZY fetching essential to avoid loading massive datasets into memory when only the parent entity information is required.

### How do I avoid the N+1 problem when querying @OneToMany collections?

Use `JOIN FETCH` in your JPQL queries to eagerly load the collection in a single SQL statement. For example: `SELECT h FROM Hotel h JOIN FETCH h.reviews`. Alternatively, you can use entity graphs or Hibernate-specific `@BatchSize` to optimize collection loading, but `JOIN FETCH` is the most straightforward solution for immediate loading of the entire collection.

### Can @ManyToOne and @OneToMany be used in the same entity?

Yes, an entity can simultaneously be the parent in one relationship and the child in another. For example, a `Department` entity might have `@OneToMany` with `Employee` while having `@ManyToOne` with `Company`. The key is ensuring that each bidirectional pair correctly identifies the owning side with `@ManyToOne` and the inverse side with `@OneToMany(mappedBy="...")` to maintain proper foreign key management.