Spring 브랜드콘 클론

춤인형의 개발일지·2025년 1월 2일

Spring실습

목록 보기
19/40

25/1/2(목)

토스 브랜드콘 클론

1. 카테고리 목록 조회

  • Method | URL
    GET | /categories
  • Endpoints
    http://localhost:8080/categories
  • Request : 전체가 보이기 때문에 x
  • Response
    • List<CategoryResponse>
      • Long id
      • String name
      • String slug
      • String imageUrl
//ex)
[
    {
        "id": 1,
        "name": "카페",
        "slug": "cafe",
        "imageUrl": "/icons/cafe.png"
    },
    {
        "id": 2,
        "name": "상품권",
        "slug": "gift-card",
        "imageUrl": "/icons/giftcard.png"
    },
    {
        "id": 3,
        "name": "치킨",
        "slug": "chicken",
        "imageUrl": "/icons/chicken.png"
    },
    {
        "id": 4,
        "name": "피자·버거",
        "slug": "pizza-burger",
        "imageUrl": "/icons/pizza.png"
    },
    {
        "id": 5,
        "name": "편의점",
        "slug": "convenience-store",
        "imageUrl": "/icons/convenience.png"
    },
    {
        "id": 6,
        "name": "외식",
        "slug": "dining",
        "imageUrl": "/icons/dining.png"
    },
    {
        "id": 7,
        "name": "디저트",
        "slug": "dessert",
        "imageUrl": "/icons/dessert.png"
    }
]

1. DTO 설계

public record CategoryResponse(
        Long id,
        String name,
        String slug,
        String imageUrl) {
}

2. API spec 토대로 controller작성

@RestController
public class CategoryRestController {

    private final CategoryService categoryService;

    public CategoryRestController(CategoryService categoryService) {
        this.categoryService = categoryService;
    }

    @GetMapping("/categories")
    public List<CategoryResponse> categoryRead(){
        return categoryService.readAll();
    }

3. Entity

@Entity
public class Brand {

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

    @ManyToOne //하나의 카테고리에 여러개의 브랜드가 들어감
    private Category category;

    public Brand() {
    }

    public Brand(String name, String imageUrl, String guidelines) {
        this.name = name;
        this.imageUrl = imageUrl;
        this.guidelines = guidelines;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public String getGuidelines() {
        return guidelines;
    }

    public Category getCategory() {
        return category;
    }
}

4. Repository & service 작성

//repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
}

//service
@Service
public class CategoryService {

    private final CategoryRepository categoryRepository;

    public CategoryService(CategoryRepository categoryRepository) {
        this.categoryRepository = categoryRepository;
    }

    public List<CategoryResponse> readAll() {
        List<Category> allCategory = categoryRepository.findAll();
        return allCategory.stream().map(category -> new CategoryResponse(
                category.getId(),
                category.getName(),
                category.getSlug(),
                category.getImageUrl()))
                .toList();
    }

전체를 읽어야 하니까 all이 붙어야한다.


2. 브랜드 목록 조회

[
    {
        "id": 1,
        "name": "메가MGC커피",
        "imageUrl": "icons/megacoffee.png"
    }
]

1.DTO 설계

public record BrandResponse(
        Long id,
        String name,
        String imageUrl
) {
}

2. Controller

@RestController
public class BrandController {

    private final BrandService brandService;

    public BrandController(BrandService brandService) {
        this.brandService = brandService;
    }

    @GetMapping("/brands")
    public List<BrandResponse> readBrands(@RequestParam String category){
        return brandService.read(category);
    }

3. Entity 설계

@Entity
public class Brand {

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

    @ManyToOne //하나의 카테고리에 여러개의 브랜드가 들어감
    private Category category;

    public Brand() {
    }

    public Brand(String name, String imageUrl) {
        this.name = name;
        this.imageUrl = imageUrl;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public Category getCategory() {
        return category;
    }
}

4. Repository & Service

//repository
public interface BrandRepository extends JpaRepository<Brand, Long> {
}

//Service
@Service
public class BrandService {

    private final BrandRepository brandRepository;

    public BrandService(BrandRepository brandRepository) {
        this.brandRepository = brandRepository;
    }

    public List<BrandResponse> read(String category) {
        List<Brand> categorySlug = brandRepository.findByCategorySlug(category);
        return categorySlug.stream().map(brand -> new BrandResponse(
                brand.getId(),
                brand.getName(),
                brand.getImageUrl()
        )).toList();
    }

나는 category의 Slug를 찾아야한다. 하지만, brandRepository 속에는 slug의 정보가 없으니까 JPA쿼리메서드로 만들어준다.

List<Brand> findByCategorySlug(String slug);

3. 브랜드 상세 조회

{
    "id": 1,
    "name": "메가MGC커피",
    "imageUrl": ""
}

1. controller수정

