diff --git a/keyword/chapter05/keyword.md b/keyword/chapter05/keyword.md new file mode 100644 index 0000000..7e47b84 --- /dev/null +++ b/keyword/chapter05/keyword.md @@ -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 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 members = new ArrayList<>(); +} + +``` +```java +em.createQuery("select t from Team t", Team.class).getResultList(); // 패치 전략 무시 +``` + + +- 지연 로딩에서 N + 1 예제 +```java +// @OneToMany(fetch = FetchType.Lazy)로 변경 +List 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 \ No newline at end of file diff --git a/keyword/chapter06/keyword.md b/keyword/chapter06/keyword.md new file mode 100644 index 0000000..12ca0e5 --- /dev/null +++ b/keyword/chapter06/keyword.md @@ -0,0 +1,227 @@ +## 지연로딩과 즉시로딩의 차이 +- 지연로딩 : 엔티티 조회 시, 연관된 엔티티는 실제 사용하는 시점에 조회하는 방식 +- 즉시로딩 : 엔티티 조회 시, 연관된 엔티티를 함께 조회하는 방식 + + +- 즉시로딩 예시 +```java +public class Member { + @ManyToOne(fetch = FetchType.EAGER) + private Team team; +} + +Member member = em.find(Member.class, 1L); // team도 join으로 함께 조회한다. +``` +- 즉시로딩 장점 : 데이터를 한 번에 로딩하기 때문에 성능이 향상된다. +- 즉시로딩 단점 : 메모리 낭비, 필요하지 않은 데이터도 가져온다. + + +- 지연로딩 예시 +```java +public class Member { + @ManyToOne(fetch = FetchType.Lazy) + private Team team; +} + +Member member = em.find(Member.class, 1L); +team1.getTeam().getName(); // 실제 target이 사용되는 시점에 조회 쿼리가 나간다. +``` +- 지연로딩 장점 : 필요한 데이터만 로딩하기 때문에 메모리 사용량이 줄어든다. +- 지연로딩 단점 : 사용하는 연관 객체가 많다면 성능 저하가 발생할 수 있다. + + +## Fetch Join +- JPQL에서 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능 + + +- 사용 예시 +```java +// members가 LAZY여도 로딩하여 성능이 향상된다. +@Query("SELECT DISTINCT t FROM Team t JOIN FETCH t.members WHERE t.id = :id") +Team findTeamByMemberId(@Param("id") Long id); +``` + + +- 일반 조인과 패치조인의 차이 + - 일반 조인은 연관 엔티티에 조인을 걸어도 SELECT 되지 않는다. + - 패치 조인은 조인이 걸린 연관 엔티티도 함께 SELECT 하여 객체 그래프를 로드한다. +```java +@Query("SELECT distinct t FROM Team t JOIN t.members") // 팀 관련 컬럼만 가져온다. + +@Query("SELECT distinct t FROM Team t JOIN FETCH t.members") // 팀과 멤버 컬럼을 가져와 영속화한다. +``` + + + +- 단점 + - 조인이다 보니, 1:N에서 중복이 발생하고 페이징 API 사용이 불가능하다. + - 두 개 이상의 컬렉션을 패치 조인 X + - 쿼리문을 직접 작성해야 한다. + + +## @EntityGraph +- 연관된 엔티티를 즉시로딩하는 어노테이션 +- 패치 조인의 어노테이션 버전이라고 생각하면 된다. + + +- 사용 예시 +```java +// 공통 메서드 + @EntityGraph +@Override +@EntityGraph(attributePaths = {"members"}) +List findAll(); + +// 쿼리 메서드 + @EntityGraph +@EntityGraph(attributePaths = {"members"}) +@Query("SELECT t FROM Team t WHERE t.name = :name") +List findByName(@Param("name") String name); +``` + + +- 패치 조인과의 차이점 + - 패치조인은 inner join, @EntityGraph는 left outer join으로 동작한다. + - attributePaths에 필드를 여러 개 지정할 수 있다. + + +## JPQL +- JPA에서 제공하는 객체 지향 쿼리 언어 +- 테이블이 아닌 엔티티 객체를 대상으로 쿼리를 작성할 수 있다. + + +- 특징 + - 엔티티의 이름과 필드는 대소문자를 구분한다. + - 엔티티의 별칭은 필수적으로 명시해야한다.(as 생략) + + +- JPQL 예시 +```java +// Member Entity 대상 select +@Query("SELECT m FROM Member m") + +// 객체의 필드에 접근하듯이 사용 +@Query("SELECT m FROM Member m WHERE m.name = :name") + +// 이름 기준 파라미터 바인딩 +String jpql = "select m from Member m where m.name = :name"; +TypedQuery query = em.createQuery(jpql, Book.class); +query.setParameter("name", param); + +// DTO 조회 가능. 단 패키지명을 모두 명시해야 한다. +String jpql = "select new com.example.MemberDto(m.name, m.age) from Member m"; +TypedQuery query = em.createQuery(jpql, MemberDto.class); +``` + + +- JPQL vs SQL + - 조회 대상 : 엔티티 / 테이블 + - 필드 : 엔티티의 필드 / 테이블의 컬럼 + - 실행 과정 : JPA가 JPQL -> SQL 변환 / SQL 그대로 실행 + + +## QueryDSL +- SQL/JPQL 쿼리를 안전하게 생성 및 관리해주는 프레임워크이다. + + +- 기존 JPQL의 문제점 + - 쿼리를 문자열로 입력하여 관리하는데 어려움이 있다. + - 실제로 쿼리를 실행하기 전까지 오류를 확인하기 어렵다. + - 동적 쿼리 작성이 어렵다. + + +- QueryDSL의 장점 + - 문자가 아닌 코드로 쿼리를 작성하여 컴파일 시점에 문법 오류를 확인한다. + - 복잡한 쿼리와 동적 쿼리 작성이 편리해진다. + + +- JPQL을 QueryDSL로 리팩토링한 예시 +```java +String jpql = "select * from Member m join Point p on p.member_id = m.id" +List result = em.createQuery(jpql, Member.class).getResultList(); +``` +```java +return jpaQueryFactory + .from(member) + .join(member.point, point) + .fetch(); +``` + + +- 동적 쿼리 예시 +```java +private final QMember member; + +public List findMembers(String name, Integer minAge) { + BooleanBuilder builder = new BooleanBuilder(); + + // BooleanExpression으로 동적 조건 추가 + builder.and(nameEq(name)); + builder.and(ageGoe(minAge)); + + return queryFactory + .selectFrom(member) + .where(builder) + .fetch(); +} + +// 도시 조건 +private BooleanExpression cityEq(String city) { + return city != null ? member.city.eq(city) : null; +} + +// 최소 나이 조건 +private BooleanExpression ageGoe(Integer minAge) { + return minAge != null ? member.age.goe(minAge) : null; +} +``` + + +## N+1 문제 해결 방법 +- BatchSize 사용 + - WHERE 절이 동일한 여러 개의 SELECT 쿼리를 하나의 IN 쿼리로 만들어준다. + - size로 분할되는 만큼 SELECT 쿼리를 따로 날린다. + + +- BatchSize 예시 +```java +@Entity +public class Parent { + @BatchSize(size = 100) + @OneToMany(mappedBy = "parent") + private List children = new ArrayList<>(); +} +``` +```mysql-sql +// 적용 전 +SELECT * FROM child WHERE child.parent_id = 1 +SELECT * FROM child WHERE child.parent_id = 2 +... +SELECT * FROM child WHERE child.parent_id = 100 + +// 적용 후 +SELECT * FROM child WHERE child.parent IN (1, 2, ... ,100) +``` + + +- Fetch Join 사용 + - 단일 SQL 쿼리로 연관된 엔티티를 함께 조회 + - 페이징 X, 모든 데이터를 메모리에 올려 JPA가 따로 페이징을 한다. + - 둘 이상의 컬렉션을 한꺼번에 패치 조인 X + + +- Fetch Join 예시 +```java +@Query("SELECT t FROM Team t JOIN FETCH t.members") +List findAllWithMembers(); +``` + + +- @EntityGraph 사용 + - fetch join과 유사한 기능을 한다. + - 어노테이션으로 적용할 수 있어 쿼리를 직접 작성하지 않아도 된다. + + +- @EntityGraph 예시 +```java +@EntityGraph(attributePaths = "members") +Page findByName(String name, Pageable pageable); +``` \ No newline at end of file diff --git a/mission/chapter05/DBConnect.png b/mission/chapter05/DBConnect.png new file mode 100644 index 0000000..02d9a88 Binary files /dev/null and b/mission/chapter05/DBConnect.png differ diff --git a/mission/chapter05/Table.png b/mission/chapter05/Table.png new file mode 100644 index 0000000..db467a0 Binary files /dev/null and b/mission/chapter05/Table.png differ diff --git a/mission/chapter05/mission.md b/mission/chapter05/mission.md new file mode 100644 index 0000000..b779952 --- /dev/null +++ b/mission/chapter05/mission.md @@ -0,0 +1,7 @@ +## 미션 + +### 1. 데이터 베이스 연결 +- ![DBConnect.png](DBConnect.png) + +### 2. 테이블 생성 +- ![Table.png](Table.png) \ No newline at end of file