김영한님의 인프런 실전! Querydsl을 듣고 정리한 내용입니다. 실전! Querydsl 링크
Java 최신 기술의 마지막 퍼즐.
스프링 부트 + 스프링 Data Jpa로 해결하지 못하는 문제?
1. 복잡한 쿼리
2. 동적인 쿼리
이 문제를 깔끔하게 해결해 주는 Querydsl.
1. 쿼리를 자바 코드로 작성
2. 문법 오류를 컴파일 시점에 알 수 있음.
3. 동적 쿼리 문제 해결!
4. 쉬운 SQL 스타일 문법
@Test
public void jpql() {
String username = "kim";
String query = "select m from Member m" + "where m.username =:username";
//실수 : 실제 쿼리는 "select m from member mwhere m.username=:username"
//자바 컴파일러가 이를 잡아주지 못함. 실행해봐야 알 수 있음.
List<Member> result = em.createQuery(query, Member.class).getResultList();
}
@Test
public void querydsl() {
String username = "kim";
List<Member> result = queryFactory
.select(member)
.from(member)
.where(member.username.eq(username))
.fetch();
//자동완성 가능
//메서드로 extract가능
//띄어쓰기, 오타 걱정 없음
}
프로젝트 생성은 다른 프로젝트 생성과 동일.
1. Querydsl 설정
plugins {
id 'org.springframework.boot' version '2.5.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
//querydsl 추가
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
id 'java'
}
dependencies {
...
//querydsl
implementation 'com.querydsl:querydsl-jpa'
...
}
//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
//에디터 설정
sourceSets {
main.java.srcDir querydslDir
}
//컴파일 클래스 패스에 넣음.
configurations {
querydsl.extendsFrom compileClasspath
}
//어노테이션 프로세서와 맞물려서 빌드시 생성
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
//여기가 안되면 삽질을 좀 해야합니다.
@Entity
@Getter
@NoArgsConstructor
public class Hello {
@Id
@GeneratedValue
private Long id;
}
위의 빌드 작업목록에 compileQuerydsl이 존재
@Generated("com.querydsl.codegen.EntitySerializer")
public class QHello extends EntityPathBase<Hello> {
private static final long serialVersionUID = -229139972L;
public static final QHello hello = new QHello("hello");
//자기 자신을 가지고 있음
public final NumberPath<Long> id = createNumber("id", Long.class);
public QHello(String variable) {
super(Hello.class, forVariable(variable));
}
public QHello(Path<? extends Hello> path) {
super(path.getType(), path.getMetadata());
}
public QHello(PathMetadata metadata) {
super(Hello.class, metadata);
}
}
@Test
void contextLoads() {
Hello hello = new Hello();
em.persist(hello);
JPAQueryFactory query = new JPAQueryFactory(em);
QHello qHello = QHello.hello;
//쿼리와 관련된건 Q타입으로 작성함.
Hello result= query
.selectFrom(qHello)
.fetchOne();
assertThat(result).isEqualTo(hello);
assertThat(result.getId()).isEqualTo(hello.getId());
}
이전과 동일!
//QueryBasicTest.class
@Autowired
EntityManager em;
@BeforeEach
public void before() {
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);
}
//멤버1 찾기
@Test
public void startJPQL() {
String qlString = "select m from Member m " +
"where m.username=:username";
Member findMember = em.createQuery(qlString, Member.class)
.setParameter("username", "member1")
.getSingleResult();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m"); //어떤 Q멤버인지 별칭
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1")) //파라미터 바인딩을 안해도, jdbc prepare statement로 바인딩을 해줌.
.fetchOne();
Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
Querydsl의 장점
1) 문자열로 쿼리를 만드는 것이 아니고, Q타입을 생성해서 자바 코드로 처리하기 때문에, 컴파일 에러를 활용 가능. 만약 쿼리문을 잘못 짠 경우, 컴파일러가 잘못 짰다고 알려줌.
2) 코드 어시스턴스 활용 가능. 뭐 있을것 같은데..? like 있나..? 같은 경우
3) 파라미터 바인딩을 해줌. where절에서 보이는 것이 문자열로 처리하는 것이 아닌, jdbc prepare statement를 통해서 하는 것이기 떄문에, 파라미터 바인딩이 알아서 처리되는 것.
//QueryBasicTest.class
@Autowired
EntityManager em;
JPAQueryFactory queryFactory;
@BeforeEach
public void before() {
queryFactory = new JPAQueryFactory(em);
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
...
}
QMember qMember = new QMember("m");
QMember qMember = QMember.member;
import static study.querydsl.domain.QMember.*;
...
//Test Method
Member findMember = queryFactory
.select(member)
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
```java
QMember qmember = new QMember(“m1”);
...
```
@Test
public void search() {
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1")
.and(member.age.eq(10)))
.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()
member.age.in(10,20) //age in 10,20
member.age.notIn(10,20) // age not in 10,20
member.age.between(10,30)
member.age.goe(30) // age >= 30, greater or equal
member.age.gt(30) // age>30 , greater than
member.age.loe(30) // age<=30, low or eqaul
member.age.lt(30) // age<30 , low than
member.usename.like("member%") //like검색
member.username.contains("member") //like %member% 검색
member.username.startsWith("member") // like "member%" 검색
...
@Test
public void searchAndParam() {
Member findMember = queryFactory
.selectFrom(member)
.where(
member.username.eq("member1"),
member.age.eq(10)
)
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void resultFetch() {
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
Member fetchOne = queryFactory
.selectFrom(member)
.fetchOne();
//.limit(1).fetchOne()과 동일
Member oneMember =
queryFactory.selectFrom(QMember.member)
.fetchFirst();
//패아장이 나감 -> 카운트 쿼리 + 데이터 쿼리 같이나감
QueryResults<Member> results = queryFactory
.selectFrom(QMember.member)
.fetchResults();
results.getTotal();
//content를 가져가야함.
List<Member> content = results.getResults();
long total = queryFactory.selectFrom(member)
.fetchCount();
}
/**
* 회원 정렬 순서
* 1. 회원 나이 내림차순 (Desc)
* 2. 회원 이름 오름차순 (asc)
* 단 2에서 회원 이름이 없으면 마지막에 출력 (Nulls last)
*/
@Test
public void sort() {
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();
}
@Test
public void paging1() {
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //앞에 몇개 스킵할거야? -> 1은 하나 스킵할거야. (0부터 시작)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
@Test
public void paging2() {
QueryResults<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //앞에 몇개 스킵할거야? -> 1은 하나 스킵할거야. (0부터 시작)
.limit(2)
.fetchResults();
assertThat(result.getTotal()).isEqualTo(4);
assertThat(result.getLimit()).isEqualTo(2);
assertThat(result.getOffset()).isEqualTo(1);
assertThat(result.getResults().size()).isEqualTo(2);
}
놀라운 템플릿
@Test
public void aggregation() {
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);
//Tuple로 조회하는 이유 : 데이터가 여러 타입으로 들어오기 때문. 실무에서 많이 쓰지는 않고, DTO로 직접 뽑아오는 방법을 많이 씀.
}
/**
* 팀의 이름과 각 팀의 평균 연령을 구해라
*/
@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);
}
@Test
public void havingTest() throws Exception {
em.persist(new Member("test",40));
em.persist(new Member("test",50));
List<Tuple> fetch = queryFactory
.select(member.age,member.count())
.from(member)
.groupBy(member.age)
.having(member.age.gt(30))
.fetch();
for (Tuple tuple : fetch) {
System.out.println(tuple);
}
Tuple targetAge = fetch.get(0);
assertThat(targetAge.get(member.age)).isEqualTo(40);
}
...