๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ/spring&springboot

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] My Select Shop(์ƒํ’ˆ ํด๋”)

by alswlfl 2026. 6. 30.

์ƒํ’ˆ ํด๋” ์„ค๊ณ„

์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„

ํด๋” ๋ณ„๋กœ ๊ด€์‹ฌ์ƒํ’ˆ์„ ์ €์žฅ/๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ ์ถ”๊ฐ€

  • ํด๋” ์ƒ์„ฑ
    - ํšŒ์›๋ณ„ ํด๋” ์ถ”๊ฐ€
    - ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ 1๊ฐœ ~ N๊ฐœ ํ•œ๋ฒˆ์— ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
  • ๊ด€์‹ฌ์ƒํ’ˆ์— ํด๋” ์„ค์ •
    - ๊ด€์‹ฌ์ƒํ’ˆ์— ํด๋”๋Š” N๊ฐœ ์„ค์ • ๊ฐ€๋Šฅ
    - ๊ด€์‹ฌ์ƒํ’ˆ์ด ๋“ฑ๋ก๋˜๋Š” ์‹œ์ ์—๋Š” ์–ด๋А ํด๋”์—๋„ ์ €์žฅ๋˜์ง€ ์•Š์Œ
    - ๊ด€์‹ฌ์ƒํ’ˆ ๋ณ„๋กœ 1๋ฒˆ์—์„œ ์ƒ์„ฑํ•œ ํด๋”๋ฅผ ์„ ํƒํ•ด ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
  • ํด๋” ๋ณ„ ์กฐํšŒ
    - ํšŒ์›์€ ํด๋” ๋ณ„๋กœ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ ๊ฐ€๋Šฅ
    - ์กฐํšŒ ๋ฐฉ๋ฒ•
    • '์ „์ฒด' ํด๋ฆญ ์‹œ: ํด๋”์™€ ์ƒ๊ด€์—†์ด ํšŒ์›์ด ์ €์žฅํ•œ ์ „์ฒด ๊ด€์‹ฌ์ƒํ’ˆ๋“ค ์กฐํšŒ
    • 'ํด๋”๋ช…' ํด๋ฆญ ์‹œ: ํด๋”๋ณ„ ์ €์žฅ๋œ ๊ด€์‹ฌ์ƒํ’ˆ๋“ค์„ ์กฐํšŒ

ํด๋”์™€ ํšŒ์› ๊ด€๊ณ„

  • ํด๋”๋ช…(name): ํšŒ์›์ด ๋“ฑ๋กํ•œ ํด๋” ์ด๋ฆ„ ์ €์žฅ
  • ํด๋” : ํšŒ์› = N : 1 ๊ด€๊ณ„
  • ํšŒ์› ๊ฐ์ฒด์—์„œ ํด๋” ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํด๋”์™€ ํšŒ์›์„ N : 1 ๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋กœ ์„ค์ •

ํด๋” Entity์™€ Repository ์ƒ์„ฑ

package com.sparta.myselectshop.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "folder")
public class Folder {

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

    @Column(nullable = false)
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    public Folder(String name, User user) {
        this.name = name;
        this.user = user;
    }
}

์ƒํ’ˆ๊ณผ ํด๋” ๊ด€๊ณ„

1. ์ƒํ’ˆ์€ ์ถ”๊ฐ€๋  ํด๋”์˜ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•จ

์ƒํ’ˆ ์ž…์žฅ

  • ๊ด€์‹ฌ ์ƒํ’ˆ์— ํด๋” ๋“ฑ๋ก
  • ์ƒํ’ˆ์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํด๋”๋ฅผ ๋“ฑ๋ก ๊ฐ€๋Šฅ
  • ํด๋” : ์ƒํ’ˆ = N : 1

2. ํด๋”์— ๊ด€์‹ฌ ์ƒํ’ˆ์„ ๋“ฑ๋ก

ํด๋” ์ž…์žฅ

  • ํ•˜๋‚˜์˜ ํด๋”๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ƒํ’ˆ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Œ
  • ํด๋” : ์ƒํ’ˆ = 1 : N

3. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ƒํ’ˆ๊ณผ ํด๋”๋Š” N : M ๊ด€๊ณ„

์ƒํ’ˆ๊ณผ ํด๋” ๊ด€๊ณ„

  • ์ƒํ’ˆ_ํด๋” ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ƒ์„ฑ
  • ์ƒํ’ˆ : ์ƒํ’ˆ_ํด๋” = 1 : N
  • ํด๋” : ์ƒํ’ˆ_ํด๋” = 1 : N

