728x90

포스팅이 늦었다. 3주차 미션인 사다리도 끝나게 되었다.

로또에서보다 난이도가 많이 올라간 느낌이었다.

리뷰어분이 빡세게 그리고 꼼꼼하게 해주신 덕분에 나 자체도 굉장히 성장한것 같다❗

아래는 깃허브 PR 목록이다.

사다리 1주차

사다리 2주차

사다리 3주차

사다리 4주차

테스트

전체적으로 테스트코드를 고민하다가 한번 로직에 손을 대면 저절로 도메인 위주로 구현을 하게 되었다.

테스트를 항상 생각하면서 그리고 테스트를 실행함으로 인해 로직을 구현해 나가야 하는것이 조금 부족했던 챕터였다.

그래서 중간에 리뷰를 받다가 너무 로직이 답답해 보였다.

읽고있던 이펙티브 자바를 접목시켜서 조금 더 나은 로직으로 개선했다.

로직개선으로 문제를 겪었었는데, 그 문제가 바로 절차지향으로 개발했기 때문에 문제였다.

모든 로직을 한군데에 구현해놓으니까 분리하기도 쉽지않고 어떻게 돌아가는지 명확하게 알 수도 없는 그런 로직이 완성되어 있었다.

이걸 일급 컬렉션으로 포장해주고, 모든 엔티티를 작게 유지한다 라는 조건을 생각하면서

개발하게 되니까 확실히 알기도 쉬워졌고, 유지보수성이 좋게 되었다.

무엇보다 인터페이스 그리고 람다에 대해 공부를 많이 해야겠다고 생각했다.

프로그램이 뭘 하는지 어떻게 해야하는지 생각하는 Out-In방식이 아니라

In-Out방식으로 최소한의 객체에서부터 출발하는 생각을 지속적으로 해야한다.

접근방식

처음에 내가 이 사다리를 놓고 접근한 방식은 다리와 세로 기둥을 같이 넣어서 구현하려고 했던게 문제였다.

그러니까 사다리 라는 큰 틀만 놓고 일차원적으로 생각한 결과가 이렇게 된 것이다.

여기가 갈아 엎은 부분

그래서 결국 생각해낸 것은 이라는 객체가 결국 이동해서 결과를 내주는 것인데

점부터 시작해서 왼쪽 오른쪽을 판단하게끔 로직을 구현하니까 점점 조금씩 큰 컬렉션으로 나가지면서 그에 대한 테스트도 조금씩 늘릴 수가 있게 되었다.

요구사항

참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)
pobi,honux,crong,jk

실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)
꽝,5000,꽝,3000

최대 사다리 높이는 몇 개인가요?
5

사다리 결과

pobi  honux crong   jk
    |-----|     |-----|
    |     |-----|     |
    |-----|     |     |
    |     |-----|     |
    |-----|     |-----|
꽝    5000  꽝    3000

결과를 보고 싶은 사람은?
pobi

실행 결과
꽝

결과를 보고 싶은 사람은?
all

실행 결과
pobi : 꽝
honux : 3000
crong : 꽝
jk : 5000

조건

자바 8의 스트림과 람다를 적용해 프로그래밍한다.

  • 규칙
    • 모든 엔티티를 작게 유지한다.
    • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.

위 요구사항에 따라 4명의 사람을 위한 5개 높이 사다리를 만들 경우, 프로그램을 실행한 결과는 다음과 같다.

728x90

'Java' 카테고리의 다른 글

JVM  (0) 2022.08.06
Effective Java 4장 요약  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
728x90

테스트 코드

자동차 경주에 대한 라이브 피드백 시간인데
느낀점이 있어서 포스팅하게 되었다.

테스트 코드 비교할 경우

우리는 항상 getter, setter 메소드를 많이 써왔다.
그래서 나도 습관처럼 객체를 생성하고 비교를 할 경우에 아래와 같이 코드를 작성했었다.

@Test
void create() {
    Position actual = new Position(5);
    assertThat(actual.getPosition).isEqualTo(5);
}

