[QueryDSL] EP2. 기본 문법
Web/QueryDSL

[QueryDSL] EP2. 기본 문법

 

 

기본 사용 방법

 

1. JPAQueryFactory 생성시 EntityManager를 넘겨주어야 합니다.

@Autowired
EntityManager em;

JPAQueryFactory queryFactory = new JPAQueryFactory(em)

 

 

2. queryfactory .select, .from, .where

        Member findMember = queryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))
                .fetchOne();

 

 

 

 

전체코드

 

 

package study.querydsl;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import study.querydsl.entity.Member;
import study.querydsl.entity.QMember;
import study.querydsl.entity.Team;

import javax.persistence.EntityManager;

import static org.assertj.core.api.Assertions.*;

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    EntityManager em;

    JPAQueryFactory queryFactory;

    @BeforeEach
    public void before() {
        //given
        queryFactory = new JPAQueryFactory(em)
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        //when
        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }

    @Test
    public void startJPQL() throws Exception {
        //member1을 찾아라
        String qlString =
                "select m from Member m " +
                "where m.username = :username";

        Member findMember = em.createQuery(qlString, Member.class)
                .setParameter("username", "member1")
                .getSingleResult();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

    @Test
    public void startQuerydsl() throws Exception {
        QMember m = new QMember("m");

        Member findMember = queryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }
}

 

 

Querydsl의 큰 특징

 

1.

파라미터 바인딩을 setParameter로 해주지 않고, 단지 eq로 파라미터를 넘겨도 jdbc의 preparedstatement 로 파라미터 바인딩을 자동으로 해줍니다.

 

2.

jpql : 만약 문법에서 오타가 발생시 컴파일 시점에 오류 발견이 불가능하고 런타임때 오류가 발생합니다.

querydsl : 컴파일 시점에 오류발견이 바로 가능합니다.

 

3.

queryfactory를 필드로 가져와도 멀티쓰레드 환경에서 동시성 문제가 발생하지 않습니다.

스프링이 제공하는 EntityManager 자체가 여러 멀티쓰레드에서 사용해도 해당 트랜잭션에 바인딩 되도록 분배해주기 때문입니다.

 

 

 

 

Q -Type 사용법

 

위 코드에서 Q-type을 사용할때 아래와 같이 선언해서 사용했습니다.

QMember m = new QMember("m");

Member findMember = queryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))
                .fetchOne();

 

 

 

위 방법보다는 사용하는 곳에서 스태틱임포트를 이용해 사용하는 방법을 쓰는것이 가장 편하고 보기 좋습니다.

 

 

QMember의 public static final QMember member = new QMember("member1") 를 이용해서 

 

아래와 같이 작성하고 스태틱 임포트를 하면

Member findMember = queryFactory
                .select(QMember.member)
                .from(QMember.member)
                .where(QMember.member.username.eq("member1"))
                .fetchOne();

 

 

이렇게 깔끔하게 바로 사용할 수 있습니다.

