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:
- Be annotated with
@Entity. - 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 theidfield as the primary key for this entity.@GeneratedValue: Configures how the primary key is generated.GenerationType.IDENTITYis 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 manyOrderentities can be associated with oneUserentity.@JoinColumn(name = "user_id"): This is the key annotation. It specifies the foreign key column in theorderstable. Without it, JPA would generate a default name.nullable = falsecreates aNOT NULLconstraint, 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 oneUsercan have manyOrderentities.- The
mappedBy = "user"attribute is essential. It tells JPA: "Do not create a join table for this relationship. The mapping is already handled by theuserfield in theOrderentity." This makes theUserside the inverse (non-owning) side and prevents redundant mapping information.
The following diagram provides a clear visual summary of this bidirectional setup.

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
@ManyToManyannotation on the collection fields in both entities. - One side is designated the owner and uses
@JoinTableto define the name of the join table and its foreign key columns. - The other side (the inverse side) uses
mappedByjust as we saw in the@OneToManyexample.
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
@Entityand 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
@ManyToOneside is the owning side and defines the@JoinColumn(foreign key). - The
@OneToManyside is the inverse side and usesmappedByto point to the owner, preventing the creation of a redundant join table. @ManyToManyrelationships require a join table, which is configured using the@JoinTableannotation 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.