Introduction
When working with relational databases in Java, JPA (Java Persistence API) makes it easier to map relationships between entities. One of the most fundamental relationships is One-to-One mapping. In this blog, we’ll break it down in a way that makes it simple and enjoyable to learn!
By the end of this article, you’ll understand:
- What One-to-One mapping is.
- How to implement it in Spring Boot with JPA and Hibernate.
- The different ways to set up the relationship.
- Practical use cases for real-world applications.
Let’s dive in!
What is One-to-One Mapping?
A One-to-One relationship means that one entity is associated with exactly one other entity. For example:
- A User can have exactly one Profile.
- A Car can have exactly one Engine.
In database terms, this means that the primary key of one table is linked to the primary key of another table.
How Does One-to-One Mapping Work in JPA?
JPA provides the @OneToOne
annotation to define this relationship. There are different ways to implement it, depending on how you design your database:
- Using a Foreign Key (Recommended)
- Using a Shared Primary Key
- Using a Join Table ️
Let’s see how we can implement these approaches in Spring Boot!
Implementing One-to-One Mapping in Spring Boot
1️⃣ One-to-One Mapping Using a Foreign Key (Recommended)
The most common way to set up a One-to-One relationship is by using a foreign key. Here’s how we do it:
Step 1: Create the User
Entity
<span>import</span> <span>jakarta.persistence.*</span><span>;</span><span>@Entity</span><span>public</span> <span>class</span> <span>User</span> <span>{</span><span>@Id</span><span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span><span>private</span> <span>Long</span> <span>id</span><span>;</span><span>private</span> <span>String</span> <span>name</span><span>;</span><span>@OneToOne</span><span>(</span><span>cascade</span> <span>=</span> <span>CascadeType</span><span>.</span><span>ALL</span><span>)</span><span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"profile_id"</span><span>,</span> <span>referencedColumnName</span> <span>=</span> <span>"id"</span><span>)</span><span>private</span> <span>Profile</span> <span>profile</span><span>;</span><span>// Getters and Setters</span><span>}</span><span>import</span> <span>jakarta.persistence.*</span><span>;</span> <span>@Entity</span> <span>public</span> <span>class</span> <span>User</span> <span>{</span> <span>@Id</span> <span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span> <span>private</span> <span>Long</span> <span>id</span><span>;</span> <span>private</span> <span>String</span> <span>name</span><span>;</span> <span>@OneToOne</span><span>(</span><span>cascade</span> <span>=</span> <span>CascadeType</span><span>.</span><span>ALL</span><span>)</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"profile_id"</span><span>,</span> <span>referencedColumnName</span> <span>=</span> <span>"id"</span><span>)</span> <span>private</span> <span>Profile</span> <span>profile</span><span>;</span> <span>// Getters and Setters</span> <span>}</span>import jakarta.persistence.*; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "profile_id", referencedColumnName = "id") private Profile profile; // Getters and Setters }
Enter fullscreen mode Exit fullscreen mode
Step 2: Create the Profile
Entity
<span>import</span> <span>jakarta.persistence.*</span><span>;</span><span>@Entity</span><span>public</span> <span>class</span> <span>Profile</span> <span>{</span><span>@Id</span><span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span><span>private</span> <span>Long</span> <span>id</span><span>;</span><span>private</span> <span>String</span> <span>bio</span><span>;</span><span>private</span> <span>String</span> <span>website</span><span>;</span><span>// Getters and Setters</span><span>}</span><span>import</span> <span>jakarta.persistence.*</span><span>;</span> <span>@Entity</span> <span>public</span> <span>class</span> <span>Profile</span> <span>{</span> <span>@Id</span> <span>@GeneratedValue</span><span>(</span><span>strategy</span> <span>=</span> <span>GenerationType</span><span>.</span><span>IDENTITY</span><span>)</span> <span>private</span> <span>Long</span> <span>id</span><span>;</span> <span>private</span> <span>String</span> <span>bio</span><span>;</span> <span>private</span> <span>String</span> <span>website</span><span>;</span> <span>// Getters and Setters</span> <span>}</span>import jakarta.persistence.*; @Entity public class Profile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String bio; private String website; // Getters and Setters }
Enter fullscreen mode Exit fullscreen mode
In the User
entity, we used @OneToOne
and @JoinColumn(name = "profile_id")
to specify that the profile_id
column in the User
table acts as a foreign key pointing to the id
column in the Profile
table.
2️⃣ One-to-One Mapping Using a Shared Primary Key
In this approach, the primary key of one table is also used as the primary key of another table.
Update the Profile
Entity
<span>@Entity</span><span>public</span> <span>class</span> <span>Profile</span> <span>{</span><span>@Id</span><span>private</span> <span>Long</span> <span>id</span><span>;</span><span>private</span> <span>String</span> <span>bio</span><span>;</span><span>private</span> <span>String</span> <span>website</span><span>;</span><span>@OneToOne</span><span>@MapsId</span><span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"id"</span><span>)</span><span>private</span> <span>User</span> <span>user</span><span>;</span><span>}</span><span>@Entity</span> <span>public</span> <span>class</span> <span>Profile</span> <span>{</span> <span>@Id</span> <span>private</span> <span>Long</span> <span>id</span><span>;</span> <span>private</span> <span>String</span> <span>bio</span><span>;</span> <span>private</span> <span>String</span> <span>website</span><span>;</span> <span>@OneToOne</span> <span>@MapsId</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"id"</span><span>)</span> <span>private</span> <span>User</span> <span>user</span><span>;</span> <span>}</span>@Entity public class Profile { @Id private Long id; private String bio; private String website; @OneToOne @MapsId @JoinColumn(name = "id") private User user; }
Enter fullscreen mode Exit fullscreen mode
The @MapsId
annotation ensures that the Profile
entity shares the same primary key as User
.
3️⃣ One-to-One Mapping Using a Join Table
Sometimes, you may want a third table to manage the relationship. In this case, JPA allows us to use @JoinTable
.
Modify the User
Entity
<span>@OneToOne</span><span>(</span><span>cascade</span> <span>=</span> <span>CascadeType</span><span>.</span><span>ALL</span><span>)</span><span>@JoinTable</span><span>(</span><span>name</span> <span>=</span> <span>"user_profile"</span><span>,</span><span>joinColumns</span> <span>=</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"user_id"</span><span>),</span><span>inverseJoinColumns</span> <span>=</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"profile_id"</span><span>))</span><span>private</span> <span>Profile</span> <span>profile</span><span>;</span><span>@OneToOne</span><span>(</span><span>cascade</span> <span>=</span> <span>CascadeType</span><span>.</span><span>ALL</span><span>)</span> <span>@JoinTable</span><span>(</span><span>name</span> <span>=</span> <span>"user_profile"</span><span>,</span> <span>joinColumns</span> <span>=</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"user_id"</span><span>),</span> <span>inverseJoinColumns</span> <span>=</span> <span>@JoinColumn</span><span>(</span><span>name</span> <span>=</span> <span>"profile_id"</span><span>))</span> <span>private</span> <span>Profile</span> <span>profile</span><span>;</span>@OneToOne(cascade = CascadeType.ALL) @JoinTable(name = "user_profile", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "profile_id")) private Profile profile;
Enter fullscreen mode Exit fullscreen mode
This approach creates a user_profile table that holds the user_id
and profile_id
columns, acting as a bridge between User
and Profile
.
When to Use One-to-One Mapping?
One-to-One relationships are useful in scenarios where:
The related data is rarely accessed separately (e.g., user profile information).
You need data integrity with clear ownership (e.g., a unique identity document per user).
You want to simplify queries and avoid data duplication.
Conclusion
In this article, we explored:
- What One-to-One mapping is.
- Three ways to implement it in Spring Boot with JPA.
- When to use each approach.
One-to-One mapping is a powerful way to structure your database relationships effectively. Mastering it will help you build better, more scalable applications!
Got questions or feedback? Drop a comment below! Happy coding!
暂无评论内容