@BeforeEach
public void before() throws Exception {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
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);
em.flush();
em.clear();
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
for (Member member : members) {
System.out.println("member = " + member);
System.out.println("member.getTeam() = " + member.getTeam());
}
}
@Autowired EntityManager em;
@Test
public void startJPQL() throws Exception {
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 {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m");
Member findMember = queryFactory .select(m)
.from(m)
.where(m.username.eq("member1")) // 파라미터 바인딩 처리
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
EntityManager
를 주입해서 JPAQueryFactory
를 생성한다.Querydsl
은 JPQL 빌더@Autowired
EntityManager em;
JPAQueryFactory queryFactory;
@BeforeEach
public void before() throws Exception {
queryFactory = new JPAQueryFactory(em);
...
}
@Test
public void startJPQL() throws Exception {
//member1 find
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 {
//given
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
✅ JPAQueryFactory를 필드로 제공하면 동시성 문제(Multi Threading)은 어떻게 될까 ❓
👉 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다.
위의 Querydsl 예제에서는 Q 클래스의 인스턴스를 사용할 때 new QMember("m")
와 같이 new
를 통해 별칭을 직접 지정해 줬지만, Querydsl
에서 제공하는 기본 인스턴스를 사용하는 게 더 간편하다.
QMember qMember = new QMember("M"); // 별칭 직접 지정
QMember qMember = QMember.member; // 기본 인스턴스 사용
member
도 바로 사용할 수 있다.import static study.querydsl.entity.QMember.*;
@Test
public void startQuerydsl3(){
Member findMember = queryFactory.selectFrom(member).where(member.id.eq(1L).fetchOne();
assertThat(findMember.getId)).isEqualTo(1L);
}
@Test
public void searchAndParam() throws Exception {
Member findMember = queryFactory
.selectFrom(member)
.where(
member.username.eq("member1")
.and(member.age.eq(10))
)
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
.and()
, .or()
를 메서드 체인으로 연결할 수 있다.select
와 from
은 selectFrom
으로 합칠 수 있다.@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");
}
member.username.eq("a")
: username = 'a'member.username.ne("a")
: username ≠ 'a'member.username.eq("a").not()
: username ≠ 'a'member.username.isNotNull()
: username is not nullmember.age.in(10,20)
: age in (10,20)member.age.notIn(10,20)
: age not in(10,20)member.age.between(10,30)
: age between 10, 30member.age.goe(30)
: age ≥ 30member.age.gt(30)
: age > 30member.age.loe(30)
: age ≤ 30member.age.lt(30)
: age < 30member.username.like("member%")
: username like 'member%'member.username.contains("member')
: username like '%member%'member.username.startsWith("member")
: like 'member%' fetch()
: 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne()
: 단 건 조회
com.querydsl.core.NonUniqueResultException
fetchFirst()
: limit(1).fetchOne()
과 같다.
fetchResults()
: 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount()
:count 쿼리로 변경해서 count 수 조회
@Test
public void resultFetchTest() throws Exception {
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
Member fetchOne = queryFactory
.selectFrom(QMember.member)
.fetchOne();
Member fetchFirst = queryFactory
.selectFrom(QMember.member)
.fetchFirst();
QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults();
results.getTotal();
List<Member> content = results.getResults();
long total = queryFactory
.selectFrom(member)
.fetchCount();
}
Querydsl에서는 정렬(Sort)용 메서드 역시 제공된다.
👉 .orderBy(인스턴스명.기준필드.정렬기준.(nullsLast()|nullsFirst()))
/**
* 회원 정렬 순서
* 1. 회원 나이 내림차순(desc)
* 2. 회원 이름 올림차순(asc)
* 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
*/
@Test
public void sort() throws Exception {
em.persist(new Member(null, 100));
em.persist(new Member("member5", 100));
em.persist(new Member("member6", 100));
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
Member member5 = result.get(0);
Member member6 = result.get(1);
Member memberNull = result.get(2);
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isNull();
}
desc()
, asc()
:일반 정렬nullsLast()
, nullsFirst()
:null 데이터 순서 부여@Test
public void paging1() throws Exception {
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
👉 [0][1][2]
[3]
@Test
public void paging2() {
QueryResults<Member> queryResults = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(queryResults.getTotal()).isEqualTo(4);
assertThat(queryResults.getLimit()).isEqualTo(2);
assertThat(queryResults.getOffset()).isEqualTo(1);
assertThat(queryResults.getResults().size()).isEqualTo(2);
}
❗ count 쿼리가 실행되니 성능상 주의
✅ 실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만,
count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인을 해버리기 때문에 성능이 안 나올 수 있다. count 쿼리에 조인이 필요 없는 성능 최적화가 필요하다면, count 전용 쿼리를 별도로 작성해야 한다.
@Test
public void aggregation() throws Exception {
List<Tuple> result = queryFactory
.select(
member.count(), // 회원 수
member.age.sum(), // 나이 합
member.age.avg(), // 나이 평균
member.age.max(), // 최대 나이
member.age.min() // 최소 나이
)
.from(member)
.fetch();
Tuple tuple = result.get(0);
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);
}
@Test
public void group() throws Exception {
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}
having
함수 역시 같이 사용 가능하다....
.groupBy(member.age)
.having(member.age.gt(25))
...