중급 문법(2)

JIWOO YUN·2024년 3월 22일
0

Querydsl

목록 보기
4/7

이전에 Proejction.constructor의 경우

  • 생성자와 맞게 처리 하지 않을 경우에도 컴파일 과정에서는 오류를 찾을 수없음
  • 실행과정에서 오류가 발생하기 때문에 오류를 찾기 힘들다.

QueryProjection

생성자 + QueryProjection

  • 생성자에 @QueryProjection을 붙여주고
    • querydsl gradle 부분을 다시 build를 해줘서 QMemberDto를 생성해준다.
  • Q타입이 만들어져 있기 때문에 값이 다를 경우 컴파일 과정에서 오류가 난다.
  • 어노테이션 하나로 적용 가능, 기술 변경이 필요하면 어노테이션 수정 또는 삭제를 통해 조절 가능.

단점

  • DTO 가 QueryDsl 에 의존해야 한다.
    • Dto는 단순히 전달용으로만 사용해야 하는데 여기에 Querydsl에 의존되면서 순수하지 않게 된다.
이전에 만들어둔 MemberDto에 추가
@Data
@NoArgsConstructor
public class MemberDto {

    private String username;
    private int age;

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}
테스트 케이스에서 적용시
@Test
public void QueryProjection(){

    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();
    for (MemberDto memberDto : result) {

        System.out.println("memberDto = " + memberDto);
    }

}
  • Dto를 그대로 적용이 가능하다.

동적 쿼리 - BooleanBuilder

동적 쿼리를 해결하는 방식 두가지가 존재한다.

  • BooleanBuilder
  • Where 다중 파라미터 사용

그중 먼저 BooleanBuilder 에 대해서 알아보자.

문서의 설명으로는

BooleanBuilder (Querydsl 4.0.7 API)

BooleanBuilder is a cascading builder for Predicate expressions. BooleanBuilder is a mutable Expression implementation.
  • Predicate를 구현하는 구현체

    • Predicated는 Where 절의 파라미터 타입
  • 쿼리의 조건 설정인 Where 뒤에 조건을 생성해주는 것

BooleanBuilder 사용 코드

@Test
public void dynamicQuery_BooleanBuilder() {
    String usernameParam = "member1";
    Integer ageParam = 10;


    List<Member> result = searchMember(usernameParam, ageParam);

    assertThat(result.size()).isEqualTo(1);

}

private List<Member> searchMember(String usernameCond, Integer ageCond) {
    BooleanBuilder builder = new BooleanBuilder();

    //if 문으로 이 조건을 만족하면 and문을 통해서 조건 추가
    if(usernameCond != null){
        builder.and(member.username.eq(usernameCond));
    }

    if(ageCond != null){
        builder.and(member.age.eq(ageCond));
    }

    return queryFactory
            .selectFrom(member)
            .where(builder)
            .fetch();

}
  • where 문에 추가 되는 builder에 if 문에 조건이 만족 할 경우 builder에 and 조건이 추가된다.
    • 만약 usernameCond 가 null 이 아닌 경우 and 문으로 member에 username이 usernameCond와 같은지 체크하는 조건문이 추가됨.

단점

  • where 절문을 통째로 보기힘들다.
    • 조건이 많을 수록 로직을 따라가면서 신경 써줘야 쿼리문을 이해할 수 있음.

where 다중 파라미터 사용

