Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

Defining JPA Entities and Relationships

Recommended resources

Introduction

Hello! In our last lesson, we configured the bridge between our Spring application and a database by setting up a DataSource. You now understand how Spring Boot uses auto-configuration and connection pooling to manage database connections efficiently.

With the connection established, our next task is to define what we want to store. This lesson directly addresses the learning outcome: Define entity classes using @Entity, @Id, and relationship annotations (@OneToMany, @ManyToOne).

We will delve into the core of Object-Relational Mapping (ORM) in JPA. You'll learn how to transform a plain Java class into a database "entity" that JPA can manage. We'll start with the basics of @Entity and @Id, and then explore how to model one of the most common database relationships: the one-to-many association. We will focus on the design principles behind these mappings, ensuring you understand not just how to use the annotations, but why they work the way they do.


What is a JPA Entity?

In JPA, an entity is a simple Java class (often called a POJO - Plain Old Java Object) that is mapped to a table in a relational database. By adding specific annotations to the class and its fields, you provide metadata that tells the JPA provider (like Hibernate) how to manage its persistence.

At a minimum, an entity class must:

  1. Be annotated with @Entity.
  2. Have a primary key, which is a field annotated with @Id.

Let's look at a simple User entity without any relationships yet:

import jakarta.persistence.*;

@Entity
@Table(name = "users") // Optional: specifies the table name
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Lets the database generate the ID
    private Long id;

    private String name;

    private String email;

    // Getters, setters, constructors...
}
  • @Entity: Marks this class as a JPA entity.
  • @Table(name = "users"): Explicitly names the database table. If omitted, JPA typically defaults to using the class name (User).
  • @Id: Designates the id field as the primary key for this entity.
  • @GeneratedValue: Configures how the primary key is generated. GenerationType.IDENTITY is a common choice that relies on the database's auto-increment feature.

This simple setup maps the User class to a users table with columns for id, name, and email.


One-to-Many Relationships: The Foundational Concept

Now, let's introduce a second entity, Order. A single user can have multiple orders, but each order belongs to only one user. This is a classic one-to-many relationship from User to Order, and conversely, a many-to-one relationship from Order to User.

The key design question is: how should the database represent this link? In a relational model, this is done by placing a foreign key in the table on the "many" side. The orders table would contain a user_id column that references the id in the users table.

Why this way? Attempting to store a list of order_ids in the users table would violate the first normal form of database design and is inefficient. The video below gives an excellent, database-centric explanation of this principle. It shows why starting with a theoretical three-table model (User, Order, User_Order_Join_Table) can be normalized into a more efficient two-table model by placing the foreign key on the "many" side. This conceptual understanding is key to using JPA's annotations correctly.

Master Spring Data JPA Entity Mapping - One To Many ( 1 - M ) relationship | Spring Boot Tutorial

The video 'Master Spring Data JPA Entity Mapping' provides a fantastic conceptual walkthrough of why the foreign key is placed on the 'many' side of a one-to-many relationship.

Please watch from 13:54 to 26:59. This segment explains: The problem of representing a one-to-many relationship in a relational database. Why a third 'join table' is a theoretical starting point. How this model is 'normalized' by merging the join table into the 'many' side's table, resulting in a single foreign key column. This will give you the 'why' before we tackle the 'how' with JPA annotations.


Implementing Bidirectional Relationships in JPA

Now that we understand the database schema we're aiming for, let's see how to implement it in JPA. The most common and useful approach is a bidirectional relationship, where the User object can access its list of Order objects, and an Order object can access its parent User.

This requires annotating both sides of the relationship.

1. The "Many" Side: The Owning Side

The "many" side of the relationship (Order) is responsible for managing the foreign key. It is therefore considered the owning side of the relationship.

We use the @ManyToOne annotation here.

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDateTime orderDate;

    // ... other order fields

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false) // Defines the foreign key column
    private User user;

    // Getters, setters...
}
  • @ManyToOne: Declares that many Order entities can be associated with one User entity.
  • @JoinColumn(name = "user_id"): This is the key annotation. It specifies the foreign key column in the orders table. Without it, JPA would generate a default name. nullable = false creates a NOT NULL constraint, ensuring every order must belong to a user.

2. The "One" Side: The Inverse Side

The "one" side of the relationship (User) holds a collection of the "many" entities. It is considered the inverse side because it does not manage the foreign key.

We use the @OneToMany annotation here, along with a crucial attribute: mappedBy.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // ... other user fields

    @OneToMany(mappedBy = "user") // "user" is the field name in the Order class
    private List<Order> orders = new ArrayList<>();

    // Getters, setters...
}
  • @OneToMany(mappedBy = "user"): Declares that one User can have many Order entities.
  • The mappedBy = "user" attribute is essential. It tells JPA: "Do not create a join table for this relationship. The mapping is already handled by the user field in the Order entity." This makes the User side the inverse (non-owning) side and prevents redundant mapping information.

