A relational database is a structured collection of data organized into tables, where each table consists of rows and columns. The relationships between these tables are defined using keys, allowing for efficient data retrieval and management.
Each record in one table is linked to exactly one record in another table, and vice versa.
In the Man class, the @JoinColumn(name = "partner_id") annotation on the woman field specifies that the Man table has a foreign key column named partner_id. This partner_id column references the id column in the Woman table, establishing a relationship between Man and Woman.
Man.java
@Entity
@Table(name = "man")
public class Man {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "partner_id")
private Woman woman;
}
Woman.java
@Entity
@Table(name = "woman")
public class Woman {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
If the following is added to Woman.java, we have a Bidirectional relationship in which both entities are aware of the relationship and can access each other, although there is only own entity that is the "owner" of the foreign key. When you modify the relationship (e.g., assigning a different Woman to a Man), the changes will be reflected in the database only if they are made through the owning side. The non-owning side will NOT automatically trigger an update to the database.
@OneToOne(mappedBy = "woman")
private Man man;
💡Unidirectional Relationship💡
In a unidirectional relationship, only one entity knows about the relationship. For example, in the initial code you provided, the
Manclass has a reference to theWomanclass (@OneToOne private Woman woman;), but theWomanclass does not have any reference back to theManclass. This means that while you can navigate from aManobject to its associatedWoman, you cannot easily navigate from aWomanobject to its associatedMan.
Multiple records in one table are linked to a single record in another table. Take the following example of food and users in an order management software in which a single user can order multiple dishes(food).
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Add the following code under the User class:
@OneToMany(mappedBy = "user")
private List<Food> foodList = new ArrayList<>();
💡Who gets to the foreign key(FK) owner?💡
The foreign key belongs to the class with the 'Many' attribute in the 'Many to one'. This is because the "many" class would require identification for each object regarding its relationship with the "one" class. For example, if we had post and comments class where one or more comments can belong to a single post, from each comment we need to know which post it is mapped to. While the other way is also possible (identifying post is associated which comment), you cannot put a list of comments in the data table (although you can do it in the actual class). Therefore, the FK belongs to the "many" class.
A single record in one table is linked to multiple records in another table. Let's look at this again with the Food and User class where the foreign key owning entity is the 'Many' in 'OneToMany'.
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany
@JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Although a bidirectional relationship is uncommon for this relationship, we can set it up by modifying the User class as below:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "food_id", insertable = false, updatable = false)
private Food food;
}
Multiple records in one table are linked to multiple records in another table, typically managed through a junction table like below.

Food
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToMany(mappedBy = "food")
private List<Order> orderList = new ArrayList<>();
}
Users
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Order> orderList = new ArrayList<>();
}
Orders
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "food_id")
private Food food;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
💡 What about using the @ManyToMany annotation? -> Not recommened for the following reasons💡
When you use the@ManyToManyannotation in JPA, it automatically creates a junction table behind the scenes to manage the relationship between the two entities. This junction table contains the foreign keys from both entities but typically doesn't allow for any additional fields or customization unless explicitly defined. However, this automatic junction table is very basic, typically consisting of only the two foreign keys, and lacks flexibility for adding extra attributes (like timestamps, relationship status, or other metadata) or for customizing queries and cascade operations.
- Explicit control over the relationship: Junction tables give more flexibility, allowing better control over cascade operations and query performance.
- Performance and complexity: Explicit junction tables can be optimized for better performance in large applications, whereas
@ManyToManycan result in inefficient queries.- Handling complex relationships: Junction tables can manage more complex requirements like filtering, sorting, or manipulating relationships.
- Better control over cascade operations: Junction tables provide finer control over persistence and deletion compared to
@ManyToMany.- Normalization best practices: Using junction tables aligns with database normalization principles, reducing redundancy and improving integrity.
Unidirectional
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Bidirectional
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToMany
@JoinTable(name = "orders", // 중간 테이블 생성
joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
private List<User> userList = new ArrayList<>();
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "userList")
private List<Food> foodList = new ArrayList<>();
}