Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- baekjoon
- UXUIPrimary
- UXUI기초정복
- 백엔드
- 부트캠프
- OPENPATH
- 디자인교육
- 객체지향
- 국비지원
- mysql
- Java
- 환급챌린지
- 국비지원교육
- 백준
- 내일배움카드
- 패스트캠퍼스
- UXUI챌린지
- 오픈챌린지
- 오픈패스
- 오블완
- KDT
- 백엔드개발자
- 내일배움캠프
- Be
- 디자인강의
- 티스토리챌린지
- Spring
- 국비지원취업
- 백엔드 부트캠프
- 디자인챌린지
Archives
- Today
- Total
군만두의 IT 공부 일지
[2주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - JPA 심화 본문

오늘은 JPA에 대해서 조금 더 심화된 내용을 공부했다. 지난 블로그에서는 JPA가 무엇이고 어떻게 사용할 수 있는지 간략히 정리했는데, 이번에는 엔티티의 연관 관계에 대해서 정리하려고 한다.
1. Entity 연관 관계
주문 APP DB 테이블 설계
- 고객(users) 테이블
id name 1 Robbie 2 Robbert CREATE TABLE users ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255), PRIMARY KEY (id) );
- 음식(food) 테이블
id name price 1 후라이드 치킨 15000 2 양념 치킨 20000 3 고구마 피자 30000 4 아보카도 피자 50000 CREATE TABLE food ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(255), price FLOAT NOT NULL, PRIMARY KEY (id) );
DB 테이블 간의 연관 관계
- 고객이 음식을 주문 시, 주문 정보는 어느 테이블에 들어가야 할까?
- 고객이 여러 음식을 주문할 수 있으므로 1:N 관계로 설정
- 주문 정보는 별도의 orders 테이블로 관리
- 주문 테이블 생성 및 연관 관계 설정
CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT, food_id BIGINT, order_date DATE, PRIMARY KEY (id) ); ALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE orders ADD CONSTRAINT orders_food_fk FOREIGN KEY (food_id) REFERENCES food (id);
- 데이터 삽입 예시
INSERT INTO users (name) VALUES ('Robbie'), ('Robbert'); INSERT INTO food (name, price) VALUES ('후라이드 치킨', 15000), ('양념 치킨', 20000); INSERT INTO orders (user_id, food_id, order_date) VALUES (1, 1, SYSDATE());
- 정리
- DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회할 수 있다.
- Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있다.
- 하지만 상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법이 없다.
- 따라서 Entity에서는 DB 테이블에는 없는 방향의 개념이 존재한다.
2. 1:1 관계 설정
@OneToOne 애너테이션은 1대 1 관계를 맺어주는 역할을 한다.
- @OneToOne 사용 예시
- 고객과 음식의 1:1 관계를 설정한다. 주문한 음식이 하나의 고객에게만 할당되는 경우를 나타낸다.
- 단방향 @OneToOne
- 고객(User)이 음식(Food)을 참조할 때 설정한다.
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name = "food_id") // 외래 키 지정 private Food food; }
- 양방향 @OneToOne
- 음식에서도 고객을 참조할 수 있게 설정한다.
@Entity @Table(name = "food") public class Food { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @OneToOne(mappedBy = "food") // 고객에서 설정한 외래 키의 주인 private User user; }
3. N:1 관계 설정
@ManyToOne 애너테이션은 N대 1 관계를 맺어주는 역할을 한다.
- @ManyToOne 사용 예시
- 음식과 고객 간의 N:1 관계를 설정할 때 사용한다. 여러 음식이 한 명의 고객에게 주문되는 경우를 나타낸다.
- 단방향 @ManyToOne
- 음식(Food)에서 고객(User)을 참조할 때 설정한다.
@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; }
- 양방향 @ManyToOne
- 음식 엔티티에서 고객을 참조하는 @ManyToOne 관계를 설정한다.
@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; }
- 고객 엔티티에서 여러 음식을 참조할 수 있도록 @OneToMany 관계를 설정한다. 이때 mappedBy 속성을 사용하여 관계의 소유자가 음식(Food)임을 명시한다.
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = "user") // 음식과의 관계에서 고객이 참조되는 방식 지정 private List<Food> foodList = new ArrayList<>(); // 음식 리스트에 음식 추가하는 편의 메서드 public void addFood(Food food) { foodList.add(food); food.setUser(this); // 양방향 관계 설정 } }
4. 1:N 관계 설정
@OneToMany 애너테이션은 N대 1 관계를 맺어주는 역할을 한다.
- @ManyToOne 사용 예시
- 고객과 음식 간의 1:N 관계를 설정할 때 사용한다. 한 고객이 여러 음식을 주문하는 경우를 나타낸다.
- 단방향 1:N 관계 설정
- 고객(User) 엔티티에서 여러 음식(Food) 엔티티를 참조할 수 있게 설정한다.
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany // 기본 Fetch 타입은 LAZY @JoinColumn(name = "user_id") // 이 설정은 User 테이블에 food_id 외래 키가 없을 때 사용 private List<Food> foods = new ArrayList<>(); public void addFood(Food food) { foods.add(food); food.setUser(this); // 양방향 설정을 위해 사용될 수 있음 } }
- 양방향 1:N 관계 설정
- 음식(Food)에서도 고객(User)을 참조하며, 고객 역시 여러 음식을 참조할 수 있도록 설정한다.
@Entity @Table(name = "food") public class Food { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @ManyToOne(fetch = FetchType.LAZY) // 고객 정보는 필요할 때 로딩 @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; @OneToMany(mappedBy = "user") // mappedBy 속성으로 Food 엔티티의 'user' 필드가 연관 관계의 주인임을 명시 private List<Food> foods = new ArrayList<>(); public void addFood(Food food) { foods.add(food); food.setUser(this); } }
5. N:M 관계 설정
@ManyToMany 애너테이션은 N대 M 관계를 맺어주는 역할을 한다.
- @ManyToMany 사용 예시
- 음식과 고객 간의 N:M 관계를 설정할 때 사용한다. 여러 고객이 동일한 음식을 주문할 수 있고, 한 고객이 여러 음식을 주문할 수 있는 경우를 나타낸다.
- 단방향 @ManyToMany
- 고객(User)에서 음식(Food)을 참조한다. 중간 테이블(orders)이 자동으로 생성된다.
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToMany @JoinTable( name = "orders", // 중간 테이블 이름 joinColumns = @JoinColumn(name = "user_id"), // 현재 Entity의 참조 키 inverseJoinColumns = @JoinColumn(name = "food_id") // 반대쪽 Entity의 참조 키 ) private List<Food> foodList = new ArrayList<>(); }
- 양방향 @ManyToMany
- 음식(Food)에서도 고객(User)을 참조할 수 있게 설정한다.
@Entity @Table(name = "food") public class Food { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; @ManyToMany(mappedBy = "foodList") private List<User> userList = new ArrayList<>(); }
6. 관계 유형별 방향성의 장단점
관계 유형 | 방향성 | 장점 | 단점 |
1:1 | 단방향 | - 단순하고 명확함. - 성능 최적화 용이. |
- 연관된 객체에 접근하기 위해 항상 소유자를 통해야 함. |
1:1 | 양방향 | - 두 엔티티 간 직접 접근 가능. - 관계의 이해가 용이. |
- 관리 복잡도 증가. - 순환 참조 가능성. |
N:1 | 단방향 | - 구현이 간단함. - 자주 사용되는 패턴. |
- 반대 방향에서 접근 불가능. |
N:1 | 양방향 | - 반대 방향에서도 접근 가능. - 관계를 양쪽에서 관리. |
- 관리 복잡도 증가. - 순환 참조 가능성. |
1:N | 단방향 | - 자연스러운 모델링. - 구현 용이. |
- N 측에서 1 측에 접근하기 어려움. |
1:N | 양방향 | - 양쪽에서 자유롭게 접근 가능. - 데이터 일관성 유지 용이. |
- 성능 문제 (EAGER 로딩). - 복잡한 관리 요구. |
N:M | 단방향 | - 구현이 간단하고 직관적임. | - 반대 방향에서의 접근이 어려움. |
N:M | 양방향 | - 양쪽에서 자유롭게 접근 가능. - 더 유연한 데이터 관리. |
- 매핑 복잡도 증가. - 성능 저하 가능성. |
- 단방향 관계는 일반적으로 구현이 간단하며, 엔티티 간의 관계가 명확할 때 사용된다. 엔티티 간의 상호 작용이 필요할 때 제약이 따를 수 있다.
- 양방향 관계는 두 엔티티 간에 자유로운 상호 작용을 가능하지만, 엔티티 간의 관계를 설정하고 유지하는 데 많은 주의가 필요하다. 잘못 구현된 양방향 관계는 성능 저하와 같은 문제를 초래할 수 있다.
7. 추가적인 JPA 설정
- FetchType 설정
- LAZY(지연 로딩): 지연 로딩은 연관된 엔티티를 실제로 사용하는 시점까지 로딩을 지연시키는 방식이다. 예를 들어, 고객 정보를 조회할 때 연관된 주문 정보는 필요할 때만 데이터베이스에서 로드된다. 이 방식은 초기 로딩 시간을 줄이고, 리소스 사용을 최적화할 수 있다.
- EAGER(즉시 로딩): 즉시 로딩은 엔티티를 조회할 때 연관된 엔티티도 함께 즉시 로드하는 방식이다. 관계된 모든 데이터를 한 번에 로딩하므로, 복잡한 조인 쿼리나 여러 SQL 호출이 필요 없는 상황에서 유용하다. 하지만 불필요한 데이터까지 로드할 수 있어 성능 저하를 일으킬 수 있다.
@Entity @Table(name = "customer") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // LAZY 로딩: 연관된 엔티티(Order)를 실제 사용하는 시점에 로드함. @OneToMany(fetch = FetchType.LAZY, mappedBy = "customer") private List<Order> orders = new ArrayList<>(); // EAGER 로딩: Customer 조회 시 연관된 Profile도 즉시 로드됨. @OneToOne(fetch = FetchType.EAGER) @JoinColumn(name = "profile_id") private Profile profile; }
- Cascade 설정
- 영속성 전이(Cascade): 영속성 전이 설정은 특정 엔티티의 영속 상태 변화를 연관된 엔티티에도 전파하는 것이다. 예를 들어, 부모 엔티티를 저장할 때 자동으로 자식 엔티티도 저장되게 할 수 있다.
- CascadeType.PERSIST: 부모 엔티티를 저장할 때 연관된 자식 엔티티도 함께 저장한다.
- CascadeType.REMOVE: 부모 엔티티를 삭제할 때 연관된 자식 엔티티도 함께 삭제한다.
- CascadeType.MERGE: 부모 엔티티 상태를 병합할 때 연관된 자식 엔티티의 상태도 함께 병합한다.
- CascadeType.REFRESH: 부모 엔티티를 새로고침할 때 연관된 자식 엔티티도 함께 새로고침한다.
- CascadeType.DETACH: 부모 엔티티가 영속성 컨텍스트에서 분리될 때 연관된 자식 엔티티도 함께 분리된다.
- CascadeType.ALL: 위의 모든 영속성 전이 옵션을 적용한다.
@Entity @Table(name = "parent") public class Parent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Parent 엔티티의 생명주기 변화가 Child 엔티티에 전파됨. 저장, 삭제, 병합 등의 연산이 자식에도 적용됨. @OneToMany(cascade = CascadeType.ALL, mappedBy = "parent") private List<Child> children = new ArrayList<>(); }
- 영속성 전이(Cascade): 영속성 전이 설정은 특정 엔티티의 영속 상태 변화를 연관된 엔티티에도 전파하는 것이다. 예를 들어, 부모 엔티티를 저장할 때 자동으로 자식 엔티티도 저장되게 할 수 있다.
- 고아 Entity 삭제(orphanRemoval)
- 고아 객체 삭제: 고아 객체 삭제 설정은 부모 엔티티와의 관계가 끊어진 자식 엔티티를 자동으로 삭제한다. 예를 들어, 한 목록에서 특정 자식을 제거하면, 그 자식 엔티티는 데이터베이스에서도 삭제된다.
- 연관된 엔티티가 더 이상 참조되지 않을 때 불필요하게 데이터베이스에 남아 있지 않도록 할 수 있다.
@Entity @Table(name = "team") public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // Team에서 Member가 제거되면, 그 Member는 데이터베이스에서도 삭제됨. 고아 객체를 관리함. @OneToMany(mappedBy = "team", orphanRemoval = true) private List<Member> members = new ArrayList<>(); public void removeMember(Member member) { this.members.remove(member); member.setTeam(null); // 연관관계 해제 } }
'개발일지 > 스파르타코딩클럽' 카테고리의 다른 글
[2주차] 내일배움캠프 Spring Java - AI 활용 비즈니스 프로젝트 DAY1 - S.A 작성하기 (0) | 2025.02.12 |
---|---|
[2주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - MSA (0) | 2025.02.11 |
[1주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - JWT (1) | 2025.02.07 |
[1주차] 내일배움캠프 Spring 프로젝트 AWS - EC2, H2를 활용하여 배포하기 (1) | 2025.02.05 |
[1주차] 내일배움캠프 Spring Java 심화 부트캠프 3기 - IoC와 DI (1) | 2025.02.04 |
Comments