Back to list
dev_to 2026年3月7日

JPAのOne-to-Oneのマッピングを使用してハチミツーハイバリー関係

JPA Mapping with Hibernate-One-to-One Relationship

Translated: 2026/3/7 13:01:43

Japanese Translation

JPA(Java Persistence API)は、プログラマーがライブラリとアプリケーション間で文書化データを操作するのに必要なSQLのための大規模なエスケープコードを書きすぎず、オブジェクトの関連物を使用して関数を表示することによりデータベースとの交互作用を統一し、ロティンデータベースにアクセスできるようにする標準的なJavaの仕様です。主な点:</br>JPAは仕様で、実装ではありません。</br>JPAはJPA標準APIを開発者にリレーションサンクライテリアを使用してリレーショナルダンプとデータタブレットを関連付けるための標準仕様APIの一部です。リレーションスタンスとデータタブレットを関連付けるエンティティマッピングは、オブジェクトとデータベースとの文書化操作に非常に役立っています。</br>ハチミツーハイバリーJPA例を示すために、User(親)を使用してUserProfile(子)がどのように作業されているか説明します。つまり、フロアキープリーンの外向け関係性を持つ1つのユーザーサンプルは次の通りです。UserProfile.java -所有側で(ファクターを保持する) @Entity @テーブル(name =

Original Content

JPA (Java Persistence API) The Java Persistence API (JPA) is a Java specification that defines a standard way to manage relational data in Java applications using Object Relational Mapping (ORM). It provides a set of interfaces and annotations that allow developers to map Java objects to database tables, perform CRUD operations, and manage persistence without writing large amounts of SQL. Key points: JPA is a specification, not an implementation It standardizes how Java applications interact with relational databases It uses annotations and configuration to map objects to tables It simplifies database operations through entity management and persistence context Hibernate Hibernate is a popular open-source ORM framework that provides the implementation of the JPA specification. It allows developers to interact with databases using Java objects instead of writing complex SQL queries, making the application code loosely coupled with the underlying database. Key points: Hibernate is a JPA implementation Provides powerful ORM capabilities Handles CRUD operations automatically Supports caching, lazy loading, and transaction management Reduces boilerplate JDBC code Relationship Between JPA and Hibernate JPA → Specification (standard API) Hibernate → Implementation of that specification In practice: Developers write code using JPA annotations and interfaces Hibernate executes the actual database operations Relationship Mapping in JPA Key Best Practices Always use FetchType.LAZY on relationships — avoids N+1 query problem mappedBy on the non-owning side (the side without the FK column) Cascade carefully — only cascade from parent to child (Order → OrderItem), not upward @JoinColumn explicitly names your FK column for clarity Snapshot prices in OrderItem.unitPrice — never rely on current Product.price Use Set instead of List for @ManyToMany to avoid duplicate join queries A One-to-One relationship occurs when one entity is associated with exactly one instance of another entity. Example: Person → Passport User → Profile Order → Invoice Example: The User entity is the parent, while the UserProfile is the child association because the Foreign Key is located in the 'user_profile' database table. This relationship is mapped as follows: UserProfile.java — OWNING side (holds the FK) @Entity @Table(name = "user_profile") @Getter @Setter public class UserProfile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String phone; @Column(nullable = false) private String address; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", unique = true) private User user; } The user_profile table contains a Primary Key (PK) column (e.g. id) and a Foreign Key (FK) column (e.g. user_id). User.java — the INVERSE side @Getter @Setter @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true, length = 50) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private boolean enabled = true; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private UserProfile profile; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "role"})) @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) private Set roles = new HashSet<>(); public void addRole(Role role) { if (role != null) { this.roles.add(role); } } public void removeRole(Role role) { this.roles.remove(role); } public boolean hasRole(Role role) { return this.roles.contains(role); } public void setProfile(UserProfile profile) { this.profile = profile; if (profile != null) { profile.setUser(this); } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User user)) return false; return username != null && username.equals(user.getUsername()); } @Override public int hashCode() { return getClass().hashCode(); } } @ElementCollection stores simple values (Strings) in a separate table — not a full entity relationship, so there's no "other side" to sync. Specifying FetchType.LAZY for the non-owning side of the @OneToOne association will not affect the loading. On the inverse side (mappedBy), Hibernate doesn't know if the associated entity exists or not without hitting the DB. which defeats the purpose of LAZY. So it just loads eagerly regardless of what you specify. What's Happening Query 1: select from users ← your actual request YOU DIDN'T ASK FOR THIS Query 2 is Hibernate silently probing — "does a profile exist for this user?" — because profile is on the inverse side and has no FK column to check. Query 3 (user_roles) firing is expected and correct — @ElementCollection(fetch = FetchType.EAGER) is explicitly eager. If you don't need roles on every fetch, change it: @ElementCollection(fetch = FetchType.LAZY) // load roles only when needed EAGER — UserDetails needs roles immediately on authentication. Option 1: @LazyToOne — Bytecode Instrumentation (True Lazy) // User.java Requires adding the Hibernate bytecode enhancer to your build: Option 2: @MapsId — Shared Primary Key (Best & Simplest) @OneToOne Relationship Using @MapsId The best way to map a @OneToOne relationship in Hibernate is by using @MapsId, which allows the child entity to share the same primary key as the parent entity. In this approach: The child table uses the parent’s primary key as its own primary key. This avoids creating an additional foreign key column. It also simplifies fetching, since the child entity can always be retrieved using the parent’s identifier. For example, in a User and UserProfile relationship: Each User has exactly one UserProfile The UserProfile shares the same primary key as the User @Entity @Table(name = "user_profile") @Getter @Setter public class UserProfile { @Id //no @GeneratedValue — inherited from User private Long id; @Column(nullable = false) private String phone; @Column(nullable = false) private String address; @OneToOne(fetch = FetchType.LAZY) @MapsId // tells Hibernate: this entity's PK = users.id @JoinColumn(name = "id") private User user; So in summary The id column in UserProfile serves as both PK and FK — its value is copied directly from User.id. User UserProfile @GeneratedValue yes — generates own PK no — copies from User Owns the relationship no profile field yes — has @MapsId Probe query on fetch gone — no inverse field — Save strategy userRepository.save(user) userProfileRepository.save(profile) User generates its own PK and knows nothing about UserProfile. UserProfile borrows User's PK via @MapsId — clean, no extra column, no probe query. Removing profile from User.java made it unidirectional. Bidirectional = convenience of user.getProfile() but costs an extra query. Unidirectional = no extra query but you lose navigation from User side. Bytecode enhancement is the only way to have both. For most apps — skip the plugin, go unidirectional, and use JOIN FETCH when you need both. You get zero runtime cost, zero probe query, and full control over when profile loads. For JOIN FETCH to work, User must have the profile field back — bidirectional. @Query("SELECT u FROM User u LEFT JOIN FETCH u.profile WHERE u.id = :id") Optional findByIdWithProfile(@Param("id") Long id); @MapsId Solves It Before (@JoinColumn on profile) After (@MapsId) Profile FK user_profile.user_id user_profile.id = users.id Hibernate knows ID? Must probe DB Already has it Extra query on getUser? Always fires Only when accessed DB schema Extra user_id column Shared PK, cleaner @MapsId No extra foreign key column Better database normalization** More efficient joins No need for a bidirectional association The UserProfile can always be fetched using the User ID @MapsId Tight Coupling Between Entities The child entity (UserProfile) cannot exist without the parent (User) because it shares the same primary key. This makes the relationship very tightly coupled. Insert Order Dependency The parent entity must be persisted first. Only then can the child entity be created because it needs the parent's ID. Example flow: save(User) save(UserProfile) Less Flexibility If in the future the relationship changes from one-to-one → one-to-many, the schema must be redesigned. A separate foreign key mapping would be easier to extend. Harder to Manage Independent Lifecycle Since the child shares the same primary key, managing it independently becomes difficult. For example: Deleting User automatically invalidates UserProfile. Not Suitable for Optional Relationships If the relationship is optional, @MapsId may not be ideal. Sometimes a User might exist without a UserProfile. More Complex for Beginners Developers unfamiliar with JPA may find: @MapsId shared primary key entity lifecycle slightly harder to understand compared to simple FK mapping. Migration / Schema Changes Are Harder If you later need to add a separate primary key to the child table, it requires database migration and entity refactoring. @MapsId Is Ideal Use it when: The relationship is strictly one-to-one The child cannot exist without the parent The child is more like an extension of the parent