4. ์—ฐ๊ด€๊ด€๊ณ„์˜ ๋ฐฉํ–ฅ ์„ ํƒ

  • ๊ด€์‹ฌ ์ƒํ’ˆ์„ ์กฐํšŒํ•  ๋•Œ ํฌํ•จ๋œ ํด๋”๋“ค์˜ ์ •๋ณด ํ•„์š”(์ƒํ’ˆ ๊ฐ์ฒด๊ฐ€ ํด๋” ๊ฐ์ฒด ์กฐํšŒ ํ•„์š”)
  • ์ƒํ’ˆ ๊ฐ์ฒด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•ด๋‹น ํด๋”์— ํฌํ•จ๋œ ์ƒํ’ˆ๋“ค์„ ์กฐํšŒ(์ƒํ’ˆ ๊ฐ์ฒด๊ฐ€ ํด๋” ๊ฐ์ฒด ์กฐํšŒ ํ•„์š”)
  • ๋”ฐ๋ผ์„œ ์ƒํ’ˆ๊ณผ ์ƒํ’ˆ_ํด๋”๋Š” ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋กœ ์„ค์ •
  • ํด๋” ๊ฐ์ฒด์—์„œ ์ƒํ’ˆ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜์ง€ ์•Š์„ ์˜ˆ์ •์ด๊ธฐ ๋•Œ๋ฌธ์— ํด๋”๋Š” ์ƒํ’ˆ_ํด๋”์™€ ๊ด€๊ณ„ ๋งบ์ง€ ์•Š์Œ!

์ƒํ’ˆ_ํด๋” Entity ๋ฐ Repository ์ƒ์„ฑ

package com.sparta.myselectshop.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "product_folder")
public class ProductFolder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "folder_id", nullable = false)
    private Folder folder;

    public ProductFolder(Product product, Folder folder) {
        this.product = product;
        this.folder = folder;
    }
}

 

Product ์—”ํ‹ฐํ‹ฐ ๋‚ด ์ƒํ’ˆ_ํด๋” ์กฐํšŒ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ˆ˜์ •

public class Product extends Timestamped {
	...
    
    @OneToMany(mappedBy = "product")
    private List<ProductFolder> productFolderList = new ArrayList<>();

    ...
}

ํด๋” ์ƒ์„ฑ ๋ฐ ์กฐํšŒ ๊ตฌํ˜„

  • ํšŒ์›๋ณ„ ํด๋” ์ถ”๊ฐ€(ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ๊ฐœ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)
  • ํšŒ์›๋ณ„ ์ €์žฅํ•œ ํด๋”๋“ค ์กฐํšŒ

API ์„ค๊ณ„

์„ค๋ช… URL Request Response
ํšŒ์›์˜ ํด๋” ์ƒ์„ฑ POST /api/folders {
folderName: [String, ...]
}
 
ํšŒ์›์˜ ํด๋” ์กฐํšŒ GET /api/user-folder   index.html
model ์ถ”๊ฐ€ → folders

 

ํด๋” ์ƒ์„ฑ API ๊ตฌํ˜„

  • controller > FolderController ๋‚ด ํด๋” ์ƒ์„ฑ API ์ถ”๊ฐ€
package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.FolderRequestDto;
import com.sparta.myselectshop.security.UserDetailsImpl;
import com.sparta.myselectshop.service.FolderService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class FolderController {

    private final FolderService folderService;

    @PostMapping("/folders")
    public void addFolders(@RequestBody FolderRequestDto folderRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        List<String> folderNames = folderRequestDto.getFolderNames();

        folderService.addFolders(folderNames, userDetails.getUser());
    }
}
package com.sparta.myselectshop.dto;

import lombok.Getter;

import java.util.List;

@Getter
public class FolderRequestDto {
    List<String> folderNames;
}
  • service > FolderService ๋‚ด ํด๋” ์ƒ์„ฑ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ƒ์„ฑ
package com.sparta.myselectshop.service;

