📚 인프런 강의와 스터디를 바탕으로 한 과제를 풀이한 포스트입니다.
Spring Data JPA를 이용해 자동으로 쿼리 날림으로서 여러 기능 구현하기
<문제1> 기존의 Controller-Service-Repository가 JPA를 이용해 동작하도록 변경하기
<풀이>
Fruit
테이블에 대응되는 Entity Class 만든다. 어떤 parameter도 받지 않는 기본 생성자를 protected로 작성했고, saveFruit
을 위해 name
, warehousing_date
, price
를 받는 생성자를 따로 만들었다. 판매 여부 purchase
의 default 값은 false
로 지정했다.updateFruit
함수는 id를 받아 purchase 값을 true로 저장한다.** fruit 테이블 명세
create table fruit (
id bigint auto_increment,
name varchar(20),
warehousing_date date,
price bigint,
purchase boolean default false,
primary key (id)
);
** @Entity 클래스 Fruit
@Entity
public class Fruit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String name;
@Column
private LocalDate warehousing_date;
@Column
private long price;
@Column
private boolean purchase;
public String getName() {
return name;
}
public LocalDate getWarehousing_date() {
return warehousing_date;
}
public long getPrice() {
return price;
}
public boolean isPurchase() {
return purchase;
}
protected Fruit() {
}
public Fruit(String name, LocalDate warehousing_date, long price) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException(String.format("잘못된 name(%s)이 들어왔습니다", name));
}
this.name = name;
this.warehousing_date = warehousing_date;
this.price = price;
this.purchase = false;
}
public void updateFruit(long id) {
this.purchase = true;
}
}
FruitService
를 FruitServiceV1
으로 바꾸고, JPA를 이용해 db를 조작하기 위해 새로 FruitServiceV2
를 만들었다.**FruitServiceV2
@Service
public class FruitServiceV2 {
private final FruitRepository fruitRepository;
public FruitServiceV2(FruitRepository fruitRepository) {
this.fruitRepository = fruitRepository;
}
// C
public void saveFruit(FruitCreateRequest request) {
Fruit fruit = fruitRepository.save(new Fruit(request.getName(),
request.getWarehousingDate(), request.getPrice()));
}
// R
public FruitResponse getFruit(String name) {
...
}
public FruitCount getFruitCount(String name) {
...
}
public List<FruitList> getFruitList(String option, long price) {
...
}
// U
public void updateFruit(FruitUpdateRequest request) {
Fruit fruit = fruitRepository.findById(request.getId())
.orElseThrow(IllegalArgumentException::new);
fruit.updateFruit(request.getId());
fruitRepository.save(fruit);
}
}
FruitRepository
는 다음과 같이 JpaRepository
를 상속받는다. 테이블의 매핑 객체인 Fruit
과 Fruit 테이블의 id인 Long
타입을 각각 적어주었다.** FruitRepository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
List<Fruit> findAllByName(String name);
List<Fruit> findAllByPurchaseAndPriceGreaterThanEqual(boolean purchase, long price);
List<Fruit> findAllByPurchaseAndPriceLessThanEqual(boolean purchase, long price);
List<Fruit> findAllByNameAndPurchase(String name, boolean purchase);
}
FruitController
는 FruitServiceV2
를 이용할 수 있도록 변경했다.** FruitController
@RestController
public class FruitController {
public FruitController(FruitServiceV2 fruitServiceV2) {
this.fruitServiceV2 = fruitServiceV2;
}
// ...
}
** FruitServiceV2의 getFruit
public FruitResponse getFruit(String name) {
long salesAmount = 0;
long notSalesAmount = 0;
List<Fruit> fruits = fruitRepository.findAllByName(name);
if (fruits.isEmpty()) {
throw new IllegalArgumentException();
}
for (Fruit fruit : fruits) {
if (fruit.isPurchase()) {
salesAmount += fruit.getPrice();
}
else {
notSalesAmount += fruit.getPrice();
}
}
return new FruitResponse(salesAmount, notSalesAmount);
}
Fruit
리스트에 담아준다. findAll()
의 메소드는 List<>
형태로 return하기 때문에 return 값이 없다는 것을 확인하려면 isEmpty()
메소드를 활용해야 한다.salesAmount
에 더하고, 판매되지 않았다면 notSalesAmount
에 더한다.salesAmount
, notSalesAmount
를 멤버 변수로 갖는 클래스 FruitResponse
를 따로 만들어 생성자를 return하면 원하는 출력 결과를 얻을 수 있다.** FruitResponse
public class FruitResponse {
private long salesAmount;
private long notSalesAmount;
public FruitResponse(long salesAmount, long notSalesAmount) {
this.salesAmount = salesAmount;
this.notSalesAmount = notSalesAmount;
}
public FruitResponse(Fruit fruit) {
this.salesAmount = salesAmount;
this.notSalesAmount = notSalesAmount;
}
public long getSalesAmount() {
return salesAmount;
}
public long getNotSalesAmount() {
return notSalesAmount;
}
}
<실행 결과>
<문제 2> 특정 과일을 기준으로 팔린 과일의 개수 세기
<풀이>
FruitController
의 getFruitCount
함수// hw6 #q2
@GetMapping("/api/v1/fruit/count")
public FruitCount getFruitCount(@RequestParam String name) {
return fruitServiceV2.getFruitCount(name);
}
FruitServiceV2
의 getFruitCount
함수public FruitCount getFruitCount(String name) {
List<Fruit> fruits = fruitRepository.findAllByNameAndPurchase(name, true);
if (fruits.isEmpty()) {
throw new IllegalArgumentException();
}
long salesCount = 0;
for (Fruit fruit : fruits) {
salesCount += 1;
}
return new FruitCount(salesCount);
}
과일 데이터에서 이름과 판매 여부가 true
(판매되었음)을 조건으로 SQL 쿼리를 날린다. 조건에 해당하는 과일 데이터들을 for문으로 돌면서 해당하는 데이터들의 개수를 count한다. count를 멤버 변수로 갖는 클래스 FruitCount
를 만들어 생성자를 return하면 원하는 출력 결과를 얻을 수 있다.
** FruitCount
public class FruitCount {
private long count;
public long getCount() {
return count;
}
public FruitCount(long count) {
this.count = count;
}
}
<실행 결과>
<문제 3> 판매되지 않은 특정 금액 이상/이하의 과일 목록 조회하기
<풀이>
FruitController
의 getFruitCount
함수// hw6 #q3
@GetMapping("/api/v1/fruit/list")
public List<FruitList> getFruitList(@RequestParam String option, Integer price) {
return fruitServiceV2.getFruitList(option, price);
}
FruitServiceV2
의 getFruitList
함수public List<FruitList> getFruitList(String option, long price) {
List<FruitList> fruitLists = new ArrayList<>();
if (option.equals("GTE")) {
List<Fruit> fruits = fruitRepository.findAllByPurchaseAndPriceGreaterThanEqual(false, price);
for (Fruit fruit: fruits) {
fruitLists.add(new FruitList(fruit));
}
return fruitLists;
}
else if (option.equals("LTE")){
List<Fruit> fruits = fruitRepository.findAllByPurchaseAndPriceLessThanEqual(false, price);
for (Fruit fruit: fruits) {
fruitLists.add(new FruitList(fruit));
}
return fruitLists;
}
else { throw new IllegalArgumentException(); }
}
option
문자열을 확인해서 findAllByPurchaseAndPriceGreaterThanEqual
혹은 findAllByPurchaseAndPriceLessThanEqual
를 통해 쿼리를 날린다. 이때 판매되지 않은 과일 목록을 select해야 하므로 parameter로 boolean purchase = false
를 넣어줬다. FruitList
를 만들었다.FruitList
클래스는 name
, price
, warehousingDate
만을 멤버 변수로 갖기 때문에, 이를 리턴하면 원하는 필드 값만 출력할 수 있다.FruitList
를 생성자를 통해 만들고 리스트 배열에 add
(추가)한다. 최종적으로는 FruitList
타입의 리스트를 리턴한다.** FruitList
public class FruitList {
private String name;
private long price;
private LocalDate warehousingDate;
public FruitList(Fruit fruit) {
this.name = fruit.getName();
this.price = fruit.getPrice();
this.warehousingDate = fruit.getWarehousing_date();
}
public FruitList(String name, long price, LocalDate warehousingDate) {
this.name = name;
this.price = price;
this.warehousingDate = warehousingDate;
}
public String getName() {
return name;
}
public long getPrice() {
return price;
}
public LocalDate getWarehousingDate() {
return warehousingDate;
}
}
<실행 결과>
<정리하며>
[Reference]