이런식으로 get 메소드를 사용해서 값을 비교를 했는데
이 방식은 잘못되었다기 보다는 get을 사용하지 않고
객체와 객체를 비교하는 방법을 사용하는 것이 오히려 객체지향적 측면에서 좋을 것 같다.

그렇게 Position 클래스에 equals()hashCode() 를 오버라이드 해주고 객체끼리 비교하게끔 만들어준다.

public class Position {
    private Position position = new Position(0);

    public Position(int position) {
        if (position < 0) {
            throw new IllegalArgumentException("음수는 위치 값이 될 수 없음");
        }
        this.position = position;
    }
}
@Test
void create() {
    Position actual = new Position(5);
    assertThat(actual).isEqualTo(new Position(5));
}

이렇게 객체 두개가 같은지를 구현하면 테스트가 성공하게 된다.

이런식으로 어떤 객체를 생성했을때 객체끼리 비교하는 습관을 들이도록 해보자.

문자열과 원자값을 포장해서 쓰는게 객체지향에서 하기 굉장히 좋은 것이니까 지금부터라도 습관 들이자~😄

이제서야 이펙티브 자바 1장이 이해가 되는것 같다. 경험해봐야 이해가 잘되는 이 기분이 좋다.

일급 컬렉션

필드변수를 하나만 두고 사용하는 클래스이다.

일급 콜렉션을 사용하면 계속해서 객체에게 메세지를 보내서 get메소드 대신 객체에게 위임해서 데이터를 조작하게끔 만들어야 한다.

하나씩 객체들을 포장해서 관리하면 테스트 로직을 짜기에 되게 수월하게 작성할 수 있다.

객체에 메시지를 보내라 = 객체가 하게 만들어라 = 객체에게 위임해라

클래스 역할은 작게할수록, 그리고 클래스를 잘게 쪼갠다면? TDD는 쉬워진다

항상 이것들을 생각하면서 코드를 작성하도록 하자 👍텍스트

728x90

'Java' 카테고리의 다른 글

Effective Java 4장 요약  (0) 2022.08.06
TDD Clean Code with Java 12기 3주차  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
728x90

로또

2주차 미션은 로또 생성기였다.


Step1 - 문자열 덧셈 계산기


Step2 - 로또(자동)


Step3 - 로또(2등)


Step4 - 로또(수동)

프로그래밍 요구사항이 점점 추가되어 조금 더 제한적인 상황에서 조건문을 사용해야 한다.
주차가 늘어가면서 느끼는것이지만, 테스트 주도 개발을 하게 되니까 안하던 방법이라서 손에 익지는 않았다. 그런데 완성되는 테스트를 먼저 구현하다 보니까 오류가 나는 상황에 대해서 더 생각하고 코드를 구현할 수 있게 되는것 같다.

이 과정을 진행하면서 이펙티브 자바도 같이 읽고 있다. 정적 팩토리 메서드는 이제 꼭 쓰게 되는것 같다.😁 꼭 쓰는것은 또 아니라고 생각해야되는데 일단 무분별하게 생성자로 객체를 생성할 수는 없게 만들어 놨다.

클래스

클래스 부분에서 좀 많은 생각을 했었고 이번 과제에서는 if조건문을 추상 클래스와 추상 메소드를 활용해서 조건문을 처리한 로직이 있다.

아직 2주차 미션임에도 불구하고 예전 코드와 좀 많이 달라졌다는게 눈에 보인다.
단순 로직만 구현을 바꾸는 것도 좋겠지만 그 안의 복잡도도 고쳐가면서 코드를 구현해 나가야겠다.

강의를 정말 신청하길 잘했다는 생각이 들고 이 과정을 완주하는것이 목표니까 최선을 다 해보도록 해야겠다.

아래는 2주차 미션의 요구사항이다.

요구사항

기능 요구사항
로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.

로또 1장의 가격은 1000원이다.
구입금액을 입력해 주세요.
14000
14개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)

힌트

  • 로또 자동 생성은 Collections.shuffle() 메소드 활용한다.
  • Collections.sort() 메소드를 활용해 정렬 가능하다.
  • ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다.

프로그래밍 요구사항

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
  • 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
    • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
      else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
728x90

