데이터 접근 기술 (6) - Querydsl
개요
이번 글은 데이터 접근 기술 시리즈의 마지막으로, Querydsl에 대한 글이다.
김영한 님의 스프링 DB 강의와 개인 공부를 통해 학습한 내용을 정리하고자 한다.
Spring Boot 3.2.2, JDK 17 버전이며 H2를 데이터베이스로 사용하니 참고하자.
- 데이터 접근 기술 (1) - 데이터 접근 기술의 종류
- 데이터 접근 기술 (2) - JdbcTemplate
- 데이터 접근 기술 (3) - MyBatis
- 데이터 접근 기술 (4) - JPA
- 데이터 접근 기술 (5) - Spring Data JPA
- 데이터 접근 기술 (6) - Querydsl
Querydsl이란?
Querydsl(Queryable Domain Specific Language)은 자바 기반의 오픈 소스 라이브러리로, 타입 안정성을 갖는 SQL 쿼리를 작성할 수 있도록 도와주며 JPA, Hibernate, SQL, MongoDB 등 다양한 데이터 소스에 대한 쿼리를 작성할 수 있도록 지원한다.
Querydsl은 말 그대로 도메인 특화 언어로서 SQL을 자바 코드로 표현할 수 있도록 한다. 따라서 SQL을 작성하며 발생할 수 있는 오타나 타입 불일치와 같은 문제를 사전에 방지할 수 있다.
또한 자바의 타입 시스템을 활용하여 쿼리를 작성할 수 있어 컴파일 시점에 오류를 찾아내고 IDE를 통해 코드 작성을 수월하게 할 수 있다는 장점도 있다.
아래와 같이 자바 코드로 동적 쿼리를 작성할 수 있다.
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
QItem item = QItem.item;
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.hasText(itemName)) {
builder.and(item.itemName.like("%" + itemName + "%"));
}
if (maxPrice != null) {
builder.and(item.price.loe(maxPrice));
}
return query //JPAQueryFactory
.select(item)
.from(item)
.where(builder)
.fetch();
}
이전 포스트들에서 살펴봤던 JPA와 Spring Data JPA는 객체 지향적 방식으로 데이터베이스를 다룰 수 있었지만 동적 쿼리를 처리하는 데에는 불편함이 있었다. 때문에 Querydsl을 함께 사용하여 동적 쿼리를 포함한 복잡한 쿼리를 작성하는 등 JPA 기술과 Querydsl을 조합하여 사용하는 방식을 택한다.
매우 복잡한 쿼리를 작성해야 한다면 JdbcTemplate이나 MyBatis를 함께 사용하는 등 프로젝트의 상황을 고려하여 사용할 기술을 결정하면 될 것이다.
기본 설정
1. build.gradle
dependencies {
//JPA, 스프링 데이터 JPA 사용
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//Querydsl 사용
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
//H2 데이터베이스 사용
runtimeOnly 'com.h2database:h2'
}
//Querydsl - 자동 생성된 Q클래스를 gradle의 clean으로 제거
clean {
delete file('src/main/generated')
}
이 설정은 Querydsl 플러그인을 사용하는 방법이며, 만약 오류가 발생한다면 dependencies 부분을 아래의 설정으로 변경하도록 한다.
dependencies {
//Querydsl 플러그인 사용X (gradlew로 빌드)
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
reload 후 Q 타입이 생성되었는지 확인이 필요하다.
Settings > Build, Execution, Deployment > Gradle에서 Build 옵션을 확인하자.
(IntelliJ IDEA 사용)

위와 같이 Gradle인 경우
Gradle > Tasks > build > clean
Gradle > Tasks > other > compileJava 를 차례로 클릭한다.
build.gradle에서 clean으로 Q 타입을 삭제할 수 있는 설정을 했기 때문에 필요한 경우 gradle의 clean으로 삭제가 가능하다.

컴파일이 완료되면 아래 경로로 Q 타입이 생성되며, Q 타입은 Entity로 등록된 객체를 토대로 만들어진다.

Build 옵션이 IntelliJ인 경우에는 Build > Build Project를 클릭한다.

빌드가 완료되면 아래 경로에 Q 타입이 생성된다.
만약 제대로 생성되지 않으면 Rebuild Project를 클릭하거나 `main()`을 실행한다.

Gradle 콘솔로 Q 타입을 확인하려면 아래와 같이 입력하면 된다.
./gradlew clean compileJava
📌 참고
Q 타입은 컴파일 시점에 자동 생성되므로 버전 관리(git)에 포함하지 않는 것이 좋다.
📌 참고
H2 데이터베이스 설치 및 실행 방법은 아래의 글을 참고하자.
https://hyunrian.tistory.com/88
Mac에서 H2 설치 및 실행하기
H2 Database는 간단한 개발이나 테스트 용도로 사용하기 좋은 DB이다. 이번 글에서는 맥에서 H2를 설치하고 실행하는 과정을 진행해보려 한다. H2 설치하기 아래의 링크로 들어가면 최신 버전을 OS에
hyunrian.tistory.com
2. application.properties
#DB 연결
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
#JPA log
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
- `logging.level.org.hibernate.SQL=DEBUG` : `Hibernate`가 생성하고 실행하는 SQL을 로그로 확인할 수 있음
- `logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE` : SQL에 바인딩되는 파라미터를 로그로 확인할 수 있음
코드 작성
간단한 CRUD 기능 구현을 위해 작성한 클래스는 아래와 같다.
- `User` : 도메인 객체(Entity)
- `UserRepository` : `JPARepository`를 인터페이스 상속받은 인터페이스
- `UserQueryRepository` : Querydsl을 사용하여 동적 쿼리를 수행하는 클래스
- `UserService` : 서비스 계층
- `UserSearchCondition` : 데이터 조회 시의 검색어를 필드로 갖고 있는 검색 조건 객체
- `UserUpdateDto` : update에서 사용할 데이터 객체
Spring Data JPA와 Querydsl을 함께 적용하였다.
이 중에서 중요하지 않은 몇 가지 클래스는 코드를 생략하고 핵심이 되는 네 개만 살펴보겠다.
📌 참고
데이터베이스(H2)에 테이블은 아래와 같이 생성해 두었다.
create table users (
id bigint generated by default as identity,
user_name varchar(10),
phone_num varchar(25),
primary key (id)
);
1. `User`
@Data
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", length = 10)
private String userName;
private String phoneNum;
public User(String userName, String phoneNum) {
this.userName = userName;
this.phoneNum = phoneNum;
}
}
- JPA는 `public` 또는 `protected`인 기본 생성자가 반드시 필요함
- `@Table` : 데이터베이스의 테이블명을 지정하며 객체명과 같으면 생략이 가능함
- `@Id` : 식별자(primary key)로 사용하는 경우 지정함. `jakarta.persistence` 패키지 내에 있는 애너테이션을 사용함
- `@GeneratedValue`
- `Entity`의 식별자 값을 자동으로 생성하는 전략을 지정할 수 있음
- 주로 데이터베이스의 자동 증가(auto increment) 기능과 연동하여 사용함
- `GenerationType.IDENTITY` : 데이터베이스의 자동 증가 컬럼 사용(MySQL, PostgreSQL 등)
- `GenerationType.SEQUENCE` : 데이터베이스의 시퀀스 사용(Oracle 등)
- `GenerationType.TABLE` : 별도의 테이블을 사용하여 식별자 생성
- `GenerationType.AUTO` : 특정 데이터베이스에 맞게 자동으로 전략 선택
- `@Column`
- 테이블의 컬럼명과 길이 등을 지정할 수 있으며 필드명과 컬럼명이 동일한 경우 생략할 수 있음
- 데이터베이스 컬럼명이 user_name(`snake_case`)이고 객체의 필드명이 userName(`camelCase`)인 경우 자동으로 변경되며, 이 경우에도 생략 가능함
2. `UserRepository`
public interface UserRepository extends JpaRepository<User, Long> {}
- Spring Data JPA 적용
3. `UserQueryRepository`
@Repository
public class UserQueryRepository {
private final JPAQueryFactory query; //Querydsl을 사용할 때 필요함
public UserQueryRepository(EntityManager em) {
this.query = new JPAQueryFactory(em);
}
public List<User> findAll(UserSearchCondition condition) {
String userName = condition.getUserName();
return query
.select(user) //QUser.user -> user (static import)
.from(user)
.where(likeUserName(userName))
.fetch();
}
private BooleanExpression likeUserName(String userName) {
if (StringUtils.hasText(userName)) {
return user.userName.like("%" + userName + "%");
}
return null;
}
}
- `EntityManager`를 주입하여 `JPAQueryFactory` 생성
- 유저명으로 검색하는 검색 조건이 포함된 전체 조회 기능 구현
- 조건이 여러개인 경우 `where(method1(), method2(), ...)`로 작성하면 and 조건으로 처리되며, 값이 `null`이면 해당 조건을 무시함
- 포스트의 위쪽에서 봤던 예시처럼 `BooleanBuilder`를 사용해도 됨
4. `UserService`
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
private final UserRepository userRepository;
private final UserQueryRepository queryRepository;
public User save(User user) {
return userRepository.save(user);
}
public void update(Long id, UserUpdateDto updateDto) {
User user = userRepository.findById(id).orElseThrow();
user.setUserName(updateDto.getUserName());
user.setPhoneNum(updateDto.getPhoneNum());
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public List<User> findAll(UserSearchCondition condition) {
return queryRepository.findAll(condition);
}
public void delete(Long id) {
userRepository.deleteById(id);
}
}
- `save()`, `findById()`, `deleteById()`는 Spring Data JPA가 제공하는 기능을 사용. `UserRepository`에서 처리
- 검색 조건에 따라 where절을 동적으로 추가해야 하는 부분만 `UserQueryRepository`에서 처리
Spring Data JPA가 지원하는 CRUD와 관련한 기본 제공 메서드는 공식 문서를 참고하자.
CrudRepository (Spring Data Core 3.2.2 API)
All Superinterfaces: Repository All Known Subinterfaces: ListCrudRepository Interface for generic CRUD operations on a repository for a specific type. Author: Oliver Gierke, Eberhard Wolff, Jens Schauder Method Summary All MethodsInstance MethodsAbstract M
docs.spring.io
참고
- 스프링 DB 2편 - 데이터 접근 활용 기술 (김영한)
- Spring Data JPA 공식 문서
- [Spring] QueryDsl gradle 설정 (Spring boot 3.0 이상) - Kai
'Database' 카테고리의 다른 글
| Mac에서 H2 설치 및 실행하기 (0) | 2024.01.18 |
|---|---|
| 데이터 접근 기술 (5) - Spring Data JPA (0) | 2024.01.01 |
| 데이터 접근 기술 (4) - JPA (0) | 2023.12.18 |
| 데이터 접근 기술 (3) - MyBatis (0) | 2023.12.09 |
| 데이터 접근 기술 (2) - JdbcTemplate (0) | 2023.12.04 |
Liked this Posting!