The following diagram provides a clear visual summary of this bidirectional setup.

One-to-Many/Many-to-One Bidirectional Relationship in JPA
This diagram illustrates a bidirectional one-to-many relationship. The `@ManyToOne` side (`EntityB`/`Order`) defines the join column and is the owning side. The `@OneToMany` side (`EntityA`/`User`) uses `mappedBy` to reference the configuration on the owning side, making it the inverse side.

For a practical demonstration of setting up @ManyToOne and @OneToMany with mappedBy, the following video is very effective.

Spring Data JPA Relationships - ManyToMany, ManyToOne & OneToMany

The video 'Spring Data JPA Relationships' by Ali Bouali gives a concise coding demonstration of setting up a one-to-many/many-to-one relationship.

Please watch from 14:31 to 21:53. The instructor implements a relationship between Employee and Department, which is conceptually identical to our User and Order example. Focus on: How @ManyToOne and @JoinColumn are used on the Employee ('many') side. How @OneToMany(mappedBy=...) is used on the Department ('one') side. The rule of thumb: use @ManyToOne for the single object field, and @OneToMany for the collection field.

The resource "How to Set Up JPA Entity Relationships in Spring Boot" also provides an excellent summary table of these key annotations.

How to Set Up JPA Entity Relationships in Spring Boot

This article serves as a great quick reference for the annotations we've just discussed.

Review the table under the 'Key Annotations Explained' section. It provides a concise definition for @OneToMany(mappedBy=...), @ManyToOne, and @JoinColumn, reinforcing what you've just learned.


A Brief Look at Many-to-Many Relationships

While the learning outcome focuses on one-to-many, it's useful to briefly see how many-to-many relationships are handled, as they build on the same principles. Consider Product and Category entities: a product can be in multiple categories, and a category can contain multiple products.

In the database, this relationship requires a third table, known as a join table or link table, which contains foreign keys to both the products and categories tables (e.g., a product_categories table with product_id and category_id columns).

JPA models this with the @ManyToMany annotation.

  • You place a @ManyToMany annotation on the collection fields in both entities.
  • One side is designated the owner and uses @JoinTable to define the name of the join table and its foreign key columns.
  • The other side (the inverse side) uses mappedBy just as we saw in the @OneToMany example.

Spring Data JPA Relationships - ManyToMany, ManyToOne & OneToMany

Let's return to Ali Bouali's video to see a practical demonstration of implementing a many-to-many relationship.

Watch the section from 21:53 to 29:10. He implements a many-to-many relationship between Employee and Mission. Pay attention to how he uses @ManyToMany on both entities and defines the @JoinTable on the owning side.


Best Practice: Maintaining Relationship Consistency

A final, crucial point for bidirectional relationships involves keeping your Java objects in sync. When you establish a link from one side (e.g., order.setUser(user)), the other side of the relationship in memory is not automatically updated (user.getOrders() will not contain the new order yet).

This can lead to subtle bugs if you continue to work with the objects before they are re-fetched from the database. The best practice is to create helper methods on your entities to synchronize both sides of the relationship at once.

For our User entity, we could add:

public class User {
    // ... fields and other methods

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    public void addOrder(Order order) {
        orders.add(order);
        order.setUser(this); // This synchronizes the other side
    }

    public void removeOrder(Order order) {
        orders.remove(order);
        order.setUser(null);
    }
}

Now, instead of calling order.setUser(user), you would call user.addOrder(order), which guarantees that both the user.orders list and the order.user field are consistent in memory.

How to Set Up JPA Entity Relationships in Spring Boot

The 'How to Set Up JPA Entity Relationships' article has a small but very important section on this exact problem.

Please read the subsection 'Bidirectional Relationship Not Synced' under 'Common Problems and Solutions'. It clearly illustrates the problem and presents the helper method solution, which is a key pattern for robust entity design.


Conclusion

In this lesson, we mapped Java objects to a relational database schema, forming the heart of our application's persistence layer. You have moved from configuring a connection to defining the data model itself.

Key Takeaways:

  • JPA entities are Java classes annotated with @Entity and containing a primary key field marked with @Id.
  • The most efficient way to model a one-to-many database relationship is with a foreign key on the "many" side's table.
  • In a bidirectional JPA relationship, the @ManyToOne side is the owning side and defines the @JoinColumn (foreign key).
  • The @OneToMany side is the inverse side and uses mappedBy to point to the owner, preventing the creation of a redundant join table.
  • @ManyToMany relationships require a join table, which is configured using the @JoinTable annotation on the owning side.
  • Using helper methods to synchronize both sides of a bidirectional relationship is a crucial best practice for maintaining data consistency in your object model.

Now that we have defined our entities, we need a way to interact with them—to create, read, update, and delete them. In our next lesson, we will explore Spring Data JPA's repository abstraction, learning how to create repository interfaces extending JpaRepository for standard CRUD operations.