import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.repository.FolderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class FolderService {

    private final FolderRepository folderRepository;

    public void addFolders(List<String> folderNames, User user) {
        // ํด๋”๊ฐ€ ์ค‘๋ณต์ธ์ง€ ํ™•์ธ
        // ์œ ์ € ๊ธฐ์ค€์œผ๋กœ ์ฐพ๊ณ , ํด๋” ์ด๋ฆ„์ด ์—ฌ๋Ÿฌ๊ฐœ(In)
        List<Folder> existFolderList = folderRepository.findAllByUserAndNameIn(user, folderNames);

        List<Folder> folderList = new ArrayList<>();

        for(String folderName : folderNames) {
            if(!isExistFolderName(folderName, existFolderList)) {
                // ์ค‘๋ณต๋˜์ง€ ์•Š์„ ๋•Œ ํด๋” ์ €์žฅ
                Folder folder = new Folder(folderName, user);
                folderList.add(folder);
            } else {
                throw new IllegalArgumentException("ํด๋”๋ช…์ด ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
            }
        }

        folderRepository.saveAll(folderList);
    }

    // ํ•ด๋‹น ์ด๋ฆ„์„ ๊ฐ€์ง„ ํด๋”๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฉ”์„œ๋“œ
    private boolean isExistFolderName(String folderName, List<Folder> existFolderList) {
        for (Folder existFolder : existFolderList) {
            if(folderName.equals(existFolder.getName())) {
                return true;
            }
        }
        return false;
    }
}
package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.Folder;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface FolderRepository extends JpaRepository<Folder, Long> {
    List<Folder> findAllByUserAndNameIn(User user, List<String> folderNames);
    // select * from folder where user_id = ? and name in (?, ?, ...);
}

ํด๋” ์กฐํšŒ API ๊ตฌํ˜„

  • UserController ๋‚ด ํด๋” ์กฐํšŒ API ์ถ”๊ฐ€
public class UserController {

    private final UserService userService;
    private final FolderService folderService;

	...

    @GetMapping("/user-folder")
    public String getUserInfo(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        model.addAttribute("folders", folderService.getFolders(userDetails.getUser()));

        return "index :: #fragment";
    }
}
  • FolderService ๋‚ด ํด๋” ์กฐํšŒํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ถ”๊ฐ€
package com.sparta.myselectshop.dto;

import com.sparta.myselectshop.entity.Folder;
import lombok.Getter;

@Getter
public class FolderResponseDto {
    private Long id;
    private String name;

    public FolderResponseDto(Folder folder) {
        this.id = folder.getId();
        this.name = folder.getName();
    }
}
public class FolderService {

    private final FolderRepository folderRepository;

    public List<FolderResponseDto> getFolders(User user) {
        List<Folder> folderList = folderRepository.findAllByUser(user);
        List<FolderResponseDto> responseDtoList = new ArrayList<>();

        for(Folder folder : folderList) {
            responseDtoList.add(new FolderResponseDto(folder));
        }
        
        return responseDtoList;
    }
}
  • Product ์กฐํšŒ ์‹œ, ํด๋” ์ •๋ณด๋„ ๊ฐ™์ด ํฌํ•จ์‹œํ‚ค๊ธฐ
package com.sparta.myselectshop.dto;

import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.entity.ProductFolder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@NoArgsConstructor
public class ProductResponseDto {
    private Long id;
    private String title;
    private String link;
    private String image;
    private int lprice;
    private int myprice;

    private List<FolderResponseDto> productFolderList = new ArrayList<>();

    public ProductResponseDto(Product product) {
        this.id = product.getId();
        this.title = product.getTitle();
        this.link = product.getLink();
        this.image = product.getImage();
        this.lprice = product.getLprice();
        this.myprice = product.getMyprice();
        for (ProductFolder productFolder : product.getProductFolderList()) {
            productFolderList.add(new FolderResponseDto(productFolder.getFolder()));
        }
    }
}
public class ProductService {

    private final ProductRepository productRepository;

    public static final int MIN_MY_PRICE = 100;

    @Transactional(readOnly = true) // ํŠธ๋žœ์žญ์…˜ ์ถ”๊ฐ€: ์ง€์—ฐ๋กœ๋”ฉ, ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•ด readOnly ์„ค์ •
    public Page<ProductResponseDto> getProducts(User user, int page, int size, String sortBy, boolean isAsc) {
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy); // Sort ๊ฐ์ฒด = (direction ๋ฐฉํ–ฅ, ์ •๋ ฌ ํ•ญ๋ชฉ)
        Pageable pageable = PageRequest.of(page, size, sort);

        // ํ˜„์žฌ ๋“ค์–ด์˜จ User์˜ ๊ถŒํ•œ์ด Admin์ธ์ง€ User์ธ์ง€ ํ™•์ธ
        UserRoleEnum userRoleEnum = user.getRole();

        Page<Product> productList;

        if(userRoleEnum == UserRoleEnum.USER) {
            productList = productRepository.findAllByUser(user, pageable);
        } else {
            productList = productRepository.findAll(pageable);
        }

        return productList.map(ProductResponseDto::new);
    }
}

