3์ฐจ ํ๋ก์ ํธ ๋ ๋ณต์กํ ์ฟผ๋ฆฌ๋ ๋๋๋ก QueryDsl์ ์ฌ์ฉํด๊ณ ์ ์์๋ดค๋ค.
Springboot์ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋นํด ์ค์ ์ด ๋ณต์กํ๋ค.
plugins {
id 'org.springframework.boot' version '2.4.12'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.dhk'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// ==== Query dsl ==== //
implementation 'com.querydsl:querydsl-jpa'
// Qํด๋์ค ์์ฑ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}
test {
useJUnitPlatform()
}
ํ
์คํธ๋ฅผ ์ํ ๋๋ฉ์ธ์ ์์ฑํ๋ค.
์ฒ์ ์ ํ๋ ๊ธฐ์ ์ด๊ธฐ ๋๋ฌธ์ ๋๋ฉ์ธ์ ๋งค์ฐ๋งค์ฐ ๊ฐ๋จํ๊ฒ ์์ฑํ๋ค.
User ์ํฐํฐ
@Entity
@NoArgsConstructor
@Getter
public class User {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
private int age;
@JoinColumn(name = "team_id")
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
public void setTeam(Team team) {
this.team = team;
}
public User(String username, String password, int age) {
this.username = username;
this.password = password;
this.age = age;
}
}
Team ์ํฐํฐ
@Entity
@NoArgsConstructor
@Getter
public class Team {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String teamName;
private int teamAge; // ํ์ด ์๊ธด์ง ๋ช ๋
๋๋์ง... ํ
์คํธ๋ฅผ ์ํด ์ต์ง๋ก ..ใ
public Team(String teamName, int teamAge) {
this.teamName = teamName;
this.teamAge = teamAge;
}
}
์ฐ๋ฆฌ๊ฐ ๋ง๋ Entity๋ฅผ QueryDsl์์ ์ฌ์ฉํ๋ ค๋ฉด ์ํฐํฐ์ ๋์๋๋ Qํ์
ํด๋์ค๊ฐ ํ์ํ๋ค.
์์ Gradle ์ค์ ์ผ๋ก Qํ์
ํด๋์ค๋ฅผ ๋ง๋ค ์ ์๋ ์ํ์ด๋ค.
Gradle ํ
์คํฌ์ ๋์์ ๋ฐ์ Qํ์
ํด๋์ค๋ฅผ ์์ฑํ๋ฉด ์๋ ๊ฒฝ๋ก๋ก ์์ฑ๋๋ค.
์์ฑ๋๋ ๊ฒฝ๋ก๋ build.gradle์์ ๋ณ๊ฒฝ๊ฐ๋ฅํ๋ค.
๋ง์ง๋ง์ผ๋ก QueryDsl ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ๋น์ ํ๋ ๋ฑ๋กํด์ค์ผ ํ๋ค.
์ด์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ๋ EntityManager๊ฐ ์๋๊ณ JPAQueryFactory๋ฅผ ์ฃผ์
๋ฐ์์ ์์ฑํด์ผ ํ๋ค. QueryDsl์ EntityManager๋ JPAQueryFactory์ด๋ค ๋ผ๊ณ ์ดํดํด๋ ๋ ๋ฏํ๋ค.
@RequiredArgsConstructor
@Configuration
public class AppConfig {
private final EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
๋ฑ๋กํ JPAQueryFactory๋ฅผ ์ฃผ์ ๋ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ค.
@Autowired
private JPAQueryFactory query;
selectFrom์ *์ ๊ฐ์ ์๋ฏธ๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
์ฌ๊ธฐ์ user
๋ Qํ์
์ QUserํด๋์ค ์ธ์คํด์ค์ด๋ค. static์ผ๋ก ์ ์ธ๋์ด ์์ด์ static importํด์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ค.
@Order(1)
@DisplayName("1. ๊ธฐ๋ณธ์กฐํ ์กฐ๊ฑด์์ด ๋ชจ๋ ์กฐํ")
@Test
void ๊ธฐ๋ณธ์กฐํ() {
// select * from user;
List<User> users = query.selectFrom(user) // QUser.user ==> user
.fetch();
users.forEach(u -> System.out.println(u.getUsername()));
}
์กฐ๊ฑด์๋ finAll์ ์ธ ์ผ์ด ์๋ค. ์กฐ๊ฑด์ ํ๋์ฉ ์ถ๊ฐํด๋ณด์.
์กฐ๊ฑด๋ค์ด ๊ต์ฅํ ์ง๊ด์ ์ด๋ค.
equal, greater than, less than, greater or equals, less or equals ...
๋๋์ ์ผ๋ก IDE์ ๋์์ ๋ฐ์ผ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
@Order(2)
@Test
@DisplayName("2. ๊ธฐ๋ณธ์กฐํ where ์ ํฌํจ")
void ๊ธฐ๋ณธ์กฐํ_where() {
// select * from user where username = test1;
List<User> users = query.selectFrom(user)
.where(user.username.eq("test1"))
.fetch();
users.forEach(u -> System.out.println(u.getUsername()));
}
@Test
@DisplayName("2-1 ๊ธฐ๋ณธ์กฐํ where์ ํฌํ")
void ๊ธฐ๋ณธ์กฐํ_where2() {
// select * from user where age between 20 and 30;
List<User> users = query.selectFrom(user)
.where(user.age.between(20, 30))
.fetch();
users.forEach(u -> System.out.println(u.getUsername()));
}
@Test
@DisplayName("3. ์ ๋ ฌ")
void ๋์ด_์ ๋ ฌ_์ค๋ฆ์ฐจ์() {
// ํ1์ ์ํ user๋ฅผ ๋์ด์์ผ๋ก ์ ๋ ฌ
/**
* select * from user u
* join team t
* where u.team_id = t.id and t.name = team1
* orderBy u.age asc;
*/
List<User> users = query.selectFrom(QUser.user)
.where(user.team.teamName.eq("team1"))
.orderBy(QUser.user.age.asc())
.fetch();
users.forEach(u -> System.out.println(u.getAge()));
}
๋ฐํํ์
์ด QueryReult์ด๋ค.
JPA์ Page๊ฐ์ฒด์ ๋น์ทํ ํํ์ด๋ค. ํ์ด์ง์ ๋ํ ์ ๋ณด์ ๋ฆฌ์คํธํํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณ ์๋ค.
@Order(5)
@Test
@DisplayName("4. ํ์ด์ง")
void ํ์ด์ง() {
QueryResults<User> queryResults = query.selectFrom(user)
.offset(1)
.limit(3)
.fetchResults();
List<User> users = queryResults.getResults();
for (User user1 : users) {
System.out.println(user1.getUsername());
}
System.out.println("offset = " + queryResults.getOffset());
System.out.println("limit = " + queryResults.getLimit());
System.out.println("total = " + queryResults.getTotal());
}
(User์ Team์ ManyToOne ๋จ๋ฐฉํฅ ๊ด๊ณ์ด๋ค)
@Order(6)
@Test
@DisplayName("5. Join")
void join1() {
// team, user inner join
List<User> users = query.selectFrom(user)
.join(user.team, team)
.fetch();
users.forEach(u -> System.out.println(u.getUsername()));
assertEquals(4, users.size());
}
left join๋ ํค์๋๋ง ๋ฐ๊ฟ์ฃผ๋ฉด ๊ฐ๋จํ๊ฒ ๊ฐ๋ฅํ๋ค.
@Test
@DisplayName("6. left join")
void leftJoin() {
List<User> users = query.selectFrom(user)
.leftJoin(user.team, team)
.fetch();
users.forEach(u -> System.out.println(u.getUsername()));
assertEquals(6, users.size());
}
Subquery
์๋ธ์ฟผ๋ฆฌ๋ JPAExpression
์ ํตํด ๊ฐ๋ฅํ๋ค.
where์ ์์ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ํ
์คํธ์ด๋ค.
Qํ์ ํด๋์ค์ ํ๋์์ ์ง๊ณ์ฟผ๋ฆฌ ํธ์ถ์ด ๊ฐ๋ฅํ ๊ฒ๋ ๋ณผ ์ ์๋ค. (max())
@Test
@DisplayName("7. where์ ์๋ธ์ฟผ๋ฆฌ")
void whereSubquery() {
List<User> users = query.selectFrom(user)
.where(user.age.in(
JPAExpressions
.select(team.teamAge.max())
.from(team)
))
.fetch();
assertEquals(1, users.size());
}
Case ์ฟผ๋ฆฌ
์ค์ ๋ก ์ฌ์ฉํด๋ณธ ์ ์ ์๋๋ฐ ์ถฉ๋ถํ ์จ์ผ ํ ๊ฒฝ์ฐ๊ฐ ์์ ๊ฒ ๊ฐ๋ค.
CaseBuilder
๋ฅผ ์ด์ฉํด์ when
-then
์ ๋ก Case๋ฌธ์ ๊ตฌ์ฑํ๊ณ ์๋ค.
@Test
@DisplayName("8. case ์ฟผ๋ฆฌ")
void caseQuery() {
List<String> results = query
.select(new CaseBuilder()
.when(user.age.loe(30)).then("์ฃผ๋์ด")
.when(user.age.goe(31)).then("์๋์ด")
.otherwise("์ด๋ฆฐ์ด: " + user.age)
).from(user)
.fetch();
results.forEach(System.out::println);
}
ํ๋ก์ ์
ํ๋ก์ ์
์ ํ ๋ฆด๋ ์ด์
์์ ํ์ํ Attribute(์นผ๋ผ)๋ง ๋ฝ์์ค๋ ๊ฒ์ ๋งํ๋ค.
ํ๋ก์ ์
์ ํ๋ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ ๊ฒ ๊ฐ์๋ฐ ์ฌ๋ฌ ๊ธ์์ DTO ์์ฑ์๋ฅผ ์ด์ฉํ ํ๋ก์ ์
์ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ผ๋ก ๊ผฝ๋ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด ๋ฐฉ๋ฒ์ด ์๋ JPA์์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ ๊ฐ์ฅ ๋น์ทํ ๊ฐ๋ค.
ํ๋ก์ ์
์ ๋์์ด ๋ DTO๋ฅผ ์ ์ํ๋ค.
์ชผ๊ธ ๋ถํธํ ๋ถ๋ถ์ด ์ด DTO ํด๋์ค๋ฅผ QueryDsl์์ ํ๋ก์ ์
๋์ ํด๋์ค๋ก ์ฐ๋ ค๋ฉด ์ด ํด๋์ค์ ๋ํ Qํ์
ํด๋์ค๋ฅผ ์์ฑํด์ค์ผ ํ๋ค.
@QueryProjection
์ ์์ฑ์์ ์ถ๊ฐํด์ฃผ๊ณ Gradle tasks - other์ compile์ ๋ค์ ํด์ค์ผ ํ๋ค.
@NoArgsConstructor
@Data
public class UserDto {
private String username;
private int age;
private String teamName;
@QueryProjection // Qํ์
ํด๋์ค๋ก ๋ง๋ค๊ธฐ ์ํจ
public UserDto(String username, int age, String teamName) {
this.username = username;
this.age = age;
this.teamName = teamName;
}
}
JPA์์๋ DTO๋ก ์กฐํํ ๋ new ํค์๋์ ํด๋น DTO์ ํจํค์ง๋ฅผ ๋ชจ๋ ์ ์ด์ฃผ๋ ๋ฐฉ์์ด์๋ค.
Query dsl์์๋ ๋์ผํ๊ฒ new๋ฅผ ์ฌ์ฉํ์ง๋ง ํจํค์ง๋ ์ ์ด์ฃผ์ง ์์๋ ๋๋ค.
@Test
@DisplayName("9. ํ๋ก์ ์
- DTO์ ์์ฑ์ ์ฌ์ฉ")
void ํ๋ก์ ์
_DTO() {
List<UserDto> userDtos = query.select(new QUserDto(user.username, user.age, team.teamName))
.from(user)
.join(user.team, team)
.fetch();
userDtos.forEach(ut -> System.out.println(ut.getUsername() + ": "+ ut.getTeamName()));
}
ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฐ์ ๋ฐ์ดํฐ๊ฐ ์ฟผ๋ฆฌ์ ํฌํจ๋๋ ๋์ ์ฟผ๋ฆฌ๋ ๋งค์ฐ ์์ฃผ ์ฐ์ธ๋ค.
JPA์์ ์ด๋ฐ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ ๋ ๋ค์ด๋ฐ ์ฟผ๋ฆฌ๋ก ๋ณต์ก์ฑ์ ์ค์ผ ์ ์์ง๋ง ๋ค์ด๋ฐ ์ฟผ๋ฆฌ์ ๊ฒฝ์ฐ ํ๊ณ๊ฐ ์๋ค.
๋๋ฌธ์ ์ง์ JPQL์ ์ง๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ ์ด ๋ ์ฟผ๋ฆฌ๊ฐ ๋ชจ๋ ๋ฌธ์์ด๋ก ์์ฑ๋๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ์์ฒด๊ฐ ์กฐ๊ธ ๋ณต์กํด์ง๋ค๋ฉด JPQL ์์ฑ์ ๋๋๋๋ ์ด๋ ค์์ง๋ค. ์ด๋ ค์์ง๋ ๊ฒ ๋ฟ๋ง ์๋๋ผ ์๋ฌ์ ๊ฐ๋ฅ์ฑ ๋ํ ๋์์ง๋ค. ์ด๋ฐ ์๋ฌ๋ ์ปดํ์ผ ์์ ์ ์บ์น๋์ง ์๊ธฐ ๋๋ฌธ์ ๋ ๊ณ ์ํ ์ ์๋ค.
๋ง์ง๋ง์ผ๋ก Intellij๊ฐ ๋ถ์กฑํ ๋์ ๊ธธ์ก์ด๊ฐ ๋์ด์ค๋ค. ๐๐๐๐๐๐
์ด๋ฅผ ์ํด Querydsl์ ๋์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค.
์ด์ ๋ง Querydsl ์ชผ๊ธ ์๊ฒ ๋ ๊ฒ์ด์ง๋ง ์์ ํ๋ ๊ฒ๋ค์ ์์งํ JPA๊ฐ ๋ ํธํ์ง ์์๊น ์ถ๋ค. ํ์ง๋ง ๋์ ์ฟผ๋ฆฌ์ ๊ฒฝ์ฐ ํด๋ณด ๊ฒฐ๊ณผ ์ฅ์ ์ด ๋งค์ฐ ๋ง๋ค. ์ ๊ธฐ์ตํ๊ณ ํ์ฉํด๋ณด์.
@Test
@DisplayName("10. ๋์ ์ฟผ๋ฆฌ1")
void ๋์ ์ฟผ๋ฆฌ1() {
List<UserDto> userDtos = getUserByUsernameAndTeamName("user1", "team1");
userDtos.forEach(ut -> System.out.println(ut.getUsername() + ": "+ ut.getTeamName()));
assertEquals(1, userDtos.size());
}
private List<UserDto> getUserByUsernameAndTeamName(String username, String teamName) {
BooleanBuilder builder = new BooleanBuilder();
if (!Objects.isNull(username)) {
builder.and(user.username.eq(username));
}
if (!Objects.isNull(teamName)) {
builder.and(team.teamName.eq(teamName));
}
return query
.select(new QUserDto(user.username, user.age, team.teamName))
.from(user)
.join(user.team, team)
.where(builder)
.fetch();
}
BooleanBuilder
๋ฅผ ์ด์ฉํด์ ์กฐ๊ฑด์ ์๋๋ค.
BooleanBuilder
๋ฅผ ํตํด ๋ง๋ค์ด์ง๋ ๊ฒฐ๊ณผ๋ BooleanExpresson
์ด๊ณ ์ด ๊ฐ์ฒด๊ฐ where ์ ์ ์กฐ๊ฑด์ญํ ์ ํ ์ ์๋ค. ํ๋ผ๋ฏธํฐ์ ๋ํ ๊ฒ์ฆ์ ์ํํ๊ณ ํ๋ผ๋ฏธํฐ๊ฐ ์ ์์ด๋ผ๋ฉด Qํ์
์ธ์คํด์ค๋ฅผ ์ด์ฉํด์ ์กฐ๊ฑด์ ๊ตฌ์ฑํ๊ณ builder๋ฅผ where์ ์ ๋ฃ์ด์ค๋ค.
์ปดํ์ผ ์์ ์ ์๋ฌ๋ฅผ ์ก์๋ผ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋ค. ๊ทผ๋ฐ ์ข ์ง๋์น๊ฒ ๊ธธ์ด์ง๋๊ฒ ์๋๊ฐ ์ถ๋ค.
๋๋ฒ์งธ ์ฅ์ ์ด ๋์์ค์ผ ํ๋ค.
@Test
@DisplayName("11. ๋์ ์ฟผ๋ฆฌ2 ์กฐ๊ฑด ๋ฉ์๋๋ก ๋นผ๋ด๊ธฐ")
void ๋์ ์ฟผ๋ฆฌ2() {
List<UserDto> userDtos = query
.select(new QUserDto(user.username, user.age, team.teamName))
.from(user)
.join(user.team, team)
.where(ageGreaterThanEquals(20), teamNameEquals("team3")) // and๋ก ์ฐ๊ฒฐ
.fetch();
assertEquals(1, userDtos.size());
}
private BooleanExpression ageGreaterThanEquals(int age) {
return age <= 0 ? null : user.age.goe(age);
}
private BooleanExpression teamNameEquals(String teamName) {
return Objects.isNull(teamName) ? null : team.teamName.eq(teamName);
}
๊ตฌ์กฐ์ ๊ธฐ๋ฅ์ ์กฐ๊ธ ๋ฐ๋์์ง๋ง ๊ฐ๊ฐ ํ๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฌ๋ฐ๋ ๋ ๊ฐ ์กฐ๊ฑด์ ํฌํจํ๋ ๊ฒ์ ๋์ผํ๋ค.
์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ ์กฐ๊ฑด๋ฌธ ์์ฒด๋ฅผ ๋ฉ์๋ํ ํ๋ค๋ ๊ฒ์ด๋ค. ์ฌ์ฌ์ฉ์ ์ฌ์ง๊ฐ ์๊ฒผ๋ค!
wow