이 방법이 가장 권장되는 방법입니다.

    @Test
    public void startQuerydsl() throws Exception {
        Member findMember = queryFactory
                .select(member)
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

 

 

 

단, 같은 테이블끼리 조인할때는 Q-type 엔티티의 이름이 달라야 하기 때문에 이름을 다르게 선언해서 사용하면 됩니다.

 

 

 

 

Querydsl은 사실 JPQL의 빌더 입니다.

결국에는 JPQL을 이용하여 쿼리를 생성합니다.

 

그래서 Querydsl로 생성되는 JPQL을 보고싶은 경우 application.yml에서 설정을 해주시면 됩니다.

 

jpa.properties.hibernate.user_sql_comments : true

 

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        # show_sql: true
        format_sql: true
        use_sql_comments: true

 

 

 

 

 

 

검색조건쿼리

 

  • select와 from이 같은경우 selectFrom을 사용해도됩니다.
  • 검색조건이 여러개일 경우 .으로 연결하면 됩니다.
    @Test
    public void search() throws Exception {
        Member findMember = queryFactory
                .selectFrom(member)
                .where(member.username.eq("member1")
                        .and(member.age.between(10, 30)))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

 

 

제공하는 검색조건 정리

member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //이름이 is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like 검색
member.username.contains("member") // like ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색

 

 

,로 여러개 넘기면 전부 and로 조립할 수 있습니다.

중간에 null 있으면 무시합니다. 추후 동적쿼리 만들때 유용합니다.

    @Test
    public void searchAndParam() throws Exception {
        Member findMember = queryFactory
                .selectFrom(member)
                .where(
                        member.username.eq("member1"),
                        member.age.eq(10)
                )
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

 

 

 

 

결과 조회

  • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne() : 단 건 조회
    결과가 없으면 : null
    결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
  • fetchFirst() : limit(1).fetchOne()
  • fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
  • fetchCount() : count 쿼리로 변경해서 count 수 조회

 

사용 예제

        // 리스트
        List<Member> fetch = queryFactory
                .selectFrom(member)
                .fetch();

        //단건
        Member fetchOne = queryFactory
                .selectFrom(member)
                .fetchOne();

        //처음 한 건
        Member fetchFirst = queryFactory
                .selectFrom(member)
                .fetchFirst();

        // 페이징에서 사용
        QueryResults<Member> results = queryFactory
                .selectFrom(member)
                .fetchResults();

        results.getTotal();
        List<Member> content = results.getResults();


        // count 쿼리만
        long total = queryFactory
                .selectFrom(member)
                .fetchCount();

 

fetchResult

페이징용 쿼리입니다. 

total count를 가져오는 쿼리가 추가되어 나가 total count를 가져올 수 있습니다.

 

 

주의점

복잡한쿼리를 날릴땐 페이징쿼리 날리지말고 토탈쿼리, 조회쿼리 따로 날려야합니다.

(복잡한 쿼리일땐 카운트 쿼리가 달라질 수 있고, 성능이슈가 생깁니다.)

 

 

 

 

 

 

 

 

정렬

 

아래의 기준으로 정렬하는 예제 코드를 작성해보겠습니다.

 

1. 회원 나이 내림차순 (desc)

2. 회원 이름 오름차순 (asc)

단 2에서 회원 이름이 없으면 마지막에 출력(nullsLast)

 

    /**
     * 회원 정렬 순서
     * 1. 회원 나이 내림차순 (desc)
     * 2. 회원 이름 올림차순 (asc)
     * 단 2에서 회원 이름이 없으면 마지막에 출력 (null last)
     */

    @Test
    public void sort() throws Exception {
        //given
        em.persist(new Member(null, 100));
        em.persist(new Member("member5", 100));
        em.persist(new Member("member6", 100));
        //when

        List<Member> result = queryFactory
                .selectFrom(member)
                .orderBy(member.age.desc(), member.username.asc().nullsLast())
                .fetch();

        //then

        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

    }

 

나가는 쿼리와 출력결과

 

 

 

 

 

 

페이징

    @Test
    public void paging1() throws Exception {
        //given
        QueryResults<Member> queryResults = queryFactory
                .selectFrom(member)
                .orderBy(member.username.desc())
                .offset(1)
                .limit(2)
                .fetchResults();

        //when

        //then
        assertThat(queryResults.getTotal()).isEqualTo(4);
        assertThat(queryResults.getLimit()).isEqualTo(2);
        assertThat(queryResults.getOffset()).isEqualTo(1);
        assertThat(queryResults.getResults().size()).isEqualTo(2);
    }

 

 

주의점

 

복잡한 쿼리일경우 카운트 로직은 분리하는 것이 좋습니다.

(조인이나 where 조건도 카운트 쿼리에 그대로 들어가서 성능이 저하됨)

 

 

 

 

 

 

 

집합

 

 

select에서 집합함수 사용이 가능합니다.

select에서 원하는것을 조회할때 tuple(querydsl의 튜플)로 반환됩니다.

import com.querydsl.core.Tuple;

 

튜플 사용은 select 절에서 썼던 함수를 그대로 넣으면 그 값을 반환합니다.

 

하지만 실무에서는 튜플을 잘 사용하지 않고 보통 DTO로 바로 만들어 사용합니다.

 

DTO반환은 추후에 다시 학습합니다.

 

집합함수 예제 코드

 

    @Test
    public void aggregation() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(
                        member.count(),
                        member.age.sum(),
                        member.age.avg(),
                        member.age.max(),
                        member.age.min()
                )
                .from(member)
                .fetch();

        //when

        Tuple tuple = result.get(0);

        //then
        assertThat(tuple.get(member.count())).isEqualTo(4);
        assertThat(tuple.get(member.age.sum())).isEqualTo(100);
        assertThat(tuple.get(member.age.avg())).isEqualTo(25);
        assertThat(tuple.get(member.age.max())).isEqualTo(40);
        assertThat(tuple.get(member.age.min())).isEqualTo(10);
    }

 

 

 

 

Group

 

join(member.team, team)으로 member의 team과 team을 조인합니다.

 

그리고 team.name으로 그룹으로 묶습니다.

 

    /**
     * 팀의 이름과 각 팀의 평균 연령을 구해라
     * @throws Exception
     */
    @Test
    public void group() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(team.name, member.age.avg())
                .from(member)
                .join(member.team, team) // member에 있는 team과 team을 조인
                .groupBy(team.name)
                .fetch();

        //when

        Tuple teamA = result.get(0);
        Tuple teamB = result.get(1);


        //then
        assertThat(teamA.get(team.name)).isEqualTo("teamA");
        assertThat(teamA.get(member.age.avg())).isEqualTo(15); // (10+20) / 2

        assertThat(teamB.get(team.name)).isEqualTo("teamB");
        assertThat(teamB.get(member.age.avg())).isEqualTo(35); // (30+40) / 2
    }

 

 

 

 

 

 

조인 - 기본조인

 

join(조인 대상, 별칭으로 사용할 Q-type)

 

    /**
     * 팀 A에 소속된 모든 회원
     * @throws Exception
     */
    @Test
    public void join() throws Exception {
        //given
        List<Member> result = queryFactory
                .selectFrom(member)
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();
        //when

        //then

        assertThat(result)
                .extracting("username")
                .containsExactly("member1", "member2");
    }

 

leftjoin, rightjoin도 가능합니다.

왼쪽부터 join, leftjoin, rightjoin

 

 

세타조인 - 연관관계가 없을때의 join

 

from 절에 여러 엔티티를 선택해서 세타 조인을 할 수 있습니다.

 

외부조인이 불가능합니다. 후에 on절을 이용해서 외부조인이 가능하도록 할 수 있습니다.

    /**
     * 세타 조인
     * 회원의 이름이 팀 이름과 같은 회원 조회
     * @throws Exception
     */
    @Test
    public void theta_join() throws Exception {
        //given
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));
        em.persist(new Member("teamC"));
        //when

        List<Member> result = queryFactory
                .select(member)
                .from(member, team)
                .where(member.username.eq(team.name))
                .fetch();

        //then
        assertThat(result)
                .extracting("username")
                .containsExactly("teamA", "teamB");
    }

 

 

 

 

 

 

 

