Querydsl - low-hill/Knowledge GitHub Wiki
Querydsl์ Hibernate์ ๊ฐ์ ORM ํ๋ ์์ํฌ์ ํ์ ์์ ์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ํ๋ฅญํ ๋์์ ๋๋ค. ๋ฉํ๋ชจ๋ธ ํด๋์ค๋ฅผ ํ์ฉํ์ฌ ํ์ ์์ ํ ๋ฐฉ์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ฉด์๋, ์ฝ๊ธฐ ์ฝ๊ณ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๊ฒ ๋์์ค๋๋ค. Querydsl๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ๋ฐ์๋ ๋ ์์ ํ๊ณ ํจ์จ์ ์ธ ์ฟผ๋ฆฌ ์์ฑ์ด ๊ฐ๋ฅํด์ง๋ฉฐ, ๋ณต์กํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ ๋ ์ง๊ด์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
๊ฐ์ฒด ๊ด๊ณ ๋งคํ(Object-Relational Mapping, ORM) ํ๋ ์์ํฌ๋ ์ํฐํ๋ผ์ด์ฆ ์๋ฐ์ ํต์ฌ์ ๋๋ค. ORM์ ๊ฐ์ฒด ์งํฅ ์ ๊ทผ ๋ฐฉ์๊ณผ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ ๊ฐ์ ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๋ฉฐ, ๊ฐ๋ฐ์๊ฐ ๋ ๊น๋ํ๊ณ ๊ฐ๊ฒฐํ ์์์ฑ ์ฝ๋ ๋ฐ ๋๋ฉ์ธ ๋ก์ง์ ์์ฑํ ์ ์๊ฒ ๋์ต๋๋ค.
ํ์ง๋ง ORM ํ๋ ์์ํฌ์์ ๊ฐ์ฅ ์ค์ํ ์ค๊ณ ์์ ์ค ํ๋๋ ์ฌ๋ฐ๋ฅด๊ณ ํ์ ์์ ํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ API์ ๋๋ค.
๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์๋ฐ ORM ํ๋ ์์ํฌ์ธ Hibernate (๊ทธ๋ฆฌ๊ณ ๋ฐ์ ํ๊ฒ ์ฐ๊ด๋ JPA ํ์ค)๋ SQL๊ณผ ๋งค์ฐ ์ ์ฌํ ๋ฌธ์์ด ๊ธฐ๋ฐ์ ์ฟผ๋ฆฌ ์ธ์ด์ธ HQL(๋๋ JPQL)์ ์ ๊ณตํฉ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ช ๋ฐฑํ ๋จ์ ์ ํ์ ์์ ์ฑ ๋ถ์กฑ๊ณผ ์ ์ ์ฟผ๋ฆฌ ๊ฒ์ฆ์ ๋ถ์ฌ์ ๋๋ค. ๋ํ, ๋ ๋ณต์กํ ์ฟผ๋ฆฌ์์๋ (์๋ฅผ ๋ค์ด, ์คํ ์์ ์ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ฟผ๋ฆฌ๋ฅผ ๋์ ์ผ๋ก ๊ตฌ์ฑํด์ผ ํ ๋) ๋ฌธ์์ด ์ฐ๊ฒฐ์ ์ฌ์ฉํด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐฉ์์ด ์ผ๋ฐ์ ์ผ๋ก ๋งค์ฐ ๋ถ์์ ํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค.
JPA 2.0 ํ์ค์์ Criteria Query API๊ฐ ๋์
๋์ด ๊ฐ์ ๋์์ต๋๋ค. ์ด API๋ ๋ฉํ๋ชจ๋ธ ํด๋์ค๋ฅผ ํ์ฉํด ํ์
์์ ํ ๋ฐฉ์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ ์๋ก์ด ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ผ๋, ์ง๋์น๊ฒ ์ฅํฉํ๊ณ ์ฝ๊ธฐ ์ด๋ ค์ด ์ฝ๋**๋ฅผ ์์ฑํ๊ฒ ๋์์ต๋๋ค. ์๋ฅผ ๋ค์ด, Jakarta EE ํํ ๋ฆฌ์ผ์์ SELECT p FROM Pet p
์ ๊ฐ์ ๊ฐ๋จํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๊ฐ ํ์ํฉ๋๋ค:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery<Pet> query = entityManager.createQuery(cq);
List<Pet> result = query.getResultList();
์์ ๊ฐ์ ์ฝ๋๊ฐ ๊ธธ๊ณ ๋ณต์กํ๊ฒ ๋๊ปด์ง๋ ๊ฒ์ ๋น์ฐํ ์ผ์ ๋๋ค. ์ด์ ๋ฐ๋ผ, ์์ฑ๋ ๋ฉํ๋ฐ์ดํฐ ํด๋์ค๋ฅผ ํ์ฉํ๋ฉด์๋ ๋ ์ง๊ด์ ์ด๊ณ ์ฝ๊ธฐ ์ฌ์ด API๋ฅผ ์ ๊ณตํ๋ Querydsl ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ฑ์ฅํ์ต๋๋ค. Querydsl์ ๋ฐ๋ก ์ด ์์ด๋์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ, ์ง๊ด์ ์ด๊ณ ๊ฐ๋ ์ฑ ์ข์ API๋ก ์ฟผ๋ฆฌ ์์ฑ ๋ฐฉ์์ ํ์ ์ ์ผ๋ก ๊ฐ์ ํ์์ต๋๋ค.
์ด์ Querydsl์ API๋ฅผ ๊ตฌํํ๋ ๋ฉํํด๋์ค๋ฅผ ์์ฑํ๊ณ ์กฐํํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ด ๋๋ค.
Querydsl์ ํ๋ก์ ํธ์ ํฌํจํ๊ธฐ ์ํด์๋ Maven pom.xml ํ์ผ์ ๋ช ๊ฐ์ง ์์กด์ฑ์ ์ถ๊ฐํ๊ณ , JPA ์ด๋ ธํ ์ด์ ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ํ๋ฌ๊ทธ์ธ์ ์ค์ ํ๋ฉด ๋ฉ๋๋ค. ๋จผ์ Querydsl ๋ฒ์ ์ ์น์ ์ ์ ์ํฉ๋๋ค. ์ด ๋ถ๋ถ์ Querydsl์ ์ต์ ๋ฒ์ ์ Maven Central์์ ํ์ธํ ํ ์ค์ ํ ์ ์์ต๋๋ค.
<properties>
<querydsl.version>5.0.0</querydsl.version>
</properties>
๋ค์์ผ๋ก, pom.xml ํ์ผ์ ์น์ ์ ์๋์ ๊ฐ์ ์์กด์ฑ์ ์ถ๊ฐํฉ๋๋ค:
<dependencies>
<!-- Querydsl APT (Annotation Processing Tool) ์์กด์ฑ -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<classifier>jakarta</classifier>
<scope>provided</scope>
</dependency>
<!-- Querydsl JPA ์์กด์ฑ -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<classifier>jakarta</classifier>
<version>${querydsl.version}</version>
</dependency>
</dependencies>
querydsl-apt ์์กด์ฑ์ JPA ์ด๋ ธํ ์ด์ ์ ์ฒ๋ฆฌํ๋ ํด๋ก, ์์ค ํ์ผ์ ์ปดํ์ผ ์ ์ ์ฒ๋ฆฌํ์ฌ Q-ํ์ ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฅผ ๋ค์ด, User ํด๋์ค๊ฐ @Entity ์ด๋ ธํ ์ด์ ์ผ๋ก ์ ์๋์ด ์์ผ๋ฉด, ์์ฑ๋๋ Q-ํ์ ํด๋์ค๋ QUser.java ํ์ผ๋ก ์์ฑ๋ฉ๋๋ค.
์ด์ , apt-maven-plugin ํ๋ฌ๊ทธ์ธ์ ์ค์ ํ์ฌ ๋น๋ ๊ณผ์ ์์ Q-ํ์ ํด๋์ค๋ฅผ ์๋์ผ๋ก ์์ฑํ๋๋ก ํฉ๋๋ค.
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
์ด ํ๋ฌ๊ทธ์ธ์ Maven ๋น๋ ์ค process ๋จ๊ณ์์ Q-ํ์ ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค. outputDirectory๋ ์์ฑ๋ ํด๋์ค๊ฐ ์์นํ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ๋ ์ค์ ์ ๋๋ค.
๋ค์์ผ๋ก, ์์ฑ๋ ์์ค ํ์ผ์ IDE์์ ์ธ์ํ ์ ์๋๋ก ํด๋น ํด๋๋ฅผ ์์ค ํด๋๋ก ์ถ๊ฐํด์ผ ํฉ๋๋ค.
์์ ๋ชจ๋ธ๋ก, User์ BlogPost ์ํฐํฐ๊ฐ ์๋ ๊ฐ๋จํ JPA ๋ชจ๋ธ์ ์ฌ์ฉํฉ๋๋ค:
@Data
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String login;
private Boolean disabled;
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
private Set<BlogPost> blogPosts = new HashSet<>(0);
}
@Data
@Entity
public class BlogPost {
@Id
@GeneratedValue
private Long id;
private String title;
private String body;
@ManyToOne
private User user;
}
์ Entity๋ก Q-ํ์ ํด๋์ค๋ฅผ ์์ฑํ๋ ค๋ฉด, ๋น๋ ๋ช ๋ น์ด๋ฅผ ์คํํฉ๋๋ค:
mvn compile
target/generated-sources/java ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ๋ฉด, ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ๋์ผํ ๊ตฌ์กฐ์ ํจํค์ง์ ํด๋์ค๊ฐ ์์ฑ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ค๋ง, ๋ชจ๋ ํด๋์ค ์ด๋ฆ์ Q๋ก ์์ํฉ๋๋ค (QUser, QBlogPost ๋ฑ).
QUser.java ํ์ผ์ ์ด์ด๋ณด๋ฉด, ์ด ํด๋์ค๊ฐ User ์ํฐํฐ์ ๊ด๋ จ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ์ด ํ์ผ์์ ๋์ ๋๋ ์ ์ @Generated ์ด๋ ธํ ์ด์ ์ด ๋ถ์ด ์๋ค๋ ์ ์ ๋๋ค. ์ด๋ ์ด ํ์ผ์ด ์๋์ผ๋ก ์์ฑ๋์์ผ๋ฉฐ ์๋์ผ๋ก ์์ ํ์ง ๋ง์์ผ ํ๋ค๋ ๋ป์ ๋๋ค. ๋๋ฉ์ธ ๋ชจ๋ธ ํด๋์ค๋ฅผ ์์ ํ ํ์๋ mvn compile์ ๋ค์ ์คํํ์ฌ ๊ด๋ จ๋ ๋ชจ๋ Q-ํ์ ์ ์ฌ์์ฑํด์ผ ํฉ๋๋ค.
๋ํ, QUser ํด๋์ค์์ ์ฃผ๋ชฉํ ์ ์ ๋ค์๊ณผ ๊ฐ์ ์ ์ ์ธ์คํด์ค์ ๋๋ค:
public static final QUser user = new QUser("user");
์ด ์ธ์คํด์ค๋ ๋๋ถ๋ถ์ Querydsl ์ฟผ๋ฆฌ์์ User ์ํฐํฐ๋ฅผ ์ฐธ์กฐํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ๋ฌ ํ ์ด๋ธ์ ์กฐ์ธํ๋ ๋ณต์กํ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ๋ ์ ์ธํ๊ณ ๋ ์ด ์ธ์คํด์ค๋ฅผ ํ์ฉํฉ๋๋ค.
๊ฐ ์ํฐํฐ ํด๋์ค์ ํ๋๋ง๋ค ํด๋นํ๋ *Path ํ๋๊ฐ ์์ฑ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, QUser ํด๋์ค์๋ NumberPath id, StringPath login, SetPath blogPosts์ ๊ฐ์ ํ๋๊ฐ ์์ฑ๋๋ฉฐ, ์ด๋ ์ฟผ๋ฆฌ์์ ํด๋น ํ๋๋ฅผ ์ฐธ์กฐํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. ์ด ํ๋๋ค์ ๋์ค์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ fluent fluent API์ ์ผ๋ถ๊ฐ ๋ฉ๋๋ค.
์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ค๋ฉด ๋จผ์ JPAQueryFactory ์ธ์คํด์ค๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. JPAQueryFactory๋ ๊ธฐ๋ณธ์ ์ผ๋ก EntityManager๋ฅผ ํ์๋ก ํ๊ณ , ์ด EntityManager๋ EntityManagerFactory.createEntityManager() ํธ์ถ์ด๋ @PersistenceContext ์ฃผ์ ์ ํตํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro");
EntityManager em = emf.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(JPQLTemplates.DEFAULT, em);
QUser user = QUser.user;
User c = queryFactory.selectFrom(user)
.where(user.login.eq("David"))
.fetchOne();
์ ์ฝ๋์์ QUser๋ QUser.user(ํด๋์ค์ ์ ์ ์ธ์คํด์ค)๋ก ์์ฑ๋ฉ๋๋ค. selectFrom() ๋ฉ์๋๋ ์ฟผ๋ฆฌ๋ฅผ ๋น๋ํ๋ ์ฒซ ๋ฒ์งธ ๋จ๊ณ์ ๋๋ค. where() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์กฐ๊ฑด์ ์ถ๊ฐํ๋ฉฐ, user.login์ QUser ํด๋์ค์ StringPath ํ๋๋ฅผ ์ฐธ์กฐํฉ๋๋ค. StringPath๋ eq() ๋ฉ์๋๋ฅผ ํตํด ํ๋ ๊ฐ๊ณผ์ ๋น๊ต๋ฅผ ํ ์ ์๊ฒ ํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก fetchOne() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฒฐ๊ณผ๋ฅผ ์กฐํํฉ๋๋ค. ์ด ๋ฉ์๋๋ ์กฐ๊ฑด์ ๋ง๋ ๊ฐ์ฒด๊ฐ ์์ผ๋ฉด null์ ๋ฐํํ๊ณ , ๋ง์ฝ ์กฐ๊ฑด์ ๋ง๋ ๊ฐ์ฒด๊ฐ ์ฌ๋ฌ ๊ฐ ์์ผ๋ฉด NonUniqueResultException ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค.
Reference