๐ŸŽฝ ์ธํ”„๋Ÿฐ ์›Œ๋ฐ์—… ํด๋Ÿฝ 0๊ธฐ ๋ฐฑ์—”๋“œ ์ผ๊ณฑ ๋ฒˆ์งธ ๊ณผ์ œ

pucaยท2024๋…„ 2์›” 27์ผ
0

์ธํ”„๋Ÿฐ

๋ชฉ๋ก ๋ณด๊ธฐ
7/7
post-thumbnail

์ถœ์ฒ˜
https://inf.run/XKQg

Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ์ž‘์— ๋Œ€ํ•ด ํ•™์Šตํ•˜์—ฌ ๊ณผ์ œ๋ฅผ ์ˆ˜ํ–‰ํ–ˆ๋‹ค.



๐ŸŒˆ Spring Data JPA๋ž€?

JPA๋Š” ์ž๋ฐ” Object์™€ Realational(๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค) ๊ฐ„์˜ ๋งคํ•‘์„ ์œ„ํ•œ ํ‘œ์ค€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
Spring Data JPA๋Š” ์ด๋Ÿฌํ•œ JPA๋ฅผ ๋” ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋ฉฐ ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•œ๋‹ค.


โ›ณ๏ธ Fruit Entity

Spring Data JPA์—์„œ ์‚ฌ์šฉ๋˜๋Š” Entity ํด๋ž˜์Šค์ด๋‹ค.
Entity ํด๋ž˜์Šค๋ž€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ์ž๋ฐ” ๊ฐ์ฒด๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์ด ํด๋ž˜์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ fruit ํ…Œ์ด๋ธ”๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ๋Š” Snake Case๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
Snake Case๋Š” ๋‹จ์–ด ์‚ฌ์ด๋ฅผ ์–ธ๋”์Šค์ฝ”์–ด(_)๋กœ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ช…๋ช… ๊ทœ์น™์œผ๋กœ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋ช…์„ ์ผ๋ฐ˜์ ์œผ๋กœ ์ง€์„ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.

์ž๋ฐ”๋Š” Camel Case๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ @Column(name = "warehousing_date")์™€ @Column(name = "is_sold")์„ ํ†ตํ•ด ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋ช…์„ ์ง์ ‘ ์ง€์ •ํ–ˆ๋‹ค.

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalDate;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@Entity
public class Fruit {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Column(name = "warehousing_date")
    private LocalDate warehousingDate;

    private Long price;

    @Column(name = "is_sold")
    private Boolean isSold = false;

    protected Fruit() {
    }

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

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

โ›ณ๏ธ FruitRepository

FruitRepository ์ธํ„ฐํŽ˜์ด์Šค๋Š” Spring Data JPA์—์„œ ์ œ๊ณตํ•˜๋Š” JpaRepository๋ฅผ ํ™•์žฅํ•˜์—ฌ ๊ณผ์ผ(Fruit) ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค ๊ธฐ๋Šฅ์„ ์ •์˜ํ•œ๋‹ค.


import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FruitRepository extends JpaRepository<Fruit, Long> {
    // ๋ฌธ์ œ1
    Optional<Fruit> findById(Long id);

    List<Fruit> findByNameAndIsSoldIsFalse(String name);

    List<Fruit> findByNameAndIsSoldIsTrue(String name);


    // ๋ฌธ์ œ2
    long countByName(String name);
    

    // ๋ฌธ์ œ3
    List<Fruit> findAllByPriceGreaterThanEqualAndIsSoldIsFalse(Long price);

    List<Fruit> findAllByPriceLessThanEqualAndIsSoldIsFalse(Long price);
}
  • findById(Long id): ๊ณผ์ผ์˜ ID๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ณผ์ผ์„ ์กฐํšŒํ•œ๋‹ค.
    ๋ฐ˜ํ™˜ ํƒ€์ž…์œผ๋กœ Optional์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ID์— ๋Œ€ํ•œ ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ NPE๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.

