Spring

[Spring] 나만의 셀렉샵 프로젝트 - 서버(API, Spring MVC, JPA)

jelliclesu 2024. 7. 29. 22:36

프로젝트 설계하기

필요 기능

  • 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
  • 관심 상품 등록하기
  • 관심 상품 조회하기
  • 관심 상품에 원하는 가격 등록하고, 그 가격보다 낮은 경우 표시하기

 

API 설계

기능 Method URL 반환
키워드로 상품 검색하고 그 결과를 목록으로 보여주기 GET /api/search?query=검색어 List<ItemDto>
관심 상품 등록하기 POST /api/products Product
관심 상품 조회하기 GET /api/products List<ItemDto>
관심 상품에 원하는 가격 등록하고, 그 가격보다 낮은 경우 표시하기 PUT /api/products/{id} id

 

 

3계층 설계

  1. Controller
    • ProductRestController: 관심 상품 관련 컨트롤러
    • SearchRequestController: 검색 관련 컨트롤러
  2. Service
    • ProductService: 관심 상품 가격 변경
  3. Repository
    • Product: 관심 상품 테이블
    • ProductRepository: 관심 상품 조회, 저장
    • ProductRequestDto: 관심 상품 등록
    • ProductMypriceRequestDto: 관심 가격 변경
    • ItemDto: 검색 결과 주고받기

 

 

관심 상품 조회

 

요구 조건

- "모아보기" 탭 클릭 시, 등록된 관심 상품 조회 기능

  • Timestamped.java 생성 ( + Week04Application@EnableJpaAuditing 추가하여 시간 자동 변경이 가능하도록 설정)
    @Getter // get 함수 자동 생성
    @MappedSuperclass   // 멤버 변수가 컬럼이 되도록 함
    @EntityListeners(AuditingEntityListener.class)  // 변경시 자동 기록
    public abstract class Timestamped {
        @CreatedDate    // 최초 생성 시점
        private LocalDateTime createdAt;
        
        @LastModifiedDate   // 마지막 변경 시점
        private LocalDateTime modifiedAt;
    }

 

  • Product.java 생성 (title, image, link, lprice, myprice 정보 필요)
    @Getter // get 함수 일괄 생성
    @NoArgsConstructor  // 기본 생성자 생성
    @Entity // DB 테이블 역할
    public class Product extends Timestamped{
        
        // ID 자동 생성 및 증가
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Id
        private Long id;
        
        // 반드시 값을 가지도록 함
        @Column(nullable = false)
        private String title;
        
        @Column(nullable = false)
        private String image;
    
        @Column(nullable = false)
        private String link;
    
        @Column(nullable = false)
        private int lprice;
    
        @Column(nullable = false)
        private int myprice;
    }

 

  • ProductRepository.java
    public interface ProductRepository extends JpaRepository<Product, Long> {
    }

 

  • ProductRestController.java
    @RequiredArgsConstructor    // final 로 선언된 멤버 변수 자동 생성
    @RestController //  JSON 으로 데이터 주고받음 선언
    public class ProductRestController {
    
        private final ProductRepository productRepository;
    
        // 등록된 전체 상품 목록 조회
        @GetMapping("/api/products")
        public List<Product> getProducts() {
            return productRepository.findAll();
        }
    }

 

 

관심 상품 등록

 

요구 조건

- 상품을 검색한 후, 등록 버튼 클릭시 관심 상품 생성되어야 함

