Home/Learn/Hibernate & JPA/One-to-One Mapping

One-to-One Mapping

Intermediate
Relationships

@OneToOne maps two entities with a shared or foreign-key strategy; use @MapsId to share the primary key between parent and child for an efficient 1:1 relationship.

Overview

@OneToOne maps two entities where each instance of A is associated with exactly one instance of B. The common strategies are: (1) Foreign key — child table has a unique FK column pointing to the parent PK; (2) Shared primary key (@MapsId) — child shares the same PK value as the parent, eliminating the extra FK column and making joins trivial; (3) Join table — a separate association table (rare for 1:1). Always make @OneToOne relationships LAZY (fetch = FetchType.LAZY) to avoid loading the associated entity on every fetch of the owner.

Foreign-Key Strategy

The child entity holds a foreign key column referencing the parent PK. @JoinColumn specifies the FK column name. Make the relationship bidirectional with mappedBy on the owning side to avoid extra joins.

Java — bidirectional @OneToOne with FK
// Parent entity — User
@Entity
@Table(name = "users")
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;

    // Bidirectional — mappedBy points to the field in UserProfile that owns the FK
    @OneToOne(mappedBy = "user",
              fetch = FetchType.LAZY,
              cascade = CascadeType.ALL,
              orphanRemoval = true)
    private UserProfile profile;
}

// Child entity — UserProfile owns the FK
@Entity
@Table(name = "user_profiles")
public class UserProfile {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", unique = true)   // FK column in user_profiles
    private User user;

    private String bio;
    private String avatarUrl;
}

// Create with cascade
User user = new User("alice@example.com");
UserProfile profile = new UserProfile("Java developer", "/avatars/alice.jpg");
profile.setUser(user);
user.setProfile(profile);
userRepository.save(user);  // cascades to UserProfile

Shared Primary Key (@MapsId)

@MapsId makes the child entity share the parent's PK — no extra FK column needed. The child table's PK IS the FK to the parent. This is the most efficient 1:1 strategy.

Java — @MapsId shared primary key strategy
// Parent
@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(mappedBy = "order", fetch = FetchType.LAZY,
              cascade = CascadeType.ALL, orphanRemoval = true)
    private OrderDetail detail;
}

// Child — PK shared with Order.id
@Entity
@Table(name = "order_details")
public class OrderDetail {
    @Id                         // PK
    private Long id;            // same value as order.id

    @MapsId                     // tells Hibernate: id = order.id
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id")    // FK column is also the PK column
    private Order order;

    private String shippingAddress;
    private String trackingNumber;
}

// Table structure:
// orders:        id | status | total
// order_details: id | shipping_address | tracking_number
//                ↑ PK and FK — same column, joins are trivially efficient

// Create
Order order = new Order(OrderStatus.PLACED, total);
OrderDetail detail = new OrderDetail(address, tracking);
detail.setOrder(order);
order.setDetail(detail);
orderRepository.save(order);

Lazy Loading Gotcha & Optional Associations

@OneToOne with LAZY fetch only works correctly when the association is non-null AND the child side owns the FK. On the non-owning (mappedBy) side, Hibernate must issue a SQL query to check existence even for LAZY — this is the "proxy problem".

Java — @OneToOne lazy loading gotcha and solution
// Problem: Hibernate cannot lazily proxy the non-owning side
// because it doesn't know if the associated entity exists without a query

// User → UserProfile (non-owning, mappedBy side)
// Even with LAZY, loading a User issues a second query for UserProfile
// to determine: does a profile exist for this user?

// Solution 1: @LazyToOne (Hibernate-specific, advanced bytecode instrumentation)
// Solution 2: Always access via the OWNING side (UserProfile → User is truly lazy)
// Solution 3: Use Optional<UserProfile> and check existence in the service layer

@Service
public class UserService {
    public Optional<UserProfile> getProfile(Long userId) {
        // Query UserProfile by userId directly — avoids the proxy issue
        return userProfileRepository.findByUserId(userId);
    }
}

// Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {
    Optional<UserProfile> findByUserId(Long userId);
}

// Rule of thumb:
// ✓ Owning side (has @JoinColumn) — LAZY works correctly
// ✗ Non-owning side (mappedBy) — LAZY may be proxied incorrectly
// ✓ Use @MapsId (child IS the owning side) to get correct lazy loading on both sides

Key Points to Remember

  • 1@OneToOne with FK: child has a unique FK column; @JoinColumn names the column.
  • 2@MapsId shares the parent PK as the child PK — no extra column, most efficient strategy.
  • 3Always declare @OneToOne with fetch = FetchType.LAZY to avoid unintended joins.
  • 4The non-owning side (mappedBy) cannot be truly lazy without bytecode instrumentation.
  • 5Use cascade = CascadeType.ALL + orphanRemoval = true to manage child lifecycle via parent.
  • 6Query via the owning side (FK holder) for correct lazy loading behaviour.

Interview Questions

Sign in to ask Aria
1

What is the difference between FK-based and shared-PK @OneToOne strategies?

MediumAmazon
2

What does @MapsId do and why is it preferred for 1:1 relationships?

MediumPivotal
3

Why does @OneToOne with LAZY fetch not work correctly on the non-owning side?

HardNetflix
4

What is the difference between cascade = ALL and orphanRemoval = true?

MediumInfosys
5

How would you query a @OneToOne relationship without the N+1 problem?

HardUber

Ask Aria about One-to-One Mapping

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…