조인 - on절

 

on절을 활용한 조인 2가지

  1. 조인 대상 필터링
  2. 연관관계 없는 엔티티 외부 조인

 

 

 

 

요구사항

회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회

 

외부 조인(left join)을 사용하면 팀 이름이 teamA가 아니면 null이 나오게 됩니다.

    /**
     * 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
     * JPQL : select m, t from Member m left join m.team on t.name = 'teamA'
     *
     * @throws Exception
     */
    @Test
    public void join_on_filtering() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq("teamA"))
                .fetch();
        //when

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }

        //then
    }

 

 

 

 

만약 member을 모두 조회할 필요가 없고 조건에 맞는 member만 조회하려면 내부조인(join)을 사용하면 됩니다.

    @Test
    public void join_on_filtering() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .join(member.team, team).on(team.name.eq("teamA"))
                .fetch();
        //when

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }

        //then
    }

 

 

하지만 내부조인의 on절은 where와 정확히 똑같은 역할을 하게됩니다.

그래서 내부조인시에는 조건을 줄때 우리에게 익숙한 where절을 사용하도록 합니다.

    @Test
    public void join_on_filtering() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
//                .join(member.team, team).on(team.name.eq("teamA"))
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();
        //when

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }

        //then
    }

 

 

외부조인 (outter, leftJoin)을 사용할때는 where절을 사용할 수 없고 on절로만 사용해야합니다.

 

 

 

 

 

 

요구사항

회원의 이름이 팀 이름과 같은 대상 외부 조인

 

