QueryDSL은 하이버네이트 쿼리 언어(HQL: Hibernate Query Language)의 쿼리를 타입에 안전하게 생성 및 관리해주는 프레임워크이다. QueryDSL은 정적타입을 이용하여 SQL과 같은 쿼리를 장성 할 수 있게 해준다.
QueryDSL은 Spring Data JPA의 단점과 기존의 MyBatis, JPQL의 단점을 해결해준다,
Spring Data JPA는 복잡한 쿼리 작성이 어렵고 MyBatis, JPQL은 쿼리작성을 해도 쿼리에 에러가 있음을 프로젝트를 실행시키고 직접 확인해야 알 수 있다.
QueryDSL은 쿼리문을 코드로 작성하기 때문에 컴파일 단계에서 에러를 확인 할 수 있고 복잡한 쿼리나 동적 쿼리 작성이 편리하다.
(해당 예시는 Spring Boot로 작성했고 빌드도구는 gradle로 했다.)
build.gradle 파일
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' // QueryDSL 목적
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" // Qclass 생성
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api" // JPA 어노테이션
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
annotationProcessor 'org.projectlombok:lombok'
}
application.properties 파일
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
entity 파일
@Entity
@Getter
@NoArgsConstructor
@Table(name="member")
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private int age;
}
QueryDSL 설정파일
@Configuration
public class querydslConfig {
@PersistenceContext // 영속성 컨텍스트에 추가
private EntityManager em; // EntityManager는 JPA에서 엔터티의 생성, 조회, 수정 삭제를 수행
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em){
// 쿼리를 작성하는 JPAQueryFactory에 EntityManager를 넘겨 사용한다.
return new JPAQueryFactory(em);
}
}
JPAQueryFactory는 QueryDSL에서 제공하는 주요 클래스 중 하나이다. 해당 Config 파일은 JPAQueryFactory를 QueryDSL을 이용한 JPA 쿼리를 빌드하는 Factory 역할로 Bean으로 등록하여 사용하는데 장점이 많아서 제일 많이 사용하는 방법이다.
사용하기전에 있어서 QClass란 무엇인가를 알아볼 필요가있다.
상단의 이미지는 entity 클래스를 빌드 했을 때 생성되는 QClass이다. QClass는 타입 세이프(Typed-Safe) 쿼리를 생성하기 위해 자동으로 생성되는 클래스이다.
타입 세이프(Typed-Safe)
프로그래밍 언어의 특성을 나타내는 용어로 프로그램이 실행되는 동안(런타임) 데이터의 타입을 체크하여 타입 오류를 방지하는 것을 의미한다. 잘못된 타입의 데이터를 사용하려고 시도하면 컴파일 오류나 런타임 오류가 발생하여 버그를 방지한다.
QueryDSL의 장점인 컴파일할 때 쿼리오류를 발견한다는 QClass에서 나오는 것 이라고해도 과언이 아니다.
기본 CRUD 메서드
@Repository
public class memberRepository{
@Autowired
private JPAQueryFactory queryFactory;
// 특정 컬럼만 선택하여 조회
@Transactional
public List<memresultDTO> searchAll() {
List<memresultDTO> result = queryFactory
.select(Projections.constructor(memresultDTO.class,
member.name,
member.age
))
.from(member)
.fetch();
return result;
}
// 엔터티에 작성된 컬럼 1개만 조회
@Transactional
public Member searchOne(int seq) {
return queryFactory.selectFrom(member)
.where(member.seq.eq(seq))
.fetchOne();
}
// 엔터티에 row를 추가한다.
@Transactional
public long insert() { // execute 리턴값 타입이 long이다.
return queryFactory.insert(member)
.columns(member.age,member.name)
.values(12,"tester11")
.execute();
}
// 조건에 해당하는 row를 업데이트한다.
@Transactional
public long update() {
return queryFactory.update(member)
.set(member.age, 22)
.where(member.age.eq(12))
.execute();
}
// 조건에 해당하는 row를 삭제한다.
@Transactional
public long delete() {
return queryFactory.delete(member)
.where(member.age.eq(12))
.execute();
}
}
insert, update, delete는 리턴을 long으로 했는데 excute()메서드 내부를 보면
long 타입인 것을 확인할 수 있다. 만약 결과값을 출력한다면
몇 건을 추가하고 수정했고 삭제했는지 출력이가능하다.
select 결과값 조회 메서드
fetchOne()과 fetchFirst()는 결과 1개만 출력한다는 점에서 같지만
queryFactory.selectFrom(member).fetchFirst();
queryFactory.selectFrom(member).fetchOne();
두 코드를 비교해보면 fetchOne은 조회시 단일 건이 아니라서 NonUniqueResultException이 예외로 발생되지만 fetchFirst는 조회된 것중에 첫번 째 값만 가져오기 때문에 첫번 째에 해당하는 row가 출력된다.
정리
메서드 | 반환 타입 | 다수 결과 경우 | 결과 없는 경우 | 사용하는 경우 |
---|---|---|---|---|
fetch() | List<T> | 조건에 해당하는 값 전체조회 | 빈 List 반환 | 다량의 값을 조회 할 때 사용 |
fetchOne() | T | NonUniqueResultException발생 | null 반환 | 반드시 단일 결과일 경우 사용 |
fetchFirst() | T | 조건에 해당하는 첫 번째 값 조회 | null 반환 | 첫번 째 값만 조회할 경우 사용 |
https://ittrue.tistory.com/292
https://sjh9708.tistory.com/174