'Java' 카테고리의 다른 글

TDD Clean Code with Java 12기 3주차  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
Effective Java 1장  (0) 2022.08.05
728x90

객체 지향 쿼리 심화

한번에 여러 데이터를 수정할 수 있는 벌크 연산이 있다.

벌크 연산

엔티티를 수정하려면 영속성 컨텍스트 변경 감지 기능이나 병합을 사용하고, 삭제하려면 em.remove() 를 사용한다. 데이터를 하나씩 처리하기엔 너무 오래걸려서 여러개를 동시에 처리할 수 있는 벌크연산이 있다.

벌크 연산은 executeUpdate() 를 사용한다. 이 메소드는 벌크 연산으로 영향을 받은 엔티티 건수를 반환한다.

주의사항

벌크 연산은 영속성 컨텍스트를 통하지 않고 바로 DB에 직접 쿼리를 날린다. 그래서 영속성 컨텍스트에 있는 엔티티와 DB에 있는 테이블의 칼럼 값이 다를 수 있는데, 그래서 이 부분을 주의하여야 한다.

  • em.refresh 사용
    • 엔티티를 사용해야 할 경우엔 DB에서 다시 조회한다.
  • 벌크 연산 먼저 실행
    • 벌크연산을 가장 먼저 실행하여 실행한 후에 조회쿼리를 하면 변경된 것으로 조회가 된다.
  • 벌크연산 수행 후 영속성 컨텍스트 초기화
    • 수행 직후 컨텍스트를 초기화하여 엔티티를 제거했다가 벌크연산이 적용된 DB에서 조회하는 방법

영속성 컨텍스트와 JPQL

쿼리 후 영속 상태인것과 아닌 것

select m from Member m //엔티티 조회 (영속성 O)
select o.address from Order o //임베디드 타입 조회 (영속성 X)
select m.id, m.name from Member m // 필드 조회 (영속성 X)

엔티티 전체를 조회해야만이 영속성 컨텍스트가 관리한다❗️

JPQL로 조회한 엔티티와 영속성 컨텍스트

앞선 포스팅에서 JPQL로 DB에서 조회한 엔티티가 영속성 컨텍스트에 있다면 JPQL로 DB에서 조회한 값은 버리고 영속성 컨텍스트에 있던것을 꺼내온다고 했다.

덮어쓰거나 하게 된다면 컨텍스트안에서 수정 중이었던 데이터가 사라질 수 있어 위험하다.
그래서 영속성 컨텍스트는 엔티티의 동일성을 보장하기 때문에 em.find로 조회를 하던, JPQL을 사용하던 영속성 컨텍스트가 같으면 동일한 엔티티를 반환해준다.

JPQL의 특징

  • JPQL은 항상 DB를 조회한다.
  • JPQL로 조회한 엔티티는 영속 상태이다.
  • 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티를 반환한다.

그래서 영속성 컨텍스트의 1차 캐시를 되도록이면 관리하여 많이 이용하는 것이 DB에 부하를 적게 주는 것이고 그게 바람직한 사용방법인것 같다는 나의 견해? 🤔

JPQL과 플러시 모드

flush는 영속성 컨텍스트의 변경 내역을 DB에 동기화 해주는 것이다.
그래서 JPA는 flush가 발생했을 때 쓰기지연 SQL 저장소 라고 했던 저장소에 있던 쿼리들을 쭉 만들어 DB에 반영해준다. flush를 호출하려면 em.flush()를 하거나 flush 모드에 따라 커밋 직전이나 쿼리 실행 직전에 자동 호출된다.

쿼리와 플러시 모드

JPQL은 영속성 컨텍스트 데이터를 고려하지 않고 DB에서 조회하기 때문에 사용할때는 반드시 영속성 컨텍스트의 변경사항을 flush해주어야 한다. 그렇지 않으면 데이터가 섞일 수 있다.

@Test
@DisplayName("쿼리와 플러시 모드")
void queryAndFlushTest() {
    em.setFlushMode(FlushModeType.COMMIT);
    Item item = em.find(Item.class, 1L);
    item.setPrice(2000);

    Object item2 = em.createQuery("select i from Item i where i.price = 2000").getSingleResult();
    System.out.println(item2.toString());
}

