이전글 Querydsl 사용해보자. ( 설정편 )에서 설정하는 방법을 알아보았습니다.
Entity 관계 매핑되있는 경우는Querydsl 사용해보자. ( Entity 관계 매핑 편, 테스트 코드 포함 )참조해주세요.
이번 글에서는 Querydsl의 버전업으로 Entity 관계 매핑되어있지 않아도 사용가능해졌기 때문에 Querydsl 사용편에 대해서 알아봅시다.
모든 소스는 github에 올려두었습니다.
Entity
package com.example.querydsl.staff.entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Staff {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
@Column(name = "store_id")
private Long storeId;
@Builder
public Staff(Long id, String name, Integer age, Long storeId) {
this.id = id;
this.name = name;
this.age = age;
this.storeId = storeId;
}
}
package com.example.querydsl.store.entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Store {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
@Builder
public Store(Long id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
}
Repository
JPA Repository와 동일하게 사용합니다.
package com.example.querydsl.store.repository;
import com.example.querydsl.store.entity.Store;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StoreRepository extends JpaRepository<Store, Long> {
Store findByName(String name);
}
package com.example.querydsl.staff.repository;
import com.example.querydsl.staff.entity.Staff;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StaffRepository extends JpaRepository<Staff, Long> {
}
RepositorySupport
Querydsl을 이용하여 실제 조회할 쿼리를 작성하는 클래스입니다.
QuerydslRepositorySupport 클래스를 상속받아서 사용합니다.
- 밑의 클래스를 작성하시면 에러가 발생할 것입니다. Querydsl plugin을 실행시켜야 합니다.
- 실행시키는 방법은 Querydsl plugin 사용방법을 참조해주세요.
package com.example.querydsl.store.support;
import com.example.querydsl.staff.entity.QStaff;
import com.example.querydsl.staff.entity.Staff;
import com.example.querydsl.store.entity.QStore;
import com.example.querydsl.store.entity.Store;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.example.querydsl.staff.entity.QStaff.*;
import static com.example.querydsl.store.entity.QStore.*;
@Repository
public class StoreRepositorySupport extends QuerydslRepositorySupport {
private final JPAQueryFactory jpaQueryFactory;
/**
* Creates a new {@link QuerydslRepositorySupport} instance for the given domain type.
*
* @param domainClass must not be {@literal null}.
*/
public StoreRepositorySupport(JPAQueryFactory jpaQueryFactory) {
super(Store.class);
this.jpaQueryFactory = jpaQueryFactory;
}
public List<Store> findByName(String name) {
return jpaQueryFactory
.selectFrom(store)
.where(store.name.eq(name))
.fetch();
}
public Store findOneByName(String name) {
return jpaQueryFactory
.selectFrom(store)
.where(store.name.eq(name))
.fetchOne();
}
/**
* Entity 관계 매핑 되어 있는 경우
* @param name
* @return
*/
// public List<Staff> findStaffsByName(String name) {
// return jpaQueryFactory
// .select(Projections.fields(Staff.class,
// staff.id
// , staff.age
// , staff.name
// ))
// .from(store)
// .join(store.staff, staff)
// .where(store.name.eq(name))
// .fetch();
// }
/**
* Entity 관계 매핑 되어 있지 않을 경우
* @param name
* @return
*/
public List<Staff> findStaffsByName(String name) {
return jpaQueryFactory
.select(Projections.fields(StaffVo.class,
staff.id
, staff.age
, staff.name
))
.from(store)
.join(staff)
.on(store.id.eq(staff.storeId))
.where(store.name.eq(name))
.fetch();
}
}
package com.example.querydsl.staff.support;
import com.example.querydsl.staff.entity.Staff;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
@Repository
public class StaffRepositorySupport extends QuerydslRepositorySupport {
private final JPAQueryFactory jpaQueryFactory;
/**
* Creates a new {@link QuerydslRepositorySupport} instance for the given domain type.
*
* @param domainClass must not be {@literal null}.
*/
public StaffRepositorySupport(JPAQueryFactory jpaQueryFactory) {
super(Staff.class);
this.jpaQueryFactory = jpaQueryFactory;
}
}
RepositorySupport에서 QuerydslRepositorySupport를 상속받으면 생성자를 설정해야합니다.
import static com.example.querydsl.entity.QStaff.staff;
import static com.example.querydsl.entity.QStore.store;
- 이 두줄이 중요합니다.
- Querydsl 플러그인을 사용해서 Q클래스를 만들었고 어떻게 사용하는지 보여주는 부분입니다. static으로 선언하고 실제 로직에서는 store, staff 만 작성하여 사용하시면 됩니다. sql문과 사용하는 방식이 비슷하죠?
@param domainClass must not be {@literal null}.
주석에서 가이드한 것처럼 domain class를 설정해줘야합니다.
1) join을 걸때 Entity에서 관계 설정이 되어있는 기준으로 작성해주시면 됩니다.
제가 삽질한 내용 공유해드립니다.
Select fields 값 세팅 시 Staff Entity로 하시면 절대 안됩니다.
Querydsl에서 조회된 값을 선언한 클래스에 접근하여 필드값을 세팅하려고 할 때 접근 권한이 없다고 나옵니다.
Class com.querydsl.core.types.QBean can not access a member of class "entity" with modifiers "protected"
이유는 Entity는 protected 이기 때문입니다.
자세한 내용은 여기를 참조해주세요.
.select(Projections.fields(StaffVo.class,))
테스트
Junit5를 사용하였습니다.
package com.example.querydsl.querydsl;
import com.example.querydsl.staff.entity.Staff;
import com.example.querydsl.staff.repository.StaffRepository;
import com.example.querydsl.store.entity.Store;
import com.example.querydsl.store.repository.StoreRepository;
import com.example.querydsl.store.support.StoreRepositorySupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ActiveProfiles("local")
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class StoreRepositorySupportTest {
@Autowired
private StoreRepository storeRepository;
@Autowired
private StaffRepository staffRepository;
@Autowired
private StoreRepositorySupport storeRepositorySupport;
@Test
void findOneByNameTest() {
//given
final Long id = 5L;
final String address = "주소5";
final String name = "스토어5";
Store store = Store.builder()
.id(id)
.address(address)
.name(name)
.build();
storeRepository.save(store);
//when
Store resultByStore = storeRepositorySupport.findOneByName(name);
//then
Assertions.assertEquals(name, resultByStore.getName());
}
@Test
void findStaffsByNameTest_Entity관계매핑안되어있는경우() {
//given
final Long staffId1 = 10L;
final String staffName1 = "staffName4";
final Integer age1 = 34;
final Long staffId2 = 11L;
final String staffName2 = "staffName5";
final Integer age2 = 21;
final Long id = 8L;
final String address = "주소6";
final String name = "스토어6";
Staff staff1 = Staff.builder()
.id(staffId1)
.name(staffName1)
.age(age1)
.storeId(id)
.build();
Staff staff2 = Staff.builder()
.id(staffId2)
.name(staffName2)
.age(age2)
.storeId(id)
.build();
staffRepository.saveAll(Arrays.asList(staff1, staff2));
Store store = Store.builder()
.id(id)
.address(address)
.name(name)
.build();
storeRepository.save(store);
//when
List<Staff> staffs = storeRepositorySupport.findStaffsByName(name);
//then
assertThat(staffs.size()).isGreaterThan(0);
assertThat(staffs.get(0).getName()).isEqualTo(staffName1);
assertThat(staffs.get(1).getName()).isEqualTo(staffName2);
}
}
하나씩 알아보겠습니다.
1 건 조회
@Test
void findOneByNameTest() {
//given
final Long id = 3L;
final String address = "주소3";
final String name = "스토어3";
Store store = Store.builder()
.id(id)
.address(address)
.name(name)
.build();
storeRepository.save(store);
//when
Store resultByStore = storeRepositorySupport.findOneByName(name);
//then
Assertions.assertEquals(name, resultByStore.getName());
}
- given
- jpa를 통해서 테스트에 필요한 데이터를 밀어넣습니다.
- when
- querydsl을 통해서 값을 조회합니다.
- then
- 데이터를 검증합니다.
Querydsl 사용해보자. ( 설정편 ) 에서 설정한 application.yaml이 있다면 로그에서 어떻게 쿼리를 생성해서 DB와 커넥션 맺어 사용하는지 볼 수 있습니다.
밑에 두줄은 쿼리 실행 시 파라미터를 어떻게 세팅했는지 보여줍니다.
데이터 Join 조회
@Test
void findStaffsByNameTest_Entity관계매핑안되어있는경우() {
//given
final Long staffId1 = 10L;
final String staffName1 = "staffName4";
final Integer age1 = 34;
final Long staffId2 = 11L;
final String staffName2 = "staffName5";
final Integer age2 = 21;
final Long id = 8L;
final String address = "주소6";
final String name = "스토어6";
Staff staff1 = Staff.builder()
.id(staffId1)
.name(staffName1)
.age(age1)
.storeId(id)
.build();
Staff staff2 = Staff.builder()
.id(staffId2)
.name(staffName2)
.age(age2)
.storeId(id)
.build();
staffRepository.saveAll(Arrays.asList(staff1, staff2));
Store store = Store.builder()
.id(id)
.address(address)
.name(name)
.build();
storeRepository.save(store);
//when
List<Staff> staffs = storeRepositorySupport.findStaffsByName(name);
//then
assertThat(staffs.size()).isGreaterThan(0);
assertThat(staffs.get(0).getName()).isEqualTo(staffName1);
assertThat(staffs.get(1).getName()).isEqualTo(staffName2);
}
- given
- jpa를 통해서 테스트에 필요한 데이터를 밀어넣습니다.
- when
- querydsl을 통해서 값을 조회합니다.
- 여기서 Entity 관계 매핑과의 다른점이 보입니다.
- 관계 매핑일 경우 : join(store.staff, staff)
- 관계 없을 경우 : join(staff).on(store.id.eq(staff.storeId)
- then
- 데이터를 검증합니다.
여기까지 Entity 관계 매핑이 되어있지 않을 경우 Querydsl 사용법에 대해서 알아보았습니다.
다음은 Spring batch에서 QuerydslPagingItemReader 를 사용하는 방법을 알아보겠습니다.
'Develop > spring-data' 카테고리의 다른 글
Querydsl 사용시 Mysql에서 생성한 Function 호출하는 방법 (0) | 2020.11.11 |
---|---|
jpa, querydsl 적용 시 로깅 비교. querydsl로 작성한 쿼리 DBMS에서 쉽게 돌리는 방법. 1탄! (0) | 2020.11.10 |
Spring Data JPA 기반 Querydsl 사용해보자. ( Entity 관계 매핑 편, 테스트 코드 포함 ) (0) | 2020.11.08 |
Spring Data JPA 기반 Querydsl plugin 실행방법 (0) | 2020.11.07 |
Spring Data JPA 기반 Querydsl 사용해보자. ( 설정편 ) (0) | 2020.11.07 |