jpa를 사용할 때 Repository에서 데이터를 바로 DTO로 셋팅하는 방법을 사용하려면 2가지 방법이 있다.
1. 첫번째 방법: @Query()를 사용
쿼리어노테이션을 사용해 바로 DTO에 매핑하는 방법
- 이 방법은 쿼리의 결과를 바로 DTO에 매핑하므로, 불필요한 객체 변환 단계를 거치지 않아서 성능상 이점이 있을 수 있습니다.
- 코드가 간결해지고 읽기 쉬워집니다. 쿼리의 내용이 명확히 드러나므로 이해하기 쉽습니다.
- 그러나 DTO의 생성자에 맞게 쿼리 결과를 매핑해야 하므로 DTO의 생성자와 쿼리의 필드 리스트가 일치해야 합니다.
별다른 DTO 가공이 필요 없다면 바로 DTO로 @Query을 사용해 DTO로 넣으면 된다.
예시:
DistrictRepository.java
package com.pnow.repository;
import com.pnow.domain.District;
import com.pnow.dto.DistrictDTO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DistrictRepository extends JpaRepository<District, Long> {
// cityId에 해당하는 district들을 DistrictDTO로 바로 넣는 방법
@Query("SELECT new com.pnow.dto.DistrictDTO(d.id, d.districtName) FROM District d WHERE d.city.id = :cityId") //추가로직 필요없으므로 DTO에 바로 받아 넣기
List<DistrictDTO> findDistrictDTOsByCityId(@Param("cityId") Long cityId);
}
District.java 엔티티
package com.pnow.domain;
import lombok.Getter;
import javax.persistence.*;
import java.util.List;
@Getter
@Entity
public class District {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "district_id")
private Long id;
@ManyToOne
@JoinColumn(name = "city_id")
private City city;
@Column(nullable = false)
private String districtName; //시군구 이름
@OneToMany(mappedBy = "district", cascade = CascadeType.REMOVE)
private List<Store> storeList; //district_id 해당하는 storeList
}
DistrictDTO.java
package com.pnow.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor //기본생성자
@AllArgsConstructor //모든생성자
public class DistrictDTO {
private Long id; //지역ID
private String districtName; //지역이름
}
2. 두번째 방법: 엔티티로 받은 후 Service에서 DTO로 직접 set 하기
service에서 DTO에 값을 설정하는 방법
- 이 방법은 먼저 엔티티 객체를 받은 다음, 서비스 레이어에서 DTO로 변환하는 과정이 필요합니다. 이로 인해 성능이 약간 저하될 수 있습니다.
- 그러나 변환 과정에서 추가적인 로직이 필요할 경우에 유용합니다. 예를 들어, 엔티티의 특정 필드를 기반으로 DTO의 특정 값을 계산하거나, 다른 로직을 적용할 수 있습니다.
- 또한, DTO와 엔티티의 구조가 다를 경우에는 DTO에 데이터를 수동으로 매핑할 수 있습니다.
DTO 가공이 필요하다면, 두번째 방법인 엔티티로 객체를 받아 service에서 DTO로 변환해주면 된다.
예시:
StoreRepository.java
package com.pnow.repository;
import com.pnow.domain.Store;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface StoreRepository extends JpaRepository<Store, Long> {
List<Store> findByCategoryIdAndDistrictIdOrderByStoreNameAsc(Long categoryId, Long districtId); //추가로직 필요하므로 일단 엔티티에 받기(추가로직은 서비스에서 작업)
}
StoreService.java
package com.pnow.service;
import com.pnow.domain.City;
import com.pnow.domain.District;
import com.pnow.domain.Store;
import com.pnow.dto.StoreDTO;
import com.pnow.repository.CityRepository;
import com.pnow.repository.DistrictRepository;
import com.pnow.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
@Slf4j
public class StoreService {
private final StoreRepository storeRepository;
private final CityRepository cityRepository;
private final DistrictRepository districtRepository;
public List<StoreDTO> findStoreDTOList(Long categoryId, Long cityId, Long districtId) {
// 1. categoryId, districtId에 해당하는 Store 엔티티 가져오기
List<Store> stores = storeRepository.findByCategoryIdAndDistrictIdOrderByStoreNameAsc(categoryId, districtId);
// 2. 전달받은 cityId에 해당하는 cityName 가져오기
Optional<City> cityOptional = cityRepository.findById(cityId);
String cityName = cityOptional.map(City::getCityName).orElse(null);
// 3. 전달받은 districtId에 해당하는 districtName 가져오기
Optional<District> districtOptional = districtRepository.findById(districtId);
String districtName = districtOptional.map(District::getDistrictName).orElse(null);
// 4. List<Store>를 List<StoreDTO>로 변환
return stores.stream()
.map(store -> {
StoreDTO storeDTO = new StoreDTO();
storeDTO.setId(store.getId());
storeDTO.setStoreName(store.getStoreName());
storeDTO.setOpeningTime(store.getOpeningTime());
storeDTO.setClosingTime(store.getClosingTime());
storeDTO.setCityName(cityName);
storeDTO.setDistrictName(districtName);
storeDTO.setDetailAddress(store.getDetailAddress());
log.info("storeDTO.getStoreName() = {}", storeDTO.getStoreName());
return storeDTO;
})
.collect(Collectors.toList());
}
}
Store.java 엔티티
package com.pnow.domain;
import lombok.Getter;
import javax.persistence.*;
import java.time.LocalTime;
import java.util.List;
@Getter
@Entity
public class Store {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "store_id")
private Long id;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@Column(nullable = false)
private String storeName; //음식점명
@ManyToOne
@JoinColumn(name = "district_id")
private District district; //시군구 district_id (도 관련 city_id도 내포되어 있음)
@Column(nullable = false)
private String detailAddress; //나머지 상세주소
@Column
private String phoneNumber; //음식점 전화번호
@Column(nullable = false)
private LocalTime openingTime; // 오픈시간
@Column(nullable = false)
private LocalTime closingTime; // 오프시간
@OneToMany(mappedBy = "store", cascade = CascadeType.REMOVE)
private List<Bookmark> bookmarkList;
@OneToMany(mappedBy = "store", cascade = CascadeType.REMOVE)
private List<Reservation> reservationList;
// @OneToMany(mappedBy = "restaurant", cascade = CascadeType.REMOVE)
// private List<Review> reviewList;
@OneToMany(mappedBy = "store", cascade = CascadeType.REMOVE)
private List<Menu> menuList;
}
StoreDTO.java
package com.pnow.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalTime;
@Getter
@Setter
@NoArgsConstructor //기본생성자
@AllArgsConstructor //모든생성자
public class StoreDTO {
private Long id; //가게 id
private String storeName; //가게 이름
private LocalTime openingTime; //오픈 시간
private LocalTime closingTime; //오프 시간
private String cityName; //도시 이름
private String districtName; //지역 이름
private String detailAddress; //상세 주소
}
정리하자면,
- 받아오는 엔티티 객체와 DTO가 거의 일치하고 DTO에 별다른 가공 작업이 필요 없다면 첫번째 @Query 어노테이션사용해서 DTO에 바로 셋팅하기
- 반대로 DTO 가공 작업이 필요하다면, 일단 엔티티 객체로 받은 후 service 부분에서 DTO로 가공작업 하며 set하기
'JPA' 카테고리의 다른 글
[JPA] Entity PK는 bigint(MySQL)-Long(java) 타입으로 해야 하는 이유 (0) | 2024.02.15 |
---|---|
[JPA] @Setter 대신 @Builder 사용해야하는 이유 (1) | 2024.02.14 |
[JPA] jpa insert시 default 적용 (0) | 2024.02.13 |
[JPA] jpa 메서드 (2) | 2024.02.12 |