flush모드를 commit시에만 플러시로 설정해놓으면

이러한 select쿼리문 후에 에러를 발생한다.


맞는 엔티티를 찾을 수 없다고 나오게 된다.

정리

  • JPQL은 SQL을 추상화하여 특정 DB에 의존하지 않는다.
  • QueryDSL은 JPQL을 만드는 빌더 역할만 하므로 JPQL을 잘 알아야 함!
  • QueryDSL을 사용하면 동적 쿼리를 생성하기가 편리하다.
  • QueryDSL은 JPA가 공식 지원하는 것은 아니지만 직관적이고 편리하다.
  • JPA도 네이티브 쿼리를 지원하지만, 종속적인 SQL을 사용하게 되면 특정 DB에만 한정적인게 된다.
    • JPQL을 최대한 활용 해보고 안되면 그 때 네이티브 SQL을 사용하자😊
  • JPQL은 대용량 수정, 삭제를 할 수 있는 벌크 연산을 지원한다.
728x90

'Java' 카테고리의 다른 글

TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
Effective Java 1장  (0) 2022.08.05
MockMvc  (0) 2022.08.04
728x90

네이티브 SQL

JPQL은 표준 SQL이 지원하는 대부분의 문법과 SQL함수를 지원한다.
근데 특정 DB의 방언과 같은 종속적 기능은 지원하지 않는다.

  • 특정 DB만 지원하는 함수, 문법, SQL 쿼리 힌트
  • 인라인 뷰(from절 서브쿼리), UNION, INTERSECT
  • 스토어드 프로시저

종속적인 기능을 지원하는 방법은

  • 특정 DB만 사용하는 함수
    • 특정 JPQL에서 네이티브 SQL 함수를 호출할 수 있다.
    • Hibernate는 DB 방언에 각 DB에 종속적 함수를 정의했다. 그리고 직접 호출할 함수를 정의하기도 가능하다.
  • 특정 DB만 지원하는 힌트
    • Hibernate를 포함한 몇몇 JPA구현체가 지원한다.
  • 인라인 뷰(from절 서브쿼리), UNION, INTERSECT
    • JPA구현체들이 지원한다. Hibernate는 지원 ❌
  • 스토어드 프로시저
    • JPQL에서 호출 가능
  • 특정 DB만 지원하는 문법
    • 너무 유니크한 쿼리 문법은 지원하지 않는데, 이때 네이티브 SQL사용. 근데 이걸 사용할까...?

네이티브 SQL을 사용하면 엔티티를 조회할 수 있고, JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.

네이티브 SQL 사용

네이티브 쿼리 API는 3가지가 있다.

  • 결과 타입을 정의
  • 결과 타입을 정의할수 없을 때
  • 결과 매핑 사용

엔티티 조회

    @Test
    @DisplayName("네이티브 SQL 엔티티 조회")
    void nativeQueryTest() {
        String sql = "select id, name, price from item where price > ?";
        Query nativeQuery = em.createNativeQuery(sql, Item.class).setParameter(1, 100);

        List<Item> items = nativeQuery.getResultList();
    }

jdbc 사용할때와 똑같은 느낌이 든다.
근데 가장 중요한점은
SQL만 직접 사용할 뿐, JPQL을 사용할 때와 같다. 조회한 엔티티도 영속성 컨텍스트에서 관리 된다.

값 조회

값으로 조회하려면 엔티티 조회처럼 class를 같이 넣어주는게 아니라
em.createNativeQuery(sql) 를 사용하면 된다.
대신 이때 nativeQuery.getResultList() 는 Object 배열을 반환하므로
List<Object[]> 로 반환을 받아야한다.
더욱 더 JDBC같이 생겼다.

결과 매핑 사용

결과 매핑을 사용하면 엔티티 자체에 너무 많은 어노테이션 설정을 해야되므로 보편적으로 사용하지 않을 것 같다는 나의 생각이 들어있다.
그래도 정리를 해보도록 하겠다.

