본문 바로가기
study

[JSCODE] Spring Data JPA

by 당코 2023. 3. 24.

ProductEntity와 Spring Data JPA를 이용하여 API를 설계해 보았다.

 

Controller, Service, Repository 3가지 계층으로 나누어 기능별로 구분하였다.

 

ProductEntity

@Entity
@Getter
public class ProductEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Long price;

    protected ProductEntity() {
    }

    public ProductEntityDto toDto() {
        return ProductEntityDto.builder()
                .id(id)
                .name(name)
                .price(price)
                .build();
    }

    @Builder
    public ProductEntity(Long id, String name, Long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

 

ProductEntityDto

@Getter
public class ProductEntityDto {

    private Long id;
    private String name;
    private Long price;

    public ProductEntity toEntity() {
        return ProductEntity.builder()
                .id(id)
                .name(name)
                .price(price)
                .build();
    }

    @Builder
    public ProductEntityDto(Long id, String name, Long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

 

Controller

    @GetMapping(params = "id")
    public ProductEntityDto findById(@RequestParam Long id) {
        return productService.findById(id);
    }

    @GetMapping(params = "name")
    public ProductEntityDto findByName(@RequestParam String name) {
        return productService.findByName(name);
    }

    @GetMapping(params = "price")
    public List<ProductEntityDto> findAllByPrice(@RequestParam Long price) {
        return productService.findAllByPrice(price);
    }

    @GetMapping(params = {"name", "price"})
    public List<ProductEntityDto> findAllByNameAndPrice(@RequestParam String name,
                                                        @RequestParam Long price) {
        return productService.findAllByNameAndPrice(name, price);
    }


    @PostMapping
    public Long save(@RequestBody ProductEntityDto productEntityDto) {
        return productService.save(productEntityDto);
    }

컨트롤러를 구현할 때 고민했던 것은 동일한 url에 Get으로 요청이 올 때 파라미터 값으로 요청들을 구분하는 방법이었다.

처음에는 @RequestParam(required = false)로 하여 들어있는 파라미터 값에 따라 나눠서 기능하는 방식으로 시도해 봤다.

    @GetMapping
    public ProductEntityDto findById(@RequestParam(required=false) Long id,
    				@RequestParam(required=false) String name) {
 	if(id != null){
    	    return productService.findById(id);
        }
        if(name != null){
            return productService.findByName(name);
        }
        throw new NoParamException("파라미터 값이 없습니다.");
    }

하지만 파라미터의 변수가 늘어날수록 생각해야 할 경우의 수가 너무 많아 효율적이지 못하였다.

그래서 @GetMapping의 인자로 param을 넘겨서 해당 파라미터 값이 있는 요청에 각각 대응시키도록 설계했다.

 

Service

    public Long save(ProductEntityDto productEntityDto) {
        List<ProductEntity> findProducts = productJpaRepository.findAllByName(productEntityDto.getName());
        return productJpaRepository.save(productEntityDto.toEntity()).getId();
    }

    public ProductEntityDto findById(Long id) {
        return productJpaRepository.findById(id).get().toDto();
    }

    public ProductEntityDto findByName(String name) {
        return productJpaRepository.findByName(name).toDto();
    }

    public List<ProductEntityDto> findAllByPrice(Long price){
        return productJpaRepository.findAllByPriceOrderByNameDesc(price).stream()
                .map(ProductEntity::toDto)
                .collect(Collectors.toList());
    }

    public List<ProductEntityDto> findAllByNameAndPrice(String name, Long price){
        return productJpaRepository.findAllByNameAndPrice(name,price)
                .stream()
                .map(ProductEntity::toDto)
                .collect(Collectors.toList());
    }

service에서는 repository에서 데이터를 받아 dto로 변환 후 controller에 리턴해 주었다.

 

Repository

public interface ProductJpaRepository extends JpaRepository<ProductEntity, Long> {

    public List<ProductEntity> findAllBy();

    public ProductEntity findByName(String name);

    public List<ProductEntity> findAllByName(String name);

    public List<ProductEntity> findAllByPriceOrderByNameDesc(Long price);

    public List<ProductEntity> findAllByNameAndPrice(String name, Long price);

    //추가기능
    public List<ProductEntity> findAllByNameLike(String name);

    public ProductEntity findTopByOrderByPriceDesc();

    public List<ProductEntity> findAllByNameContaining(String name);

    @Query("select p.name from ProductEntity p where p.price = (select min(p2.price) from ProductEntity p2)")
    public String findNameOfCheapestProduct();

    @Query("select avg(p.price) from ProductEntity p")
    public double findAvgPrice();
}

 

Spring Data JPA를 사용해서 repository를 만들었다.

추가적으로 다음 5가지 기능을 하는 메서드를 만들어 추가하였다.

  • 전체 상품을 조회하는데, name이 모니터인 상품은 무시
  • 가장 가격이 비싼 상품 조회하기
  • 이름에 “컴”을 포함하는 상품 조회하기
  • 가장 가격이 저렴한 상품의 ‘이름만’ 조회하기
  • 상품 가격의 평균 구하기

참고로 @Query로 직접 jpql을 작성할 때 limit을 사용해 보았는데 @Query에서는 지원을 안 하여 다른 방식으로 작성했다.

 

 

배운 내용, 깨달은 점

간단한 기능임에도 시간이 많이 소요되었다. 사소한 것 하나하나 직접 찾아가면서 코드를 짜야하다 보니 생각해야 할 것이 많았다. @ExceptionHandler를 통해 예외 처리하는 방법, 엔티티 리스트를 dto로 변환하는 방법, JpaRepository에서 test 실행 방법 등을 찾아가면서 정리하는 과정을 통해 전체적인 흐름에 대해 좀 더 감을 잡을 수 있었다.

 

어려웠던 점, 반성하고 싶은 점 / 개선할 방법

mysql로 테스트를 진행하는 것이 아닌 h2 embedded db로 테스트를 하고자 했는데 여러 오류로 인해 실패하였다. 좀 더 찾아보고 적용시키는 방법을 알아봐야겠다.