-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[5, 6주차/피우] 워크북 제출합니다.
- Loading branch information
Showing
5 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
## Domain | ||
- 애플리케이션에서 해결하고자 하는 문제의 영역 | ||
|
||
|
||
- 예시) | ||
- 쇼핑몰 애플리케이션 : 상품, 주문, 결제, 배송 등으로 문제 영역을 나눌 수 있다. | ||
- 은행 애플리케이션 : 에금/출금, 대출, 계좌관리 등으로 문제 영역을 나눌 수 있다. | ||
|
||
|
||
- 도메인 모델 | ||
- 도메인을 이해하기 위한 형태로 구조화한 것 | ||
- 구성요소, 규칙, 기능을 정의 | ||
- 도메인 자체를 이해하는 것이 목적이므로 모델링 방법은 중요하지 않다. | ||
- 그러나 객체지향 프로그래밍 환경에서는 클래스 다이어그램과 같은 UML 표기법을 사용하는 것이 유용하다. | ||
|
||
|
||
- 도메인 모델의 분류 | ||
- 엔티티 : 고유한 식별자를 가지는 객체, 식별자만으로 동일성 판단 | ||
- 값 객체 : 고유한 식별자가 없는 객체, 모든 속성 값으로 동일성 판단 | ||
|
||
|
||
- 하나의 모델을 각각 엔티티와 값 객체로 표현한 예제 | ||
```java | ||
// 엔티티로 표현한 User | ||
class User { | ||
|
||
private final String userId; // 고유 식별자로 사용 | ||
private String name; | ||
private int age; | ||
|
||
// 생성자 | ||
public User(String userId, String name, int age) { | ||
this.userId = userId; | ||
this.name = name; | ||
this.age = age; | ||
} | ||
|
||
// Getter 및 Setter ... | ||
|
||
// 동일성은 식별자(userId)를 기준으로 판단 | ||
@Override | ||
public boolean equals(Object o) { | ||
// ... | ||
return userId.equals(user.userId); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(userId); | ||
} | ||
} | ||
``` | ||
```java | ||
// 값 객체로 표현한 User | ||
final class User { | ||
|
||
private final String name; | ||
private final int age; | ||
|
||
// 생성자 | ||
public User(String name, int age) { | ||
this.name = name; | ||
this.age = age; | ||
} | ||
|
||
// Getter 메서드만 ... | ||
// 불변 객체를 보장되어야 하므로 Setter X | ||
|
||
// 동일성 비교는 모든 속성 값을 기준으로 한다. | ||
@Override | ||
public boolean equals(Object o) { | ||
// ... | ||
return (age == user.age && name.equals(user.name)); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(name, age); | ||
} | ||
} | ||
``` | ||
|
||
- 엔티티와 값 객체를 선택하는 기준 | ||
- 고유한 식별자가 필요하다. -> 엔티티 (User, Order) | ||
- 상태가 바뀌어도 동일한 객체로 간주한다. -> 엔티티 (User의 개명) | ||
- 객체의 상태가 변하지 않는다. -> 값 객체 (Address) | ||
|
||
|
||
## 양방향 매핑 | ||
- 두 개체가 양방향 연관관계를 갖도록 설정하는 것 | ||
|
||
|
||
- 양방향 연관관계 | ||
- 두 개체가 서로를 참조하고 있는 관계 | ||
|
||
|
||
- 관계형 데이터베이스의 양방향 매핑 | ||
- 한 테이블이 다른 테이블에 대한 기본 키를 외래 키로 갖는다. | ||
- JOIN을 통해 서로 다른 테이블 간의 탐색이 가능해진다. | ||
|
||
|
||
- 객체 지향 프로그래밍의 양방향 매핑 | ||
- 서로 다른 두 객체가 각각을 인스턴스로 갖는다. | ||
- 즉 2번의 객체 참조로 양방향을 설정할 수 있다. | ||
|
||
|
||
- 패러다임의 불일치 | ||
- 관계형 데이터베이스에서는 하나의 테이블이 외래키를 관리한다. | ||
- 객체 지향 패러다임에서는 두 번의 참조로 양방향 관계를 구현한다. | ||
- 따라서 데이터의 무결성, 관계의 명확한 관리를 위해 한 엔티티만 외래키를 관리하도록 한다. | ||
|
||
|
||
- '외래키를 관리한다'의 의미 | ||
- DB 상에서 FK를 갖는다. | ||
- 실제 DB에 대한 저장, 수정, 삭제 권한을 갖는다. | ||
|
||
|
||
- JPA 양방향 매핑 예시 | ||
```java | ||
@Entity | ||
public class Member { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
private String username; | ||
|
||
@ManyToOne | ||
@JoinColumn(name = "team_id") // 외래 키 정의 | ||
private Team team; | ||
|
||
// 생성자, Getter ... | ||
|
||
public void setTeam(Team team) { | ||
this.team = team; | ||
} | ||
} | ||
``` | ||
```java | ||
@Entity | ||
public class Team { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
private String name; | ||
|
||
@OneToMany(mappedBy = "team") | ||
private List<Member> members = new ArrayList<>(); | ||
|
||
// 생성자, Getter ... | ||
|
||
// 연관관계 편의 메서드는 자주 사용하는 엔티티에 위치하도록 한다. | ||
public void addMember(Member member) { | ||
members.add(member); | ||
member.setTeam(this); | ||
} | ||
} | ||
``` | ||
|
||
|
||
## N + 1 문제 | ||
- ORM 기술에서 엔티티 조회 시, 연관관계도 함께 조회하여 추가적인 쿼리가 나가는 문제 | ||
|
||
|
||
- 즉시로딩에서 N + 1 예제 | ||
```java | ||
@Entity | ||
public class Team { | ||
@Id | ||
@GeneratedValue | ||
private long id; | ||
private String name; | ||
|
||
@OneToMany(fetch = FetchType.EAGER) | ||
private List<Member> members = new ArrayList<>(); | ||
} | ||
|
||
``` | ||
```java | ||
em.createQuery("select t from Team t", Team.class).getResultList(); // 패치 전략 무시 | ||
``` | ||
|
||
|
||
- 지연 로딩에서 N + 1 예제 | ||
```java | ||
// @OneToMany(fetch = FetchType.Lazy)로 변경 | ||
List<Team> teams = teamRepository.findAll(); | ||
|
||
teams.stream().forEach(team -> { | ||
team.getMembers().size(); // 실제 사용 시점에 SELECT 쿼리가 나간다. | ||
}); | ||
``` | ||
|
||
|
||
- 발생 원인 | ||
- 즉시 로딩에서의 원인 : JPQL은 글로벌 패치 전략을 무시한 채 JPQL 그대로 조회하기 때문이다. | ||
- 지연 로딩에서의 원인 : 실제 엔티티가 사용되는 시점까지 조회를 미루기 때문이다. | ||
|
||
|
||
- 해결 방법 | ||
- 조인 패치 : @Query("select t from Team t join fetch t.members") - 1번의 쿼리로 객체 그래프 조회 | ||
- 엔티티 그래프 : @EntityGraph(attributePaths = {"members"}) - members 필드 Eager 조회 | ||
- 배치 사이즈 : @BatchSize(size = 10) - IN 절 사용 | ||
|
||
|
||
- 각 연관관계의 default 속성 | ||
- @ManyToOne : EAGER | ||
- @OneToOne : EAGER | ||
- @ManyToMany : LAZY | ||
- @OneToMany : LAZY |
Oops, something went wrong.