  • findByNameAndIsSoldIsFalse(String name): ์ด๋ฆ„๊ณผ ํŒ๋งค ์—ฌ๋ถ€๊ฐ€ 'false'์ธ ๊ณผ์ผ์„ ์ฐพ๋Š”๋‹ค.
    ์—ฌ๋Ÿฌ ๊ฐœ์˜ Fruit์ด ์กฐํšŒ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ List๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  • findByNameAndIsSoldIsTrue(String name): ์ด๋ฆ„๊ณผ ํŒ๋งค ์—ฌ๋ถ€๊ฐ€ 'true'์ธ ๊ณผ์ผ์„ ์ฐพ๊ณ , List๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  • countByName(String name): ํŠน์ • ์ด๋ฆ„์„ ๊ฐ€์ง„ ๊ณผ์ผ์˜ ์ˆ˜๋ฅผ ๊ตฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  • findAllByPriceGreaterThanEqualAndIsSoldIsFalse(Long price): ๊ฐ€๊ฒฉ์ด ํŠน์ • ๊ฐ’ ์ด์ƒ์ด๊ณ  ํŒ๋งค ์—ฌ๋ถ€๊ฐ€ 'false'์ธ ๊ณผ์ผ์„ ์ฐพ๊ณ , List๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  • findAllByPriceLessThanEqualAndIsSoldIsFalse(Long price): ๊ฐ€๊ฒฉ์ด ํŠน์ • ๊ฐ’ ์ดํ•˜์ด๊ณ  ํŒ๋งค ์—ฌ๋ถ€๊ฐ€ 'false'์ธ ๊ณผ์ผ์„ ์ฐพ๊ณ , List๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ์ฟผ๋ฆฌ ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•˜๋ฉด ๊ฐœ๋ฐœ์ž๋Š” ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฉฐ, ๋†’์€ ์ˆ˜์ค€์˜ ์ถ”์ƒํ™”๋ฅผ ํ†ตํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.


โ›ณ๏ธ FruitServiceV2

import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class FruitServiceV2 {
    private final FruitRepository fruitRepository;

    public void saveFruit(String name, LocalDate warehousingDate, long price) {
        fruitRepository.save(new Fruit(name, warehousingDate, price));
    }

    public void updateFruit(Long id) {
        Fruit fruit = fruitRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("ํ•ด๋‹นํ•˜๋Š” ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

        fruit.changeStatus();
        fruitRepository.save(fruit);
    }

    public FruitAmountResponse getAmount(String name) {
        List<Fruit> notSalesFruits = fruitRepository.findByNameAndIsSoldIsFalse(name);
        List<Fruit> salesFruits = fruitRepository.findByNameAndIsSoldIsTrue(name);
        
        long salesAmount = salesFruits.stream().mapToLong(Fruit::getPrice).sum();
        long notSalesAmount = notSalesFruits.stream().mapToLong(Fruit::getPrice).sum();
        return new FruitAmountResponse(salesAmount, notSalesAmount);
    }

    public FruitCountResponse getSoldFruitCount(String name) {
        long count = fruitRepository.countByName(name);
        return new FruitCountResponse(count);
    }

    public FruitsInfoResponse getFruitInfo(FruitsInfoRequest request) {
        if ("GTE".equals(request.option())) {
            return getFruitsInfoByPriceGreaterThanEqual(request.price());
        }

        if ("LTE".equals(request.option())) {
            return getFruitsInfoByPriceLessThanEqual(request.price());
        }

        throw new IllegalArgumentException("๊ณผ์ผ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
    }

    private FruitsInfoResponse getFruitsInfoByPriceGreaterThanEqual(Long price) {
        List<Fruit> fruits = fruitRepository.findAllByPriceGreaterThanEqualAndIsSoldIsFalse(price);
        return FruitsInfoResponse.mapTo(fruits);
    }

    private FruitsInfoResponse getFruitsInfoByPriceLessThanEqual(Long price) {
        List<Fruit> fruits = fruitRepository.findAllByPriceLessThanEqualAndIsSoldIsFalse(price);
        return FruitsInfoResponse.mapTo(fruits);
    }
}

โ›ณ๏ธ FruitController

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class FruitController {

    private final FruitServiceV2 fruitServiceV2;

    @PostMapping("/api/v1/fruit")
    public void saveFruit(@RequestBody FruitInfo request) {
        fruitServiceV2.saveFruit(request.name(), request.warehousingDate(), request.price());
    }

    @PutMapping("/api/v1/fruit")
    public void updateFruit(@RequestBody FruitIdRequest request) {
        fruitServiceV2.updateFruit(request.id());
    }

    @GetMapping("/api/v1/fruit/stat")
    public FruitAmountResponse getAmount(@RequestParam String name) {
        return fruitServiceV2.getAmount(name);
    }

    @GetMapping("/api/v1/fruit/count")
    public FruitCountResponse getSoldFruitCount(@RequestParam String name) {
        return fruitServiceV2.getSoldFruitCount(name);
    }

    @GetMapping("/api/v1/fruit/list")
    public FruitsInfoResponse getFruitInfo(FruitsInfoRequest request) {
        return fruitServiceV2.getFruitInfo(request);
    }
}

โ–ถ๏ธ ์‹คํ–‰๊ฒฐ๊ณผ

1๏ธโƒฃ ๋ฌธ์ œ 1

- ๊ณผ์ผ ์ •๋ณด ์ €์žฅ

- ํŒ๋งค ๊ณผ์ผ ๊ธฐ๋ก

- ํŒ๋งค๋œ ๊ธˆ์•ก & ๋ฏธํŒ๋งค๋œ ๊ธˆ์•ก ์กฐํšŒ


2๏ธโƒฃ ๋ฌธ์ œ 2

- ๊ณผ์ผ ๊ฐœ์ˆ˜ ์กฐํšŒ


3๏ธโƒฃ ๋ฌธ์ œ 3

- ํŒ๋งค๋˜์ง€ ์•Š์€ ํŠน์ • ๊ธˆ์•ก ์ด์ƒ ๊ณผ์ผ ๋ชฉ๋ก ์กฐํšŒ

- ํŒ๋งค๋˜์ง€ ์•Š์€ ํŠน์ • ๊ธˆ์•ก ์ดํ•˜ ๊ณผ์ผ ๋ชฉ๋ก ์กฐํšŒ


โ›ณ๏ธ ๊ฐœ์ธ ํšŒ๊ณ 

Spring Data JPA๋Š” ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ์ž‘์„ ํ•  ์ˆ˜ ์žˆ์–ด ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํŠนํžˆ, Repository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด SQL ์ฟผ๋ฆฌ ์ž‘์„ฑ์— ๋Œ€ํ•œ ๋ถ€๋‹ด์ด ์ค„์–ด๋“  ๊ฒƒ ๊ฐ™๋‹ค. ๋˜ํ•œ, Optional์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , ํƒ€์ž… ์„ธ์ดํ”„ํ•œ ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

profile
๐Ÿ€

0๊ฐœ์˜ ๋Œ“๊ธ€