String sql = "select M.ID, AGE, NAME, TEAM_ID, I.ORDER_COUNT FROM MEMBER M " +
"LEFT JOIN (SELECT IM.ID, COUNT(*) AS ORDER_COUNT FROM ORDERS O, MEMBER IM " +
"WHERE O.MEMBER_ID = IM.ID) I ON M.ID = I.ID";

Query nativeQuery = em.createNativeQuery(sql, "memberWithOrderCount");
List<Object[]> members = nativeQuery.getResultList();

아래는 매핑 정의 코드이다.

@Entity
@SqlResultSetMapping(name = "memberWithOrderCount",
    entities = {@EntityResult(entityClass = Member.class) },
    columns = {@ColumnResult(name = "ORDER_COUNT")}
}
public class Member {...}

id, age, name, team_idMember 엔티티로 매핑을 시키고 order_count 는 단순 칼럼으로 매핑했다.
이렇게 여러 컬럼들을 매핑해서 추출할 수 있다.

@NamedNativeQuery

Named 네이티브 SQL을 사용하여 정적 SQL도 작성이 가능하다.
엔티티 클래스에

@NamedNativeQuery(
    name = "Member.memberSQL",
    query = "select 조회 쿼리문",
    resultClass = Member.class
)

로 등록해주고 사용하고자 하는 곳에서

TypedQuery<Member> nativeQuery = em.createNamedQuery("@NamedNativeQuery의 name", Member.class)
//파라미터가 있을 때
.setParameter();

네이티브 SQL은 휴먼에러를 발생할 확률이 QueryDSL보다 굉장히 높을 것으로 예상한다.
그래서 웬만하면 QueryDSL로 하지만 한방쿼리가 적절하게 필요할때만 사용하도록 해야할듯? 싶다. 😅
아직 실무에서 제대로 사용하지 않아서 이런 실무에서의 타협점은 점차 늘려가야 될것으로 보인다.

728x90

'JPA' 카테고리의 다른 글

JPA metamodel must not be empty!  (0) 2022.08.06
[JPA] findAll, findById 차이  (0) 2022.08.06
[JPA] 객체 지향 쿼리 언어 - Querydsl 2  (0) 2022.08.05
[JPA] 객체 지향 쿼리 언어 - Querydsl  (0) 2022.08.05
728x90

이번 포스팅에서는 조인에 대해 알아볼 것이다.

조인

조인은 innerJoin(join), leftJoin, rightJoin, fullJoin을 사용할 수 있고 추가로 JPQL의 on과 성능 최적화를 위한 fetchJoin을 사용할 수 있다.

jpaQueryFactory.selectFrom(item).join(조인할 쿼리클래스).fetch();

연관관계가 있으면 그냥 join만 사용해도 되지만 지금은 on이 추가되어 on() 절로 연관관계 없이 조인도 가능하다.

jpaQueryFactory.selectFrom(item).join(chair).on(chair.name.eq(item.name)).fetch();


from절에 여러 조건을 사용해서 세타조인도 가능하다.

서브 쿼리

서브 쿼리는 예전 버전에서는 JPASubQuery를 사용했지만 업데이트가 되어 현재 버전에서는 JPAExpressions 를 사용하여 서브쿼리를 작성한다.

@Test
@DisplayName("subQuery 테스트")
void subQueryTest() {
    jpaQueryFactory.selectFrom(item)
                   .where(item.name.eq(String.valueOf(JPAExpressions.selectFrom(chair).where(chair.name.eq("item3")))))
                   .fetch();
}

전 직장에서 근무하였을때 서브쿼리로 너무 많은것들을 처리했던 레거시 쿼리가 상당히 많아서
서브쿼리를 사용할때는 항상 생각해보고 쿼리를 짜야 좋을것 같다.
이게 다른 조회를 여러번 하는것이나 Join을 사용할때 보다 느린것 같다. 자세하게 속도를 비교해보면서 포스팅을 한번 했었어야 했는데 이게 안되서 개인적으로는 아쉬운 부분이다.😅

프로젝션

프로젝션 대상이 하나라면 해당하는 타입으로 결과를 받아야 한다.

