1. 엔티티 설계(단반향 매핑)
Member
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username; // 회원 이름
private int age; // 회원 나이
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team; // 소속 팀
// 팀 변경 로직
public void changeTeam(Team team) {
this.team = team;
}
}
Team
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class Team {
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name; // 팀 이름
}
2. 데이터 세팅(샘플 데이터 삽입)
@Component
public class InitData {
private final EntityManager em;
public InitData(EntityManager em) {
this.em = em;
}
@PostConstruct
@Transactional
public void init() {
Team teamA = Team.builder().name("TeamA").build();
Team teamB = Team.builder().name("TeamB").build();
em.persist(teamA);
em.persist(teamB);
Member member1 = Member.builder().username("John").age(25).team(teamA).build();
Member member2 = Member.builder().username("Jane").age(30).team(teamA).build();
Member member3 = Member.builder().username("Tom").age(35).team(teamB).build();
Member member4 = Member.builder().username("Kim").age(40).team(teamB).build();
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
}
}
3. QueryDSL 예제
단방향 매핑에서는 Team이 Member로 참조하지 않으므로, 모든 쿼리는 Member를 중심으로 작성
1) 기본 Q-Type 활용
Q클래스 인스턴스를 사용하는 2가지 방법
QMember qMember = new QMember('m'); // 별칭 직접 지정
QMember qMember = QMember.member; // 기본 인스턴스 사용
"QMember.member에서 QMember는 static import가 가능하여 member만 써도됨"
QMember member = QMember.member;
List<Member> members = queryFactory
.selectFrom(member)
.fetch(); // 모든 멤버 조회
members.forEach(m -> System.out.println("Member: " + m.getUsername()));
2) 검색 조건 쿼리
List<Member> members = queryFactory
.selectFrom(member)
.where(
member.age.gt(30), // 나이가 30보다 큰 멤버 (gt는 grater 줄임말)
member.team.name.eq("TeamB") // 팀 이름이 TeamB인 조건(eq는 equals 줄임말)
)
.fetch();
JPQL이 제공하는 모든 검색 조건 제공
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%’ 검색
2-1) AND 조건을 파라미터로 처리
public void searchAndParam() {
List<Member> result = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"),
member.age.eq(10))
.fetch();
}
- where() 에 파라미터로 검색조건을 추가하면 AND 조건이 추가됨.
3) 결과 조회
// List(전체 조회)
List<Member> fetch = queryFactory
.selectFrom(member)
.fetch();
// 단건 조회
Member singleMember = queryFactory
.selectFrom(member)
.where(member.username.eq("John")) // 이름이 Jhon인 조건
.fetchOne();
// 처음 한 건 조회
Member findFirstMember = queryFactory
.selectFrom(member)
.fetchFirst();
// 총 멤버 수 조회
long count = queryFactory
.selectFrom(member)
.fetchCount();
// 페이징에서 사용
QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults();
4) 정렬
List<Member> sortedMembers = queryFactory
.selectFrom(member)
.orderBy(
member.age.desc(), // 나이 내림차순
member.username.asc() // 이름 오름차순
member.username.asc().nullsLast() // 이름 오름차순인데 null이면 마지막
)
.fetch();
5) 페이징
List<Member> pagedMembers = queryFactory
.selectFrom(member)
.orderBy(member.age.desc())
.offset(0) // 0번 페이지부터
.limit(2) // 2개 가져오기
.fetch();
- 주의! 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인 해버리기 떄문에 성능이 안나올 수 있다. count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면, count 전용 쿼리를 별도로 작성해야 한다.(그래서 fetch()로 List 형태로 가져와 count 쿼리와 조합해 페이징을 처리하자!)
6. 집합
List<Tuple> result = queryFactory
.select(member.team.name, member.age.avg())
.from(member)
.groupBy(member.team.name) // 팀별로 그룹화 // having절로 그룹화된 결과를 제한 가능
.fetch();
result.forEach(tuple -> {
System.out.println("Team: " + tuple.get(member.team.name)
+ ", Avg Age: " + tuple.get(member.age.avg()));
});
//전체 조회 수
queryResults<Member> queryResults = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
7. 조인 - 기본 조인
List<Member> teamAMembers = queryFactory
.selectFrom(member)
.join(member.team, team) // Member와 Team 조인, team은 QTeam team임
.where(team.name.eq("TeamA"))
.fetch();
8. 조인 - on절
8-1) 조인 대상 필터링
ex) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
List<Tuple> result = queryFactor
.select(member, team) // Member 엔티티의 데이터와 Team 엔티티의 데이터를 함께 조회
.from(member) // 데이터 소스(Member 테이블)
.leftJoin(member.team, team).on(team.name.eq("teamA")) // 조건 기반 조인
.fetch();
"
t=[Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
t=[Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
"
9. 조인 - 페치 조인
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1")
.fetchOne();
10. orderBy에서 Case문과 함께 사용하기 예제
ex)
1. 0 ~ 30살이 아닌 회원을 가장 먼저 출력
2. 0 ~ 20살 회원 출력
3. 21 ~ 30살 회원 출력
NumberExpression<Integer> rankPath = new CaseBuilder()
.when(member.age.between(0, 20)).then(2)
.when(member.age.between(21, 31)).then(1)
.otherwise(3);
List<Tuple> result = queryFactory
.select(member.username, member.age, rankPath)
.from(member)
.orderBy(rankPath.desc())
.fetch();
"
username = member4 age = 40 rank = 3
username = member1 age = 10 rank = 2
username = member2 age = 20 rank = 2
username = member3 age = 30 rank = 1
"
QueryDSL은 자바 코드로 작성하기 때문에 rankPath처럼 복잡한 조건을 변수로 선언해서 select절, orderBy절에서 함께 사용할 수 있다.
11. 문자 더하기
Strint result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
"
member1_10
"
- member.age.stringValue() 부분이 중요하다. 문자가 아닌 다른 타입들은 stringValue()로 문자로 변환할 수 있다. 이 방법은 ENUM을 처리할 때 자주 사용한다.
'Back-End > QueryDSL' 카테고리의 다른 글
| Spring Data Jpa와 QueryDSL (0) | 2025.01.17 |
|---|---|
| QueryDSL 중급 문법 (0) | 2025.01.17 |
| JPQL을 QueryDSL로 바꿔보자! (0) | 2025.01.17 |
| QueryDsl 3.x 설정 (0) | 2024.12.29 |