ํด๋” ์ƒ์„ฑ ๋ฐ ์กฐํšŒ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ


๊ด€์‹ฌ ์ƒํ’ˆ์— ํด๋” ์ถ”๊ฐ€ ๊ตฌํ˜„

  • ๊ด€์‹ฌ์ƒํ’ˆ์— ํด๋”๋Š” 0๊ฐœ ~ N๊ฐœ ์„ค์ • ๊ฐ€๋Šฅ

์˜ˆ์‹œ

  • ๊ด€์‹ฌ์ƒํ’ˆ์ด ๋“ฑ๋ก๋˜๋Š” ์‹œ์ ์—๋Š” ์–ด๋А ํด๋”์—๋„ ์ €์žฅ๋˜์ง€ ์•Š์Œ
  • ๊ด€์‹ฌ์ƒํ’ˆ ๋ณ„๋กœ ๊ธฐ ์ƒ์„ฑํ–ˆ๋˜ ํด๋”๋ฅผ ์„ ํƒํ•ด ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

์˜ˆ์‹œ

๊ด€์‹ฌ ์ƒํ’ˆ์— ํด๋” ์ถ”๊ฐ€ API ์„ค๊ณ„

์„ค๋ช… URL Request Response
ํด๋” ์ „์ฒด ์กฐํšŒ GET /api/folders   List<FolderResponseDto>
ํด๋” ์ถ”๊ฐ€ POST /api/products/{productId}/folder {productId}: ๊ด€์‹ฌ์ƒํ’ˆ ID

[Form ํ˜•ํƒœ]
folderId: ์ถ”๊ฐ€ํ•  ํด๋” ID
 

๊ด€์‹ฌ ์ƒํ’ˆ์— ํด๋” ์ถ”๊ฐ€ API ๊ตฌํ˜„

ํด๋” ์ „์ฒด ์กฐํšŒ API

  • FolderController ๋‚ด ์ „์ฒด ์กฐํšŒ API ์ถ”๊ฐ€
public class FolderController {

    private final FolderService folderService;

    ...

    @GetMapping("/folders")
    public List<FolderResponseDto> getFolders(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        return folderService.getFolders(userDetails.getUser());
    }
}

 

๊ด€์‹ฌ ์ƒํ’ˆ์— ํด๋” ์ถ”๊ฐ€ API

  • ProductController ๋‚ด API ์ถ”๊ฐ€
public class ProductController {

    private final ProductService productService;
	
    ...
    
    @PostMapping("products/{productId}/folder")
    public void addFolder(
            @PathVariable Long productId,
            @RequestParam Long folderId,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        productService.addFolder(productId, folderId, userDetails.getUser());
    }
}
  • ProductService ๋‚ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ถ”๊ฐ€
public class ProductService {

    private final ProductRepository productRepository;
    private final FolderRepository folderRepository;
    private final ProductFolderRepository productFolderRepository;