List<String> list = jpaQueryFactory.select(item.name).from(item).orderBy(item.name.desc()).fetch();

그렇지만 대상이 여러개라면?
반환값이 Tuple인 객체를 반환하기 때문에 이렇게 사용해야 한다.

List<Tuple> list = jpaQueryFactory.select(item.name, item.price).from(item).orderBy(item.name.desc()).fetch();

가만보니까 이렇게 특정 칼럼만 받아서 쓰는 서비스 로직이 따로 있을거란 생각이 든다.
그때 그냥 DTO를 써주면 어떨까?

빈 생성

  • 프로퍼티 접근(Setter)
  • 필드 접근
  • 생성자 사용
    객체를 생성하는 방법 3가지 이다. 이것을 통해 그리고 Projections를 사용하여 dto객체를 생성해주면 될듯 하다.

주의❗️ 빈 생성자를 무조건적으로 넣어줘야 한다.

기본값이 빈 생성자라서 명시적으로 넣어주지 않았는데
다음과 같은 에러가 발생한다. 😱


protected 생성자 넣었을때 또 protected 에러를 발생시킨다.

그래서 lombok의 @NoArgsConstructor를 넣어주었더니 정상 실행이 된다.
public 접근제어자여야 한다.

@Test
@DisplayName("projection dto")
void dtoTest() {
        List<ItemDto> result = jpaQueryFactory.select(
        Projections.bean(ItemDto.class,item.name.as("name"), item.price))
        .from(item).fetch();
}

프로퍼티 접근 방식인데 여기선 Projections.bean을 사용한다. 이것이 setter 메소드를 사용해서 값을 채우는 방식이다.

필드 직접 접근 방식은 Projections.fields()를 사용하면 된다.

생성자 접근 방식은 Projections.constructor()를 사용하는데 지정한 프로젝션과 파라미터 순서가 같은 생성자여야 잘 동작한다.

수정, 삭제 배치 쿼리

이부분은 그냥 영속성 컨텍스트에서 삭제, 수정 해주는것이 더 편리할것 같아서 따로 정리하지는 않고 읽는 단계로 넘어가겠다.

동적 쿼리

여기가 내가 많이 생각했고 어떻게 해야 조건문으로 쿼리를 처리할지에 대한 고민을 했던게 이 부분인것 같다.

@Test
@DisplayName("동적 쿼리")
void 동적쿼리_Test() {
    SearchParam param = new SearchParam();
    param.setName(null);
    param.setPrice(200);

    BooleanBuilder booleanBuilder = new BooleanBuilder();
    if (!ObjectUtils.isEmpty(param.getName())) {
        System.out.println(param.getName());
        booleanBuilder.and(item.name.eq(param.getName()));
    }

    if (!ObjectUtils.isEmpty(param.getPrice())) {
        System.out.println(param.getPrice());
        booleanBuilder.and(item.price.eq(param.getPrice()));
    }

    List<Item> result = jpaQueryFactory.selectFrom(item).where(booleanBuilder).fetch();
}

BooleanBuilder를 사용하여 조건에따른 조건을 할당해서 조회해줄수가 있다.
이것을 할줄 몰라서 Mybatis를 고집했던 이유도 없지않아 있는것 같다.

JPQL이 기본인 CRUD보다 훨씬 중요하고 이것을 알아야 원하는대로 select 쿼리를 날려줄 수가 있다.
그러면서 동시에 이 QueryDSL을 쿼리로 만드는게 복잡하고 어렵다고 생각해서 mybatis에서 또는 그냥 @Query에서 못벗어난? 것 같다. 😭

여기에 리팩토링을 추가한다면 조건문 하나당 로직을 메서드로 분리하여 수행해주면 편할것이다.

728x90

'JPA' 카테고리의 다른 글

[JPA] findAll, findById 차이  (0) 2022.08.06
[JPA] 객체 지향 쿼리 언어 - Native SQL  (0) 2022.08.05
[JPA] 객체 지향 쿼리 언어 - Querydsl  (0) 2022.08.05
[JPA] 객체 지향 쿼리 언어 - 3  (0) 2022.08.05

+ Recent posts