실무에서 사용하면 좋은 방법이다.

    @Test
    public void dynamicQuery_WhereParam(){
        String usernameParam = "member1";
        Integer ageParam = 10;


        List<Member> result = searchMember2(usernameParam, ageParam);

        assertThat(result.size()).isEqualTo(1);

    }

    private List<Member> searchMember2(String usernameCond, Integer ageCond) {


        return queryFactory
                .selectFrom(member)
                .where(usernameEq(usernameCond),ageEq(ageCond))
                .fetch();
    }

    private Predicate usernameEq(String usernameCond) {

        return usernameCond != null ? member.username.eq(usernameCond) : null;
    }

    private Predicate ageEq(Integer ageCond) {

        return ageCond != null ? member.age.eq(ageCond) : null;
    }
  • where 조건에 null 은 무시된다.

  • 메서드를 다른 쿼리에서도 재활용 가능.

    • 조합이 가능해진다. -> 조합을 하기 위해서는 BooleanExpression을 사용해야한다.

      • usernameEq와 ageEq를 조합해서 allEq 를 만들어서 사용하는 것도 가능하다.
    • private BooleanExpression allEq(String usernameCond, Integer ageCond) {
       return usernameEq(usernameCond).and(ageEq(ageCond));
      }
      • 대신 null 체크는 주의 해야한다.
    • BooleanExpression은 querydsl에서 제공하는 기능으로 쿼리를 동적으로 생성하기 위해서 사용된다.

    • 들어가서 보니 implement로 Predicate를 하고 있다.

    • BooleanExpression (Querydsl 4.1.3 API)

  • booleanBuilder보다 가독성 좋음.

수정, 삭제 벌크 연산

  • executeUpdate() 메서드를 통해서 벌크 연산을 수행

    • 사용되는 이유
      • 수십만개의 데이터를 한꺼번에 처리하지않고 건마다 처리하게 되면 수십만개의 쿼리가 날라가서 처리하게된다.
  • 중요점

    • 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 한다는 점을 주의해야함.
      • 영속성 컨텍스트와 DB 간에 데이터 차이가 발생하게 되니 매우 주의 해야한다.
      • 해결 방법으로는 3가지 정도존재
        1. em.refresh() 사용
        2. 벌크 연산을 먼저 수행 하고 조회하기.
          1. 가장 실용적인 해결책
        3. 벌크 연산 수행 후 영속성 컨텍스트를 초기화.
          1. 영속성 컨텍스트에 남아있는 엔티티를 제거하는 방법
@Test
@Commit
//트랜잭션이 테스트에 걸린경우 마지막에 전부 롤백시켜버려서 확인이 불가능함.
//그렇기 때문에 따로 Commit을 넣어서 남겨두기.
public void bulkUpdate(){

    long count = queryFactory
            .update(member)
            .set(member.username, "비회원")
            .where(member.age.lt(28))
            .execute();
}
  • 벌크 쿼리를 실행했으면 영속성 컨텍스트를 꼭 초기화 해주자.

SQL Function 호출

  • JPA 와 같이 Dialect에 등록된 내용만 호출할 수 있다.
public void sqlFunction() {
    queryFactory
            .select(Expressions.stringTemplate("function('replace',{0},{1},{2})",
                    member.username, "member", "M"))
            .from(member)
            .fetchFirst();
}
  • 설정하는 방법은 JPA 기본편에서 자세히 설명해준다.
  • 커스텀하는 방법도있지만 기본적으로 사용되는 것은 내장하고 있다.

SQL Function이란 무엇인가?

  • 데이터베이스에서 함수도 일반적인 프로그래밍언어에서 함수의 역할이 동일
  • 입력값을 받아서 특정한 작업을 수행하고 결과값을 출력하는 구조.

특징

  1. 함수에서 입력 값을 받아 처리하고 해당 결과값을 반환한다.
  2. 함수 호출 문에서 반환된 값으로 사용할 수 있다.

예시

-- 생성
CREATE FUNCTION myfunc
(
    	str VARCHAR(32)
)
RETURNS VARCHAR(32)

-- 문자열을 인식 받아 반대로 출력 (수행할 쿼리)
BEGIN
	DECLARE result VARCHAR(32); -- 변수 선언
	SET result = REVERSE(str1); -- 변수에 값넣기 SELECT 컬림 INTO 변수명 도 가능
	RETURN result; -- 함수 호출시 반환할 리턴 값.
END;

--삭제
DROP FUNCTION myfunc;

참고 출처 : MySQL (13) - 프로시저(Procedure)와 함수(Function) (tistory.com)

MySQL 함수(Function) 만들기부터 조회까지 정리글 (tistory.com)


profile
열심히하자

0개의 댓글