서로 연관관계가 없는 엔티티끼리 조회할때는 주의해야할 점이 있습니다.

 

연관관계 없으니까 member.team 넣는게 아닌 team을 넣습니다.

member와 team을 그 자체로 조인해버리고 필터링 하는 것입니다.

 

    /**
     * 연관관계가 없는 엔티티 외부 조인
     * 회원의 이름이 팀 이름과 같은 대상 외부 조인
     * @throws Exception
     */
    @Test
    public void join_on_no_relation() throws Exception {
        //given
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));
        em.persist(new Member("teamC"));
        //when

        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .join(team).on(member.username.eq(team.name))
                .fetch();

        //then
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

 

 

 

 

외부조인도 가능합니다.

    /**
     * 연관관계가 없는 엔티티 외부 조인
     * 회원의 이름이 팀 이름과 같은 대상 외부 조인
     * @throws Exception
     */
    @Test
    public void join_on_no_relation() throws Exception {
        //given
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));
        em.persist(new Member("teamC"));
        //when

        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
//                .join(team).on(member.username.eq(team.name))
                .leftJoin(team).on(member.username.eq(team.name))
                .fetch();

        //then
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
    }

 

 

 

 

 

 

 

조인 - 페치조인

 

Member

 

Member 엔티티에서 성능최적화를 위해 Team은 Lazy로 설정되어있습니다.

 

 

 

그래서 아래와같이 Member을 조회하면 Team은 조회되지 않습니다.

    @Test
    public void fetchJoinNo() throws Exception {
        //given
        em.flush();
        em.clear();

        Member findMember = queryFactory
                .selectFrom(member)
                .where(member.username.eq("member1"))
                .fetchOne();

        //when
        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("페치 조인 미적용").isFalse();


        //then
    }

 

 

 

 

fetch join을 Querydsl에서 쓰는 방법

 

join 절에 .fetchJoin()을 붙여주면 됩니다.

 

.join(member.team, team).fetchJoin()

 

inner join으로 team을 같이 조회해옵니다.

    @Test
    public void fetchJoinUse() throws Exception {
        //given
        em.flush();
        em.clear();

        Member findMember = queryFactory
                .selectFrom(member)
                .join(member.team, team).fetchJoin()
                .where(member.username.eq("member1"))
                .fetchOne();

        //when
        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
        assertThat(loaded).as("페치 조인 미적용").isTrue();


        //then
    }

 

 

 

 

 

 

 

 

 

서브쿼리

 

 

요구사항

나이가 가장 많은 회원 조회

 

나이가 가장 많은 회원의 나이를 구하고 그 회원과 같은 나이의 회원들을 구하는 쿼리를 짜야합니다.

 

서브쿼리를 짜기 위해서는 2가지가 필요합니다.

  1. 서브쿼리에 넣을 다른 Q타입
  2. JPAExpressions

 

 

where절의 eq안에 JPAExpressions로 서브쿼리를 만들어주면 됩니다.

이때 서브쿼리 안에는 서브쿼리 밖에 있는 엔티티와 다른 엔티티가 필요하기 때문에 새로 선언해 만들어줍니다.

    /**
     * 나이가 가장 많은 회원 조회
     * @throws Exception
     */
    @Test
    public void subQuery() throws Exception {

        QMember memberSub = new QMember("memberSub");
        //given
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.eq(
                        JPAExpressions
                                .select(memberSub.age.max())
                                .from(memberSub)
                ))
                .fetch();

        //when
        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

        //then
        assertThat(result).extracting("age")
                .containsExactly(40);
    }

 

 

eq외에도 goe, in절 등도 사용이 가능합니다.

 

/**
     * 나이가 평균이상인 회원 조회
     * @throws Exception
     */
    @Test
    public void subQueryGoe() throws Exception {

        QMember memberSub = new QMember("memberSub");
        //given
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.goe(
                        JPAExpressions
                                .select(memberSub.age.avg())
                                .from(memberSub)
                ))
                .fetch();

        //when
        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

        //then
        assertThat(result).extracting("age")
                .containsExactly(30, 40);
    }

    /**
     * 나이가 평균이상인 회원 조회
     * @throws Exception
     */
    @Test
    public void subQueryIn() throws Exception {

        QMember memberSub = new QMember("memberSub");
        //given
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.in(
                        JPAExpressions
                                .select(memberSub.age)
                                .from(memberSub)
                                .where(memberSub.age.gt(10))
                ))
                .fetch();

        //when
        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

        //then
        assertThat(result).extracting("age")
                .containsExactly(20, 30, 40);
    }

 

 

 

