<aside> 👉 우리는 JPA라는 개념을 배우고 유저 테이블에 JPA를 적용해 보았습니다. 몇 가지 문제를 통해 JPA를 연습해 봅시다! 🔥

</aside>

1. Convert To JPA

// Fruit.java

package com.inflearn.Inflearn.Study.day07.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Entity
@NoArgsConstructor // JPA는 기본 생성자가 필요하다.
@Getter
public class Fruit {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private LocalDate warehousingDate;

    private Long price;

    private boolean isSold; // 0 -> false, 1 -> true

    public Fruit(String name, LocalDate warehousingDate, Long price) {
        this.name = name;
        this.warehousingDate = warehousingDate;
        this.price = price;
    }

    public void soldFruit() {
        this.isSold = true;
    }
}

// FruitService.java

package com.inflearn.Inflearn.Study.day07.service;

import com.inflearn.Inflearn.Study.day07.dto.*;
import com.inflearn.Inflearn.Study.day07.entity.Fruit;
import com.inflearn.Inflearn.Study.day07.repository.FruitRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@Service // 스프링 빈으로 만들어 줌
@Transactional(readOnly = true) // default값이 false
public class FruitService {

    private final FruitRepository fruitRepository;

    public FruitService(FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }

    @Transactional
    public void saveFruit(FruitRequest request) {
        Fruit fruit = new Fruit(request.getName(), request.getWarehousingDate(), request.getPrice());
        fruitRepository.save(fruit);
    }

    @Transactional
    public void sellFruit(FruitUpdateRequest updateRequest) {
        Optional<Fruit> fruit = Optional.ofNullable(fruitRepository.findById(updateRequest.getId())
                .orElseThrow(() -> new IllegalArgumentException("데이터베이스에 과일이 없습니다.")));
        fruit.get().soldFruit();
    }

    public FruitSoldResponse getFruitIsSoldOrNot(String name) {
        List<Fruit> fruit = fruitRepository.findByName(name);
        if(fruit.size() == 0) {
            throw new IllegalArgumentException("이름과 일치하는 과일이 없습니다.");
        }
        List<Object[]> fruits = fruitRepository.findIsSoldAndPriceByName(name);
        Long isSold = 0L;
        Long isNotSold = 0L;
        for(Object[] res : fruits) {
            if((Boolean) res[0]) {
                isSold = ((BigDecimal) res[1]).longValue();
            }
            else {
                isNotSold = ((BigDecimal) res[1]).longValue();
            }
        }
        return new FruitSoldResponse(isSold, isNotSold);
    }
}
// FruitRepository.java

package com.inflearn.Inflearn.Study.day07.repository;

import com.inflearn.Inflearn.Study.day07.dto.FruitResponse;
import com.inflearn.Inflearn.Study.day07.entity.Fruit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface FruitRepository extends JpaRepository<Fruit, Long> {
    List<Fruit> findByName(String name);

    @Query(value = "select f.is_sold, sum(f.price) from fruit f where f.name = :name group by f.is_sold", nativeQuery = true)
    List<Object[]> findIsSoldAndPriceByName(@Param("name") String name);
}

// FruitController.java

package com.inflearn.Inflearn.Study.day07.controller;

import com.inflearn.Inflearn.Study.day07.dto.*;
import com.inflearn.Inflearn.Study.day07.service.FruitService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class FruitController {

    private final FruitService fruitService;

    public FruitController(FruitService fruitService) {
        this.fruitService = fruitService;
    }

    @PostMapping("/api/v3/fruit")
    public void saveFruit(@RequestBody FruitRequest request) {
        fruitService.saveFruit(request);
    }

    @PutMapping("/api/v3/fruit")
    public void sellFruit(@RequestBody FruitUpdateRequest updateRequest) {
        fruitService.sellFruit(updateRequest);
    }

    @GetMapping("/api/v3/fruit/stat")
    public FruitSoldResponse getFruitIsSoldOrNot(@RequestParam String name) {
        return fruitService.getFruitIsSoldOrNot(name);
    }
}

(Dto는 동일하므로 코드 생략)

2. 특정 과일 기준 가게에 있는(던) 과일 개수 조회 GET API

  1. JPA를 사용하면 이름을 통해 메서드 역할을 부여할 수 있다.
  2. 우리는 이름을 통해 개수를 알고 싶은 것이므로 → countByName이라는 네이밍을 해줄 수 있다.
@GetMapping("/api/v3/fruit/count")
public FruitCountResponse getFruitCountByName(@RequestParam String name) {
    return fruitService.getFruitCountByName(name);
}
public FruitCountResponse getFruitCountByName(String name) {
    return new FruitCountResponse(fruitRepository.countByName(name));
}
Long countByName(String name);

3. 판매되지 않은 특정 금액 이상 / 이하 과일 목록 반환 GET API

  1. 주어진 조건은 2개다. 특정 금액 이상 / 이하 + 판매되지 않음
  2. JPA를 사용하면 findAllByPriceGreaterThanEqualAndIsSoldFalse 라는 네이밍을 통해서 조건을 만족하는 쿼리를 날릴 수 있다.
@GetMapping("/api/v3/fruit/list")
public List<FruitResponse> getFruitByPrice(@RequestParam String opt, @RequestParam Long price) {
    if(opt.equals("GTE")) {
        return fruitService.getFruitGreaterThanEqualByPrice(price);
    }
    return fruitService.getFruitLessThanEqualByPrice(price);
}