Querydsl란? 🤔
Querydsl은 정적 타입을 사용하여 SQL과 같은 쿼리를 생성할 수 있도록 지원하는 라이브러리입니다. 즉, SQL 형식의 쿼리를 타입 세이프(Type-Safe)하게 작성할 수 있도록 DSL(Domain-Specific Language)을 제공하는 라이브러리입니다.
DSL이란?
DSL은 특정 도메인에서 발생하는 문제를 효과적으로 해결하기 위해 설계된 언어입니다. Querydsl을 사용하면, @Query를 통해 직접 JPQL 쿼리를 작성하는 대신, 컴파일 시점에 쿼리 오류를 잡을 수 있고, IDE의 코드 자동 완성 기능을 활용할 수 있습니다.
주의: Spring Boot 2에서 3으로 업그레이드하면서
javax패키지가jakarta로 변경되었습니다.
Querydsl 사용 방법
1. Querydsl-JPA 의존성 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
- Querydsl-JPA 버전을 명시한 뒤
:jakarta를 반드시 추가해야 합니다.
2. QClass 생성을 위한 annotationProcessor 추가
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
- QClass: 엔티티 클래스 속성과 구조를 설명하는 메타데이터 클래스로, 쿼리 조건을 타입 세이프하게 설정할 수 있도록 합니다.
3. build.gradle 설정
// Querydsl 설정
sourceSets {
main.java.srcDir "$buildDir/generated/querydsl"
}
configurations {
querydsl.extendsFrom compileClasspath
}
build.gradle에 Querydsl 설정을 추가하여 QClass가 제대로 생성되도록 합니다.
4. DB와 연동 설정
application.properties에 다음 코드를 작성합니다:
spring.application.name=QueryDsl-tutorial
spring.datasource.url=jdbc:mysql://localhost:3306/querydsl
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username={사용자 이름}
spring.datasource.password={비밀번호}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
logging.level.org.springframework.data.jpa.repository.query=DEBUG
logging.level.com.querydsl.jpa.impl.JPAQueryFactory=DEBUG
5. Entity 클래스 작성
예시로 User와 Post 엔티티를 정의합니다:
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
@OneToMany(mappedBy = "user")
@JsonManagedReference
private List<Post> posts;
}
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JsonBackReference
@JoinColumn(name = "user_id")
private User user;
}
6. JPAQueryFactory 빈 등록
JPAQueryFactory는 Querydsl에서 제공하는 주요 클래스로, JPA 쿼리를 빌드하는 역할을 합니다. 이를 빈으로 등록하기 위해 다음과 같은 설정 파일을 작성합니다:
@Configuration
public class QueryDSLConfiguration {
@PersistenceContext
EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
@PersistenceContext: Entity Manager를 주입받기 위해 사용합니다. JPA에서 영속성 컨텍스트를 관리하고, 트랜잭션과 CRUD 작업을 지원합니다.
7. Repository 작성
Querydsl을 사용하기 위해 세 가지 Repository 인터페이스를 작성해야 합니다:
- JpaRepository(Interface)
- CustomRepository(Interface)
- CustomRepository 구현체(Class)
JpaRepository 인터페이스:
@Repository
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
}
CustomRepository 인터페이스:
public interface UserRepositoryCustom {
List<User> findUsersByName(String name);
List<User> findUsersByNameAndEmail(String name, String email);
}
CustomRepository 구현체:
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public List<User> findUsersByName(String name) {
QUser user = QUser.user;
QPost post = QPost.post;
return queryFactory.select(user)
.from(user)
.leftJoin(user.posts, post).fetchJoin()
.where(user.name.eq(name))
.fetch();
}
@Override
public List<User> findUsersByNameAndEmail(String name, String email) {
QUser user = QUser.user;
QPost post = QPost.post;
BooleanBuilder builder = new BooleanBuilder();
if (name != null && !name.isEmpty()) {
builder.and(user.name.eq(name));
}
if (email != null && !email.isEmpty()) {
builder.and(user.email.eq(email));
}
return queryFactory.selectFrom(user)
.leftJoin(user.posts, post).fetchJoin()
.where(builder)
.fetch();
}
}
- BooleanBuilder는 동적 쿼리를 생성할 때 사용됩니다.
8. Service와 Controller 작성
Service 클래스:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional(readOnly = true)
public List<UserResponseDto> findUsersByName(String name) {
return userRepository.findUsersByName(name).stream().map(UserResponseDto::of).collect(Collectors.toList());
}
@Transactional(readOnly = true)
public List<UserResponseDto> findUsersByNameAndEmail(String name, String email) {
return userRepository.findUsersByNameAndEmail(name, email).stream().map(UserResponseDto::of).collect(Collectors.toList());
}
}
Controller 클래스:
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/users")
public List<UserResponseDto> getUsersByName(@RequestParam("name") String name) {
return userService.findUsersByName(name);
}
@GetMapping("/users/search")
public List<UserResponseDto> getUsersByNameAndEmail(@RequestParam(required = false) String name,
@RequestParam(required = false) String email) {
return userService.findUsersByNameAndEmail(name, email);
}
}
언제 Querydsl을 사용할까? 🧐
- 가독성 향상: 메서드 네이밍을 통해 쿼리 조건과 정렬 방식을 유추할 수 있습니다.
- 동적 쿼리 생성: 고정된 SQL 쿼리 대신 동적으로 조건을 추가하여 쿼리를 작성할 수 있습니다.
- 재사용성 향상: 메서드 분리를 통해 코드의 재사용성을 높일 수 있습니다.
- 타입 안전성: 런타임 에러를 방지하고 컴파일 타임에 쿼리 오류를 확인할 수 있습니다.
동적으로 조건문을 사용하거나 런타임 에러를 방지하고 싶을 때 Querydsl을 사용하는 것이 좋습니다. 다만, 1차 캐시의 장점을 누릴 수 없다는 점은 주의해야 합니다.
'🌱 Spring > Spring Data JPA & QueryDSL' 카테고리의 다른 글
| [Spring] Spring Data JPA (0) | 2024.10.16 |
|---|
