Save-Help 토이 프로젝트를 진행하며 여러 엔티티를 만들면서 @OneToMany, @ManyToOne 등을 사용했는데, Entity와 JPA 매핑과 어노테이션에 대해 정확한 개념 이해를 위해 글을 작성하게 되었습니다.
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100)
private String name;
@Column(name = "login_id", nullable = false, length = 50)
private String loginId;
@Column(length = 255, unique = true)
private String email;
@Column(length = 255, unique = true)
private String phone;
@Column(length = 255)
private String password;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Provider provider = Provider.LOCAL;
@Column(name = "provider_user_id", nullable = false, length = 255)
private String providerUserId;
@Column(name = "profile_image_url", length = 500)
private String profileImageUrl;
private String nickname;
private String gender;
private int age;
private String incomeLevel;
private boolean disabled;
private boolean inEmergency;
}
위 코드는 DB의 user 테이블과 매핑되고, user 객체의 필드들이 각각 테이블 컬럼에 대응합니다.
1. DB 테이블과 1:1 매핑
2. JPA가 엔티티를 관리
3. 객체 중심 개발 가능
DB 중심이 아니라 객체 모델링(도메인 주도 설계)이 가능합니다.
JPA 연관관계 매핑(JPA Relationship Mapping)이란 데이터베이스의 테이블 간의 관계를 Java 엔티티 객체 간의 관계로 매핑하는 것 입니다.
1. @OneToOne (1:1)
한 엔티티가 다른 엔티티와 1:1로 연결
예: 사용자(User) ↔ 사용자 프로필(UserProfile)
2. @ManyToOne (N:1)
여러 엔티티가 하나의 엔티티와 연결(외래키가 N쪽에 속함)
예: 게시글(Post) ↔ 작성자(User)
@Entity
public class Chat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 메시지 발신자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sender_id")
private User sender;
//매시지 수신자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "receiver_id")
private User receiver;
@Column(nullable = false, length = 1000)
private String message;
@Enumerated(EnumType.STRING)
private ChatType type;
private LocalDateTime sentAt;
@Column(name = "is_read", nullable = false)
private boolean read;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "call_id")
private Call call;
}
Chat엔티티에서 한 명의 사용자(User)는 여러 개의 채팅 메시지(Chat)를 보낼 수 있고, 한 명의 사용자(User)는 여러개의 채팅 메시지를 받을 수 있으므로 @ManyToOne을 사용하였습니다.
3. @OneToMany (1:N)
하나의 엔티티가 여러 엔티티를 가짐 (컬렉션 사용).
하지만 실제 DB에서는 N쪽에 FK가 있기 때문에 @OneToMany 단독 사용은 보다 보통 mappedBy와 같이 쓴다고 합니다.
예: 사용자(User) ↔ 게시글(Post) (User 기준)
@Entity
public class Helper {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Enumerated(EnumType.STRING)
private HelperRole role;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "center_id")
private CommunityCenter center;
@Column(length = 20)
private String phoneNumber;
private boolean available = true;
@OneToMany(mappedBy = "assignedHelper")
private List<Emergency> emergencies = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "hospital_id")
private Hospital hospital;
}
프로젝트에서 생성한 Helper라는 엔티티인데, emergencies라는 필드는 helper하나의 엔티티가 여러 긴급 상황들을 담당할 수 있으므로 @OneToMany를 사용하였습니다.
4. @ManyToMany (N:M)
엔티티와 엔티티가 다대다 관계.
하지만 DB는 다대다를 직접 지원하지 않기 때문에 중간 조인 테이블이 자동 생성됩니다.
단순 ManyToMany 예시
@Entity
public class Student {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses = new ArrayList<>();
}
@Entity
public class Course {
@Id @GeneratedValue
private Long id;
private String title;
@ManyToMany(mappedBy = "courses")
private List<Student> students = new ArrayList<>();
}
중간 테이블 엔티티 따로 만들기
@Entity
public class StudentCourse {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "student_id")
private Student student;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
private LocalDateTime enrolledDate; // 추가 필드 예시
}
@Entity
public class CommunityCenter {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 센터 이름
@Column(nullable = false, length = 100)
private String name;
// 센터 타입
@Enumerated(EnumType.STRING)
private CenterType type;
// 위치 정보 (위도, 경도)
private Double latitude;
private Double longitude;
// 연락 가능한 전화번호
@Column(length = 20)
private String phoneNumber;
// 운영 여부
private boolean active = true;
// 이 센터가 처리한 Emergency 요청들
@OneToMany(mappedBy = "assignedCenter")
private List<Emergency> emergencies = new ArrayList<>();
}
@Entity
@Table
@Id
@Column