    public void addFolder(Long productId, Long folderId, User user) {
        Product product = productRepository.findById(productId).orElseThrow(
                () -> new NullPointerException("ํ•ด๋‹น ์ƒํ’ˆ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

        Folder folder = folderRepository.findById(folderId).orElseThrow(
                () ->  new NullPointerException("ํ•ด๋‹น ํด๋”๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

        // ํ•ด๋‹น ์ƒํ’ˆ ํ˜น์€ ํด๋” ๋‘˜ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋“ฑ๋กํ•œ๊ฒŒ ์•„๋‹Œ ๊ฒฝ์šฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
        if(!product.getUser().getId().equals(user.getId())
        || !folder.getUser().getId().equals(user.getId())) {
            throw new IllegalArgumentException("ํšŒ์›๋‹˜์˜ ๊ด€์‹ฌ์ƒํ’ˆ์ด ์•„๋‹ˆ๊ฑฐ๋‚˜, ํšŒ์›๋‹˜์˜ ํด๋”๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
        }

        // ์ƒํ’ˆ-ํด๋” ์ค‘๋ณต ํ™•์ธ
        Optional<ProductFolder> overlapFolder =  productFolderRepository.findByProductAndFolder(product, folder);

        if(overlapFolder.isPresent()) {
            throw new IllegalArgumentException("์ค‘๋ณต๋œ ํด๋”์ž…๋‹ˆ๋‹ค.");
        }
        
        productFolderRepository.save(new ProductFolder(product, folder));
    }
}

๊ด€์‹ฌ ์ƒํ’ˆ ํด๋” ์ถ”๊ฐ€ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ


ํด๋” ๋ณ„ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ ๊ตฌํ˜„

  • ํšŒ์›์€ ํด๋” ๋ณ„๋กœ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ ๊ฐ€๋Šฅ

ํด๋” ๋ณ„ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ API ์„ค๊ณ„

์„ค๋ช… URL Request Response
ํด๋”๋ณ„ ์ƒํ’ˆ ์กฐํšŒ GET /api/folders/{folderId}/products {folderId}: ์กฐํšŒ๋ฅผ ์›ํ•˜๋Š” ํด๋” id Page<ProductResponseDto>

ํด๋” ๋ณ„ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ API ๊ตฌํ˜„

  • ProductController ๋‚ด ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ API ์ถ”๊ฐ€
public class ProductController {

    private final ProductService productService;

    @GetMapping("/folders/{folderId}/products")
    public Page<ProductResponseDto> getProductsInFolder(
            @PathVariable Long folderId,
            @RequestParam("page") int page,
            @RequestParam("size") int size,
            @RequestParam("sortBy") String sortBy,
            @RequestParam("isAsc") boolean isAsc,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        return productService.getProductsInFolder(
                folderId,
                page-1,
                size,
                sortBy,
                isAsc,
                userDetails.getUser()
        );
    }
}
  • ProductService์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ถ”๊ฐ€
public class ProductService {

    private final ProductRepository productRepository;
    private final FolderRepository folderRepository;
    private final ProductFolderRepository productFolderRepository;

    public Page<ProductResponseDto> getProductsInFolder(Long folderId, int page, int size, String sortBy, boolean isAsc, User user) {
        Sort.Direction direction = isAsc ? Sort.Direction.ASC : Sort.Direction.DESC;
        Sort sort = Sort.by(direction, sortBy); // Sort ๊ฐ์ฒด = (direction ๋ฐฉํ–ฅ, ์ •๋ ฌ ํ•ญ๋ชฉ)
        Pageable pageable = PageRequest.of(page, size, sort);

        // ํ•ด๋‹น ํด๋”์— ๋“ฑ๋ก๋˜์–ด์žˆ๋Š” ์ƒํ’ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
        Page<Product> productList =  productRepository.findAllByUserAndProductFolderList_FolderId(user, folderId, pageable);

        Page<ProductResponseDto> responseDtoList = productList.map(ProductResponseDto::new);

        return responseDtoList;
    }
}
package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
    Page<Product> findAllByUser(User user, Pageable pageable);

    Page<Product> findAllByUserAndProductFolderList_FolderId(User user, Long folderId, Pageable pageable);
    /*
    * select *
    * from product p
    * left join product_folder pf
    * on p.id = pf.product_id
    * where p.user_id = ?
    * and pf.folder_id = ?
    * order by p.id asc|desc
    * limit 0, 10; (ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ, ํ•œ ํŽ˜์ด์ง€ ๋‹น ๋ณด์—ฌ์ค„ ๊ฐœ์ˆ˜)
    * */
}

ํด๋” ๋ณ„ ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ

'๋ฐฑ์—”๋“œ > spring&springboot' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์‹ฌํ™”์ฃผ์ฐจ] ํ…Œ์ŠคํŠธ(๋‹จ์œ„ ํ…Œ์ŠคํŠธ, Mockito, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ)  (0) 2026.07.01
[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์‹ฌํ™”์ฃผ์ฐจ] ์†Œ์…œ ๋กœ๊ทธ์ธ(OAuth), ์นด์นด์˜ค ๋กœ๊ทธ์ธ  (0) 2026.07.01
[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] My Select Shop(ํšŒ์›๊ธฐ๋Šฅ, ์ƒํ’ˆ ํŽ˜์ด์ง• ๋ฐ ์ •๋ ฌ ๊ตฌํ˜„)  (0) 2026.06.30
[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] My Select Shop(์ƒํ’ˆ ๊ฒ€์ƒ‰, ๊ด€์‹ฌ ์ƒํ’ˆ ๋“ฑ๋ก, ๊ด€์‹ฌ ์ƒํ’ˆ ํฌ๋ง ์ตœ์ €๊ฐ€ ์ˆ˜์ •, ๊ด€์‹ฌ ์ƒํ’ˆ ์กฐํšŒ API ๊ตฌํ˜„, Scheduler ๊ตฌํ˜„)  (0) 2026.06.30
[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] JPA ํ•œ ๊ฑธ์Œ ๋” ๋‚˜์•„๊ฐ€๊ธฐ(์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ์ฆ‰์‹œ ๋กœ๋”ฉ, ์˜์†์„ฑ ์ „์ด, ๊ณ ์•„ Entity)  (0) 2026.06.30