Person(Entity)
@Entity
class Person(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
@Column(name = "name")
var name: String? = null,
@Column(name = "age")
var age: Int? = null,
@Column(name = "job")
var job: String? = null
)
PersonRepository, PersonCustomRepostiory
interface PersonRepository: JpaRepository<Person, Long>, PersonCustomRepository {
// JPQL 예제용
@Query("select p.id from Person p where p.age between :from and :to")
fun findIdByAgeBetween(@Param("from") from: Int, @Param("to") to: Int)
}
interface PersonCustomRepository {
fun findPeople(): MutableList<Person>
}
PersonCustomRepositoryImpl
import com.example.querydsl.domain.QPerson.*
@Repository
@Transactional
class PersonCustomRepositoryImpl(
private val jpaQueryFactory: JPAQueryFactory
): PersonCustomRepository {
override fun findPeople(): MutableList<Person> {
return jpaQueryFactory
.selectFrom(person)
.fetch()
}
}
Custom 키워드를 사용해 Repostiory를 만들었는데 이는 Spring Data JPA도 사용하고 Querydsl 둘 다 사용할 때 보통 이 구조로 구성한다. 이 구조는 Spring Data JPA 공식 문서에 나온다.
Querydsl Repository, Spring Data JPA Repository 두 개가 분리되어 있고 서로 상속/구현 관계가 맺어져 있지 않으면 두 개의 Repository가 필요한 Service 파일에 의존성을 둘 다 주입해야 한다.
하지만 위 구조로 구성한다면 하나의 Repository를 의존성 주입(DI)해도 두 가지 Repository 기능을 사용할 수 있다.
JpaQueryFactory를 사용해 쿼리를 구현한다.
위 예제에서 person
은 QPerson.person
인데 QPerson.*
을 static import
해서 person
으로 사용할 수 있다.
.selectFrom(person)
은 .select(person).from(person)
과 같다.
.selectFrom(person)
은 Person 엔티티의 모든 컬럼을 조회하는 것이고, select
와 from
을 분리해서 쓰면 select
의 인자에 우리가 조회할 컬럼 하나 또는 필요한 컬럼들을 DTO로 변환해 조회할 수 있다. DTO로 변환하는 방법은 나중에 따로 게시글을 작성할 예정이다.
(Kotlin 프로젝트 기준)
Q파일이 생성되지 않았다면 gradle의compileKotlin
을 실행해서 생성시키자.
위 예제에서 findPerson()
메서드는 Person 엔티티의 모든 사람들을 조회하는 메서드다.
따라서 리턴 타입을 MutableList<Person>
으로 받았다. 참고로 Kotlin에서는 앞에 Mutable이 붙은 컬렉션은 mutable 하고 앞에 Mutable이 붙지 않은 컬렉션은 immutable 하다. 위 예제에서 List나 MutableList나 둘 중 아무거나 사용해도 무방하다.
결과 조회
-
fetch()
는 리스트 조회할 때 사용되고 데이터가 없으면 빈 리스트를 반환한다. -
fetchOne()
은 한 건을 조회할 때 사용되고 데이터가 없으면 null을 반환한다. 한 건 조회인데 결과가 두 개 이상이라면com.querydsl.core.NonUniqueResultException
을 발생시킨다.
이외에도 fetchFirst()
, fetchResults()
, fetchCount()
가 있다.
-
fetchFirst()
는 첫 번째 한 건을 조회할 때 사용된다. -
fetchResults()
는 페이징 정보를 포함하고 total count 쿼리를 추가로 실행한다. -
fetchCount()
는 count 쿼리로 변경해서 count 수를 조회한다.
하지만 fetchResults()
와 fetchCount()
는 deprecated 되었다.
테스트 코드를 만들어 위 예제를 테스트 해보자.
@SpringBootTest
class Test {
@Autowired
private lateinit var personRepository: PersonRepository
@Test
fun findPeopleTest() {
// given
val person1 = Person(1, "person1", 20, "backend-programmer")
val person2 = Person(2, "person1", 25, "frontend-programmer")
// when
personRepository.saveAll(mutableListOf(person1, person2))
// then
assertThat(personRepository.findPeople().size).isEqualTo(2)
assertThat(personRepository.findPeople().first().id).isEqualTo(person1.id)
assertThat(personRepository.findPeople().first().name).isEqualTo(person1.name)
assertThat(personRepository.findPeople().first().age).isEqualTo(person1.age)
assertThat(personRepository.findPeople().first().job).isEqualTo(person1.job)
}
}
조건 (where)
위 예제는 조건 없이 조회해서 코드에서 where절을 볼 수 없었다.
where절도 간단하다.
override fun findPeopleByAge(age: Int): MutableList<Person> {
return jpaQueryFactory
.selectFrom(person)
.where(person.age.eq(20))
.fetch()
}
나이가 20살인 사람들을 조회하는 쿼리다.
eq()
메서드를 사용해 인자값과 같은 사람을 조회한다. 테스트 코드로 확인해보자.
@Test
fun findPeopleByAgeTest() {
// given
val person1 = Person(1, "person1", 20, "backend-programmer")
val person2 = Person(2, "person1", 25, "frontend-programmer")
// when
personRepository.saveAll(mutableListOf(person1, person2))
// then
assertThat(personRepository.findPeopleByAge(20).size).isEqualTo(1)
assertThat(personRepository.findPeopleByAge(20).first().id).isEqualTo(person1.id)
assertThat(personRepository.findPeopleByAge(20).first().name).isEqualTo(person1.name)
assertThat(personRepository.findPeopleByAge(20).first().job).isEqualTo(person1.job)
}
eq()
이외에도
gt()
: 큰지goe()
: 크거나 같은지lt()
: 작은지loe()
: 작거나 같은지between()
: 사이 범위에 있는지like()
: %를 사용해 해당 위치에 부합하는지person.name.like("김%")
: 성이 김씨인지
contains()
: ~에 포함되었는지like("%문자열%")
와 같다.
isNull()
: null인지isNotNull()
: null이 아닌지and()
: 그리고- 콤마(
,
)로 대체 가능하다. person.age.eq(20).and(person.job.eq("backend-programmer"))
person.age.eq(20), person.job.eq("backend-programmer")
- 콤마(
or()
: 또는
등등… 엄청 많이 있다. 필요한 부분은 검색해보면서 적용해보도록 하자!