또한, where절 뿐만 아니라 select 절에도 서브쿼리 사용이 가능합니다.

 

    @Test
    public void selectSubQuery() throws Exception {

        QMember memberSub = new QMember("memberSub");


        //given
        List<Tuple> result = queryFactory
                .select(member.age,
                        JPAExpressions
                                .select(memberSub.age.avg())
                                .from(memberSub))
                .from(member)
                .fetch();

        //when

        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }

        //then
    }

 

 

 

서브 쿼리 한계

 

JPA, JPQL의 서브쿼리 한계때문에 결국 QueryDSL에도 같은 한계가 있습니다.

 

from절에서 서브쿼리 사용이 안됩니다.

 

 

from절에서 서브쿼리 사용불가에 대한 해결방안 3가지

 

  1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
  2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
  3. nativeSQL을 사용한다.

 

 

보통 from절에 서브쿼리를 쓰는 이유에는 안좋은 이유가 많습니다. (복잡한 쿼리, 뷰에 맞춘 쿼리 등)

db는 데이터를 최소화해 퍼올리는 용도로 쓰고 복잡한 로직은 애플리케이션에서 하는 것이 좋습니다.

 

 

 

 

 

 

Case문

 

요구사항

회원들의 나이를 조회하는데,

10살은 "열살"

20살은 "스무살"

나머지는 "기타"로 조회하기

 

 

select 절에서 member.age.when.then으로 사용이 가능합니다.

 

    public void basicCase() throws Exception {
        //given
        List<String> fetch = queryFactory
                .select(member.age
                        .when(10).then("열살")
                        .when(20).then("스무살")
                        .otherwise("기타"))
                .from(member)
                .fetch();

        //when
        for (String s : fetch) {
            System.out.println("s = " + s);
        }

        //then
    }

 

 

 

요구사항

회원들의 나이를 조회하는데,

0~20살은 "0~20살"

21~31살은 "21~30살"

나머지는 "기타"로 조회하기

 

 

 

new CaseBuilder()을 사용하여 복잡한 case 사용이 가능합니다.

    @Test
    public void complexCase() throws Exception {
        //given
        List<String> result = queryFactory
                .select(new CaseBuilder()
                        .when(member.age.between(0, 20)).then("0~20살")
                        .when(member.age.between(21, 30)).then("21~30살")
                        .otherwise("기타"))
                .from(member)
                .fetch();

        //when
        for (String s : result) {
            System.out.println("s = " + s);
        }

        //then
    }

 

 

 

 

 

웬만하면 case절(when then)을 사용하지 않고 db에서는 그냥 데이터 줄이는 일만하기

 

 

 

 

 

 

 

 

 

 

상수사용, 문자더하기

 

상수사용

 

Expressions.constant를 사용하면 됩니다.

 

JPQL이나 SQL에서는 상수가 나가지 않고 결과값에서만 상수가 적용됩니다.

 

    @Test
    public void constant() throws Exception {
        //given
        List<Tuple> result = queryFactory
                .select(member.username, Expressions.constant("A"))
                .from(member)
                .fetch();

        //when
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }

        //then
    }

 

 

 

 

문자더하기

 

만약 {username}_{age} 형식으로 표시하려고 할때 사용하면 좋습니다.

 

.concat을 사용하면 됩니다.

주의할 점은 age같은 경우 타입이 String이 아니기 때문에 .stringValue()로 캐스팅을 해주어야합니다.

 

SQL에서도 age가 casting 되는 것을 볼 수 있습니다.

 

    @Test
    public void concat() throws Exception {
        //given

        //{username}_{age}
        List<String> result = queryFactory
                .select(member.username.concat("_").concat(member.age.stringValue()))
                .from(member)
                .where(member.username.eq("member1"))
                .fetch();

        //when

        for (String s : result) {
            System.out.println("s = " + s);
        }

        //then
    }

 

 

 

.stringValue()는 enum 처리할때 사용 하면 좋습니다. (자주 사용됨)