🏕️상황
//school 리스트를 반환하는 메소드
//school_tb : id와 schoolName, tag_tb : name(태그명), user_tb : schoolId를 counting. 총 3개의 테이블을 조인하여 반환
public List<SchoolInfoDTO> findSchoolInfoWithTagsAndUserCount() {
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
QSchool school = QSchool.school;
QTag tag = QTag.tag;
QUser user = QUser.user;
List<SchoolInfoDTO> schoolInfoList = queryFactory
.select(Projections.constructor(SchoolInfoDTO.class,
school.id, school.schoolName))
.from(school)
.leftJoin(tag).on(school.id.eq(tag.schoolId))
.leftJoin(user).on(school.id.eq(user.schoolId.id))
.groupBy(school.id, school.schoolName)
.fetch();
for (SchoolInfoDTO schoolInfo : schoolInfoList) {
List<String> tags = queryFactory
.select(tag.name)
.from(tag)
.where(tag.schoolId.eq(schoolInfo.getId()))
.fetch();
schoolInfo.setTag(tags);
Long userCount = queryFactory
.select(user.id.count())
.from(user)
.where(user.schoolId.id.eq(schoolInfo.getId()))
.fetchOne();
schoolInfo.setUserCount(userCount != null ? userCount.intValue() : 0);
}
return schoolInfoList;
}
- 위의 코드는 주석에 있는 것과 같이 학교 리스트를 반환할 때 프런트 측에서 원하는 정보를 반환하기 위해서 필수적으로 조인이 필수였고, 그 쿼리가 간단하지는 않기에 JPA의 기본 메서드나 JPQL을 사용하기보다는 좀 더 안정적인 Querydsl을 사용하기로 하였다.
- 아직 Querydsl의 원리나 방법에 대해 미숙하기 때문에 해당 코드를 분석하고, Querydsl에 대해 공부하는 시간을 가지려 한다. >>> https://grogrammer.tistory.com/51
🗝️코드분석
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
- JPAQueryFactory는 Querydsl을 사용하여 JPA 쿼리를 작성하기 위한 도구이다.
(uerydsl은 자바로 쿼리를 작성하기 위한 편리한 문법과 API를 제공) - 위 코드는 'entityManager' 을 기반으로 'JPAQueryFactory' 인스턴스를 생성.
- 'JPAQueryFactory' 생성자에는 'entityManager'이 필요.
'entityManager'는 JPA에서 엔티티의 영속성 관리와 데이터베이스와의 상호작용을 처리하는 핵심 클래스 - 'JPAQueryFactory' 는 'entityManager' 과 연동하여 JPA 쿼리를 실행하고 결과를 반환한다.
- 'JPAQueryFactory' 인스턴스를 생성한 후, 해당 인스턴스를 사용하여 Querydsl을 활용한 쿼리 작성을 수행하고
Querydsl의 다양한 기능과 메서드를 제공하여 쿼리를 구성하고 실행한다.
QSchool school = QSchool.school;
QTag tag = QTag.tag;
QUser user = QUser.user;
- 위 코드는 Querydsl을 사용하여 JPA 쿼리를 작성할 때, 엔티티와 속성에 접근하기 위해 해당 엔티티의 Q타입 클래스를 생성하는 과정이다.
- Q타입 클래스는 Querydsl의 핵심 개념으로, 쿼리 작성 시 타입 안정성을 제공하며 쿼리 작성의 편의성을 제공한다.
(아직 애매한 느낌이라 나만의 해석을 해보자면 "Q타입 클래스는 Querydsl 버전의 entitiy 클래스이다." 느낌?ㅎㅎ)
💛Q타입 클래스는 어떻게 생기는거고, 생성되는 원리는 무엇인가?
- Annotation Processor : Querydsl은 컴파일 시점에서 Annotation Processor를 활용하여 엔티티 클래스를 분석하고 Q타입 클래스를 생성한다. 엔티티 클래스에 지정된 Querydsl 관련 어노테이션을 확인하고 이를 바탕으로 Q타입 클래스를 생성한다. (Querydsl 관련 어노테이션 = entity 클래스에 있는 DB관련 어노테이션.)
- 메타모델 클래스 생성 : Annotation Processor는 엔티티 클래스의 에타데이터를 분석하여 해당 속성과 관계에 대한 정보를 수집한다. 이 정보를 바탕으로 Q타입 클래스의 필드와 메서드를 생성한다. 엔티티 클래스 이름에 "Q"접두사가 붙은 형태로 생성된다.
- 코드 생성 결과물 : 생성된 Q타입 클래스는 엔티티 클래스와 속성에 대한 정보를 담고 있으며, Querydsl 쿼리 작성 시 사용됩니다.
List<SchoolInfoDTO> schoolInfoList = queryFactory
.select(Projections.constructor(SchoolInfoDTO.class,
school.id, school.schoolName))
.from(school)
.leftJoin(tag).on(school.id.eq(tag.schoolId))
.leftJoin(user).on(school.id.eq(user.schoolId.id))
.groupBy(school.id, school.schoolName)
.fetch();
- 위의 코드는 Querydsl을 사용하여 'SchoolInfoDTO' 객체를 조회하는 과정이다.
- select(Projections.constructor(SchoolInfoDTO.class, school.id, school.schoolName))
Projections.constructor() 메서드를 사용하여 'SchoolInfoDTO'의 생성자를 지정하고 'SchoolInfoDTO'의 생성자에 school.id와 school.schoolName을 전달하여 객체를 생성한다. - from(school)
school 테이블을 기준으로 조회를 수행한다. - leftJoin(tag).on(school.id.eq(tag.schoolId))
school 테이블과 tag 테이블 조인된 결과를 가져올 수 있음. school.id와 tag.schoolId를 비교하여 조인조건 설정. - leftJoin(user).on(school.id.eq(user.schoolId.id))
school테이블과 user테이블의 조인 결과를 가져올 수 있음. school.id와 user.schoolId,id를 비교하여 조인조건 설정. - groupBy(school.id, school.schoolName)
school.id와 school.schoolName이 같은 결과들이 하나의 그룹으로 묶이게 된다. - fetch( )
쿼리를 실행하고 결과를 가지고 온다. fetch() 메서드를 호출하여 결과를 가져오기 전까지는 실제로 db에 접근하지 않는다.
💛Projections.constructor() 메서드란??
Projections.constructor() 메서드는 Querydsl에서 사용되는 투영(Projection)을 생성하는 메서드이다.
투영(Projection)을 생성하는 메서드로 투영은 쿼리 결과를 원하는 방식으로 변환하는 작업을 의미한다.
즉, 해당 클래스의 생성자에 전달된 인자를 사용하여 객체를 생성한다.
for (SchoolInfoDTO schoolInfo : schoolInfoList) {
List<String> tags = queryFactory
.select(tag.name)
.from(tag)
.where(tag.schoolId.eq(schoolInfo.getId()))
.fetch();
schoolInfo.setTag(tags);
Long userCount = queryFactory
.select(user.id.count())
.from(user)
.where(user.schoolId.id.eq(schoolInfo.getId()))
.fetchOne();
schoolInfo.setUserCount(userCount != null ? userCount.intValue() : 0);
}
- List<String> tags = queryFactory.select(tag.name).from(tag).where(tag.schoolId.eq(schoolInfo.getId())).fetch();
- tag 테이블에서 tag.name 필드를 조회하여 List<String>으로 가지고 온다.
- 조회하는 조건은 tag.schoolId가 schoolInfo.getId()와 동일한 경우이다.
근데 schoolInfo.getId()와 왜 비교를 할까?
schoolInfoDTO 객체와 관련된 태그를 조회해서 정보룰 가지고 위해 사용된다. - schoolInfo.setTag(tags)
위에 코드에서 조회한 태그 목록을 schoolInfo 객체에 넣어준다. - Long userCount = queryFactory.select(user.id.count()).from(user).where(user.schoolId.id.eq(schoolInfo.getId())).fetchOne();
- user 테이블에서 user.schoolId가 schoolInfo.getId() 와 동일한 레코드의 수를 조회한다.
- user.id.count() 를 사용하여 해당 조건을 만족하는 레코드 수를 카운트한다. - schoolInfo.setUserCount(userCount != null ? userCount.intValue() : 0)
- 조회한 사용자 수를 schoolInfo 객체에 set(넣어)해준다.
- schoolInfo 객체의 setUserCount() 메서드(별 다른 기능이 있는 건 아니고 그냥 setter임)를 사용하여 사용자 수를 설정한다. 🔜 userCount가 null인 경우 0으로 설정된다. - 이 코드는 전체적으로 SchoolInfoDTO 객체에 대해(이 DTO에는 내가 얻고자 하는 필드가 다 있으니까) 추가 정보를 조회하고 설정하는 작업을 수행한다.
school, tag, user 테이블에서 얻고자하는 값과 형태가 모두 다르니까 한 방에 한 번의 방식으로 해결하는 게 아니라, 테이블마다 조회하고자 하는 형식에 맞게 쿼리를 작성하고 반환값을 set 하는 것임,
💛 fetchOne()과 fetch()의 차이점은 무엇일까?
fetchOne():
- fetchOne() 메서드는 쿼리 결과에서 단일 값을 반환합니다.
- 결과가 없는 경우 null을 반환하거나, 결과가 여러 개인 경우 첫 번째 값을 반환합니다.
- 주로 단일 레코드 또는 단일 필드의 값을 조회할 때 사용됩니다.
fetch():
- fetch() 메서드는 쿼리 결과를 컬렉션 형태로 반환합니다.
- 결과를 리스트, 세트, 맵 등의 컬렉션 형태로 가져올 수 있습니다.
- 결과가 없는 경우 빈 컬렉션을 반환합니다.
- 주로 다중 레코드를 조회하거나, 여러 필드 값을 조회할 때 사용됩니다.

'개발일지 > 2023_한이음' 카테고리의 다른 글
[개발] Querydsl를 사용하여 원하는 정보 조회/반환하기(Refactoring) (0) | 2023.07.18 |
---|---|
[개발]🤦🏻♀️고민하기 - Querydsl 사용 시 클래스 구조(repository, service) (0) | 2023.07.17 |
[개발] spring boot - db 접속 정보 암호화, application.properties 암호화 (0) | 2023.07.11 |
[개발외]멘토링 진행 - 2023/07/10 (0) | 2023.07.11 |
[개발]🚨ERROR - cannot find symbol method value() (0) | 2023.07.10 |