- 검색 결과에서 제목, 이미지, 링크, 최저가 가져오기

  • ProductRequestDto.java 생성
    @Getter
    public class ProductRequestDto {
        private String title;
        private String link;
        private String image;
        private int lprice;
    }

 

  • ProductMypriceRequestDto.java 생성(관심 가격 등록 - 최저가)
    @Getter
    public class ProductMypriceRequestDto {
    	private int myprice;
    }

 

  • Product.java에 생성자 추가
    // 관심 상품 생성 시 이용
    public Product(ProductRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.image = requestDto.getImage();
        this.link = requestDto.getLink();
        this.lprice = requestDto.getLprice();
        this.myprice = 0;
    }
    
    // 관심 가격 변경 시 이용
    public void update(ProductMypriceRequestDto requestDto) {
        this.myprice = requestDto.getMyprice();
    }

 

  • ProductService.java 생성
    @RequiredArgsConstructor    // final 로 선언된 멤버 변수 자동 생성
    @Service    // 서비스임 선언
    public class ProductService {
        
        private final ProductRepository productRepository;
        
        @Transactional  // 메소드 동작이 SQL 쿼리문임 선언
        public Long update(Long id, ProductMypriceRequestDto requestDto) {
            Product product = productRepository.findById(id).orElseThrow(
                    () -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
            );
            product.update(requestDto);
            return id;
        }
    }

 

  • ProductRestController.java에 신규 상품 등록 API 추가 (POST 방식)
    @RequiredArgsConstructor    // final 로 선언된 멤버 변수 자동 생성
    @RestController //  JSON 으로 데이터 주고받음 선언
    public class ProductRestController {
    
        private final ProductRepository productRepository;
        private final ProductService productService;
    
        // 등록된 전체 상품 목록 조회
        @GetMapping("/api/products")
        public List<Product> getProducts() {
            return productRepository.findAll();
        }
        
        // 신규 상품 등록
        @PostMapping("/api/products")
        public Product createProduct(@RequestBody ProductRequestDto requestDto) {
            Product product = new Product(requestDto);
            productRepository.save(product);
            return product;
        }
    }

 

 

최저가 변경

  • ProductRestController.java에 최저가 변경 API 추가 (PUT 방식)
    // 설정 가격 변경
    @PutMapping("/api/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) {
        return productService.update(id, requestDto);
    }

 

 

Scheduler

- 매일 새벽 1시에 관심 상품 목록 제목으로 검색하여 최저가 정보를 업데이트

  • src > main > java > com.sparta.week04 > utils 에 Scheduler.java 파일 생성
    @RequiredArgsConstructor    // final 로 선언된 멤버 변수 자동 생성
    @Component  // 스프링이 필요시 자동으로 생성하는 클래스 목록에 추가
    public class Scheduler {
    
        private final ProductService productService;
        private final ProductRepository productRepository;
        private final NaverShopSearch naverShopSearch;
    
        // 초, 분, 시, 일, 월, 주 순서
        @Scheduled(cron = "0 0 1 * * *")
        public void updatePrice() throws InterruptedException {
            System.out.println("가격 업데이트 실행");
            // 저장된 모든 관심상품을 조회합니다.
            List<Product> productList = productRepository.findAll();
            for (int i = 0; i < productList.size(); i++) {
                // 1초에 한 상품 씩 조회합니다 (Naver 제한)
                TimeUnit.SECONDS.sleep(1);
                // i 번째 관심 상품을 꺼냅니다.
                Product p = productList.get(i);
                // i 번째 관심 상품의 제목으로 검색을 실행합니다.
                String title = p.getTitle();
                String resultString = naverShopSearch.search(title);
                // i 번째 관심 상품의 검색 결과 목록 중에서 첫 번째 결과를 꺼냅니다.
                List<ItemDto> itemDtoList = naverShopSearch.fromJSONtoItems(resultString);
                ItemDto itemDto = itemDtoList.get(0);
                // i 번째 관심 상품 정보를 업데이트합니다.
                Long id = p.getId();
                productService.updateBySearch(id, itemDto);
            }
        }
    }

 

  • ProductService.java 에 내용 추가하기
    @Transactional  // 메소드 동작이 SQL 쿼리문임 선언
    public Long updateBySearch(Long id, ItemDto itemDto) {
        Product product = productRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
        );
        product.updateByItemDto(itemDto);
        return id;
    }

 

  • Product.java 에 내용 추가하기
    // 최저가 변경 시 이용
    public void updateByItemDto(ItemDto itemDto) {
        this.lprice = itemDto.getLprice();
    }

 

  • Week04Application.java@EnableScheduling 추가하여 스프링 부트에서 스케줄러가 작동하게 함