 @GetMapping("/brands/{brandId}")
    public BrandResponse readBrand(@PathVariable Long brandId){
        return brandService.oneBrandRead(brandId);
    }

2. Service 수정

public BrandResponse oneBrandRead(Long brandId) {
        Brand brand = brandRepository.findById(brandId).orElseThrow();
        return new BrandResponse(brandId, brand.getName(), brand.getImageUrl());
    }

4. 상품 목록 조회

1. DTO 설계

public record ProductResponse(
        Long id,
        String brandName,
        String productName,
        int price,
        String imageUrl
) {
}

2. controller

@RestController
public class ProductRestController {
    private final ProductService productService;

    public ProductRestController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/products")
    public List<ProductResponse> productOfbrandRead(@RequestParam Long brandId){
        return productService.productOfBrand(brandId);
    }


}

3. Entity

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String brandName;
    private String productName;
    private int price;
    private String imageUrl;

    @ManyToOne
    private Brand brand;

    public Product() {
    }

    public Product(String brandName, String productName, int price, String imageUrl) {
        this.brandName = brandName;
        this.productName = productName;
        this.price = price;
        this.imageUrl = imageUrl;
    }

    public Long getId() {
        return id;
    }

    public String getBrandName() {
        return brandName;
    }

    public String getProductName() {
        return productName;
    }

    public int getPrice() {
        return price;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public Brand getBrand() {
        return brand;
    }
}

4. Repository & Service

//Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
	List<Product> findByBrandId(Long brandId);
}

//Service
@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public List<ProductResponse> productOfBrand(Long brandId) {
        List<Product> byBrandId = productRepository.findByBrandId(brandId);
        return byBrandId.stream().map(product -> new ProductResponse(
                product.getId(),
                product.getBrandName(),
                product.getProductName(),
                product.getPrice(),
                product.getImageUrl())).toList();
    }
}

brandId를 받아서 brand를 나눠줘야하기 때문에 brandId를 받는 JPA쿼리 메서드를 작성해주었다.


5. 인기 상품 목록 조회

1. controller

@GetMapping("/products/popular")
    public List<ProductResponse> popularProduct(@RequestParam(required = false)Long categoryId, Long brandId){
        return productService.popularRead(categoryId, brandId);
    }

request param으로 받아, category일때와 brand가 들어왔을 때를 구분해준다.

2. service & Repository

public List<ProductResponse> popularRead(Long categoryId, Long brandId) {
        if (brandId == null) {
            return categoryPopular(categoryId);
        }
        else return brandPopluar(brandId);
    }

brandId가 안들어왔을 땐, categoryId가 들어온거니까, categoryId의 상품을 조회하는 함수로 넘어가고 brandId가 들어왔을 땐, 반대로 실행시켜준다.

    private List<ProductResponse> categoryPopular(Long categoryId) {
        List<Product> byBrandCategoryId = productRepository.findByBrandCategoryId(categoryId);
        return byBrandCategoryId.stream().map(product -> new ProductResponse(
                product.getId(),
                product.getBrandName(),
                product.getProductName(),
                product.getPrice(),
                product.getImageUrl()
        )).toList();
    }

    private List<ProductResponse> brandPopluar(Long brandId) {
        List<Product> byBrandId = productRepository.findByBrandId(brandId);
        return byBrandId.stream().map(product -> new ProductResponse(
                product.getId(),
                product.getBrandName(),
                product.getProductName(),
                product.getPrice(),
                product.getImageUrl()
        )).toList();
    }

Category는 brand를 참조하고 있기 때문에, brand의 id값을 찾기 위해선 category에 접근한 JPA쿼리 메서드를 Repository에 저장해준다.

List<Product> findByBrandCategoryId(Long categoryId);

그걸 받아서 사용한다.


6. 상품 상세조회

{
    "productId": 1,
    "productName": "(ICE)아메리카노",
    "price": 2000,
    "brand": {
        "id": 1,
        "name": "메가MGC커피",
        "guidelines": "#사용처 - 전국 메가MGC커피 매장에서 사용 가능합니다. #제한사항 - 사진은 이미지 컷이므로 실제와 다를 수 있습니다. #유의사항 - *지급보증 : 본 상품은 별도의 지급보증 및 피해보상보험계약체결 없이 자체 신용으로 발행되었습니다.",
    },
    "expirationDays": 366
}

1. DTO구현

public record detailProductResponse(
        Long productId,
        String productName,
        int price,
        int expirationDays,
        Brand brand

) {
}

2.Controller구현

@GetMapping("/products/{productId}")
    public detailProductResponse detailRead(@PathVariable Long productId){
        return productService.detailRead(productId);
    }

3. Service구현

public detailProductResponse detailRead(Long productId) {
        Product product = productRepository.findById(productId).orElseThrow();
        return new detailProductResponse(
                productId,
                product.getProductName(),
                product.getPrice(),
                product.getExpirationDays(),
                new Brand(
                        product.getBrandName(),
                        product.getImageUrl(),
                        product.getGuidelines())
                );
    }

😐 느낀점

이제 좀 알 것같다~!~!
1:N관계에서도 관계매핑하는 방법도 익숙해진 것 같고, 이제는 조금 부드러운 코딩이 된 것 같다.

0개의 댓글