Project MySelectShop - Refactoring

박영준·2022년 12월 4일
0

Java

목록 보기
21/111

MySelectShop

필요한 기능 확인하기

  1. 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
  2. 관심 상품 등록하기
  3. 관심 상품 조회하기
  4. 관심 상품 최저가 등록하기

Product API 확인하기

AllInOneController의 역할 분리

Controller

  • 클라이언트의 요청을 받음
  • 요청에 대한 처리는 서비스에게 전담
  • 클라이언트에게 응답
  • 외부 소통 창구
  • Controller 는 외부의 요청을 받아 적절한 Service를 순서에 맞게 호출까지만 담당

Service

  • 사용자의 요구사항을 처리하는 실세 중에 실세!!! (= 비즈니스 로직을 담당)
    • 현업에서는 서비스 코드가 계속 비대해짐
  • DB 정보가 필요할 때는 Repository 에게 요청
  • Service는 비즈니스로직을 처리하되, 너무 많은 역할을 짊어짐으로써 Entity 책임이 흐려지지 않도록 주의

Repository

  • DB 관리 (연결, 해제, 자원 관리)
  • DB CRUD 작업 처리
  • 비즈니스로직에 필요한 객체와 데이터베이스를 연결

참고: JpaRepository

전체적으로

AllInOneController의 코드 분리

분리하기 전의 AllInOneController

//JPA 를 사용한다면, 이런 반복되고 긴 코드를 사용할 필요가 없어진다
package com.sparta.myselectshopbeta.controller;

import com.sparta.myselectshopbeta.dto.ProductMypriceRequestDto;
import com.sparta.myselectshopbeta.dto.ProductRequestDto;
import com.sparta.myselectshopbeta.dto.ProductResponseDto;
import com.sparta.myselectshopbeta.entity.Product;
import org.springframework.web.bind.annotation.*;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api")

public class AllInOneController {
//----------Repository 로 이동!! (getProducts() 메소드를 사용해서)----------------------
    // 관심 상품 등록하기
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
//----------service 로 이동!!---------------------------------------------------------
        // 요청받은 DTO 로 DB에 저장할 객체 만들기
        Product product = new Product(requestDto);
//------------------------------------------------------------------------------------
        // DB 연결
        //저번(jpaRepository 를 사용했던)과는 다르게, Connection 과 DriverManager 를 이용해서 직접 DB 에 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");

        // DB Query 작성
        //직법 쿼리도 작성해야 했음
        PreparedStatement ps = connection.prepareStatement("select max(id) as id from product"); //JPA 에서는 Id 값을 찾아서 넣지 않아도, @GeneratedValue(strategy = GenerationType.IDENTITY) 에 의해 자동으로 생성된다
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            // product id 설정 = product 테이블의 마지막 id + 1
            product.setId(rs.getLong("id") + 1);
        } else {
            throw new SQLException("product 테이블의 마지막 id 값을 찾아오지 못했습니다.");
        }

        ps = connection.prepareStatement("insert into product(id, title, image, link, lprice, myprice) values(?, ?, ?, ?, ?, ?)");
        //아래의 값들이 values(?, ?, ?, ?, ?, ?) 안에 들어가게 됨
        ps.setLong(1, product.getId());
        ps.setString(2, product.getTitle());
        ps.setString(3, product.getImage());
        ps.setString(4, product.getLink());
        ps.setInt(5, product.getLprice());
        ps.setInt(6, product.getMyprice());

        // DB Query 실행
        ps.executeUpdate();

        // DB 연결 해제
        ps.close();     //PreparedStatement 도 직접 닫아주기
        connection.close();     //Connection 도 직접 닫아주기
//------------------------------------------------------------------------------------
        // 응답 보내기
        return new ProductResponseDto(product);

    }
//----------Repository 로 이동!! (getProduct() 메소드를 사용해서)------------------------
    // 관심 상품 조회하기
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts() throws SQLException {
        List<ProductResponseDto> products = new ArrayList<>();

        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");

        // DB Query 작성 및 실행
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery("select * from product");

        // DB Query 결과를 상품 객체 리스트로 변환
        while (rs.next()) {     //List 라서 while 문을 돌려서 가져옴
            Product product = new Product();        //객체 product 생성
            product.setId(rs.getLong("id"));
            product.setImage(rs.getString("image"));
            product.setLink(rs.getString("link"));
            product.setLprice(rs.getInt("lprice"));
            product.setMyprice(rs.getInt("myprice"));
            product.setTitle(rs.getString("title"));
            products.add(new ProductResponseDto(product));  //products 는 ProductResponseDto 를 List 형식으로 만들어놓은 ArrayList 배열
        }

        // DB 연결 해제
        rs.close();
        connection.close();
//------------------------------------------------------------------------------------
        // 응답 보내기
        return products;
    }
//----------Repository 로 이동!! (updateProducts() 메소드를 사용해서)--------------------
    // 관심 상품 최저가 등록하기
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
        Product product = new Product();    //select * from product where id = ? 를 사용해서, product 객체 안에 넣음
        // DB 연결
        Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", "sa", "");

        // DB Query 작성
        //where 문을 사용해서, 내가 등록한 product 의 id 도 같이 넣어줌
            //basic.js 에 보면, let targetId 가 전역으로 걸려있는데, 변수 targetId 안에 내가 선택한 product 의 id 가 들어가서 가져올 수 있는 것
            //@PathVariable Long id 로 가져옴
        PreparedStatement ps = connection.prepareStatement("select * from product where id = ?");
        ps.setLong(1, id);

        // DB Query 실행
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            product.setId(rs.getLong("id"));
            product.setImage(rs.getString("image"));
            product.setLink(rs.getString("link"));
            product.setLprice(rs.getInt("lprice"));
            product.setMyprice(rs.getInt("myprice"));
            product.setTitle(rs.getString("title"));
        } else {
            throw new NullPointerException("해당 아이디가 존재하지 않습니다.");
        }

        // DB Query 작성
        //update 의 where 에 받아온 id 값을 넣음
        ps = connection.prepareStatement("update product set myprice = ? where id = ?");
        ps.setInt(1, requestDto.getMyprice());
        ps.setLong(2, product.getId());

        // DB Query 실행
        ps.executeUpdate();

        // DB 연결 해제
        rs.close();
        ps.close();
        connection.close();
//------------------------------------------------------------------------------------
        // 응답 보내기 (업데이트된 상품 id)
        return product.getId();
    }
}

Refactoring 으로 분리 후

AllInOneController 의 한계점 재확인

  • 한 개의 클래스에 너무 많은 양의 코드가 존재
  1. 코드 이해가 어려움: 처음부터 끝까지 다 읽어야 코드 내용을 이해할 수 있음
    --> 3개의 클래스에 역할 별로 코드가 정리됨

  2. 현업에서는 코드 추가 혹은 변경 요청이 계속 생김 (--> Controller 의 역할)
    1) 관심 상품 등록 시 Client 에게 응답 (Response) 하는 값 변경
    : 등록된 Product 전체 정보 → 등록된 Product 의 id
    2) 최저가 (Myprice) 업데이트 조건 변경 (--> Service 의 역할)
    : Client 가 최저가를 0원 이하로 입력 → 에러 발생
    3) DB 테이블 이름 변경 (--> Repository 의 역할)
    : Product 테이블의 lprice → lowprice 변경

객체 중복 생성 문제 해결하기

ProductController

package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.service.ProductService;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLException;
import java.util.List;

@RestController
@RequestMapping("/api")
public class ProductController {

    // 멤버 변수 선언 --> 이 코드로 객체 중복 생성 문제 해결! ----------------------------
    private final ProductService productService;

    public ProductController() {
        this.productService =  new ProductService();
    }
    //------------------------------------------------------------------

    // 관심 상품 등록하기
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
        // 응답 보내기
        //Client 에게서 받아온 값 requestDto 을 productService 에 연결해서 넣어줌
        return productService.createProduct(requestDto);
    }

    // 관심 상품 조회하기
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts() throws SQLException {
        // 응답 보내기
        return productService.getProducts();
    }

    // 관심 상품 최저가 등록하기
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
        // 응답 보내기 (업데이트된 상품 id)
        return productService.updateProduct(id, requestDto);
    }

}

ProductService

package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.repository.ProductRepository;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.util.List;



@Component
public class ProductService {

    //DB에 저장하기 위해 DB 와 연결하는 Repository 를 만듦 --> 모든 각 메소드에 이 코드가 들어갔었음 --> 반복되는 코드!
    //ProductRepository productRepository = new ProductRepository();

    // 멤버 변수 선언 --> 이 코드로 객체 중복 생성 문제 해결! ----------------------------
    private final ProductRepository productRepository;

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

    public ProductResponseDto createProduct(ProductRequestDto requestDto) throws SQLException {
        // 요청받은 DTO 로 DB에 저장할 객체 만들기
        // Controller 에서 넘겨준 requestDto 파라미터값을 받아서 DB 에 저장할 객체 product 를 만듦
        Product product = new Product(requestDto);

        //Repository 쪽으로 만들었던 객체 product 를 보내줌
        return  productRepository.createProduct(product);
    }

    public List<ProductResponseDto> getProducts() throws SQLException {

        return productRepository.getProducts();
    }

    public Long updateProduct(Long id, ProductMypriceRequestDto requestDto) throws SQLException {
        //Product 객체를 만들고, Repository 쪽에서 getProduct 메서드를 통해 확인해야할 id 를 넘겨주고, DB 에 접속해서 확인하고, 반환되면 Product 객체에 담는다
        Product product = productRepository.getProduct(id);

        //DB 에 product 가 없는 상태라면
        if(product == null) {
            throw new NullPointerException("해당 상품은 존재하지 않습니다.");
        }

        //DB 에 product 가 있는 상태라면
        return productRepository.updateProduct(product.getId(), requestDto);
    }

}

DI(의존성 주입)

DI, IoC, Bean: https://velog.io/@baekgom/DI-IoC-Bean

강한 결합

'강한 결합' 이란?

  1. Contoller1 이 Service1 객체를 생성하여 사용

    public class Controller1 {
        private final Service1 service1;
    
        public Controller1() {
            this.service1 = new Service1();
        }
    }
  2. Service1 이 Repostiroy1 객체를 생성하여 사용

    public class Service1 {
        private final Repository1 repository1;
    
        public Service1() {
            this.repository1 = new Repository1();
        }
    }
  3. Repostiroy1 객체 선언

    public class Repository1 { ... }

강한 결합의 문제점

그런데 만약,
Repository1 객체 생성 시 DB에 접속해서,
id와 pw 를 받아서 Repository1를 DB 접속 시에 사용하는 경우(생성자에 DB 접속 id, pw 를 추가)에는?

public class Repository1 {

	public Repository1(String id, String pw) {
    // DB 연결
    Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", id, pw);
  }
}

결론적으로,
Controller 5 개가 각각 Service1 을 생성하여 사용하게 되고
Repository1 생성자 변경이 발생한다면, 모든 Contoller와 모든 Service의 코드 변경이 필요하게 됨!

강한 결합 해결방법

  • 각 객체에 대한 객체 생성은 딱 1번만 하기!
  • 생성된 객체를 모든 곳에서 재사용하기!
  1. Repository1 클래스 선언 및 객체 생성 → repository1

    public class Repository1 { ... }
    
    // new 연산자를 사용해서, 객체 Repository1 생성
    Repository1 repository1 = new Repository1();
  2. Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1

    Class Service1 {
        private final Repository1 repitory1;
    
        // repository1 객체 사용
        //Repository1 repository1 이렇게 파라미터로 생성이 된 repository를 받아와서, 변수 repitory1에 넣어주기
        public Service1(Repository1 repository) {
            //this.repository1 = new Repository1();  --> X
            this.repository1 = repository1;
        }
    }
    
    // 객체 생성
    Service1 service1 = new Service1(repository1);
  3. Contoller1 선언 (service1 사용)

    Class Controller1 {
        private final Service1 service1;
    
        // service1 객체 사용
        //Service1 service1 이렇게 파라미터로 받아와서, 변수 service1에 넣어주는 방식으로
        public Controller1(Service1 service1) {
            //this.service1 = new Service1();	--> X
            this.service1 = service1;
        }
    }

결론적으로,
이 방식으로 Repository1 객체 생성 시 DB 접속 id와 pw 를 받아서 DB 접속 시 사용할 때
repository1 객체를 생성해서, id와 pw를 넣어준다.
--> 생성된 repository1을 계속 재사용하면 된다.

public class Repository1 {

	public Repository1(String id, String pw) {
    // DB 연결
    Connection connection = DriverManager.getConnection("jdbc:h2:mem:db", id, pw);
  }
}

// 객체 생성
String id = "sa";
String pw = "";
Repository1 repository1 = new Repository1(id, pw);

  • Repository1 생성자 변경은 다른 곳에 영향을 끼치지 않게 됨
    - Service1 생성자가 변경되면? 모든 Contoller에 영향을 주지 않음

결론적으로, 느슨한 결합 이 되었다.

스프링 IoC 컨테이너 사용하기

DI, IoC, Bean: https://velog.io/@baekgom/DI-IoC-Bean

스프링 '빈' 등록 방법

1. @Component (어노테이션으로 등록하는 방법)

  • How?
    @Component		//클래스 선언 위에 설정
    public class ProductService { ... }
    // ProductService 객체 생성
        //객체 productService(= 빈) 가 스프링 IoC 컨테이너에 저장된다
        //빈 이름?  클래스의 앞글자만 소문자로 변경(public class ProductServcie → productServcie)
    ProductService productService = new ProductService();

'빈' 아이콘(나뭇잎 모양. 스프링 IoC 에서 관리할 '빈' 클래스라는 표시)

  • @Component 적용 조건
    - @SpringBootApplication 에 의해 default 설정이 되어 있음
    - @ComponentScan이 달린 패키지의 하위 패키지에 있는 @ComponentScan이 달린 클래스들을 찾아서 빈으로 등록하고, IoC 컨테이너에 저장
    @Configuration
    @ComponentScan(basePackages = "com.sparta.myselectshop")
    class BeanConfig { ... }

돋보기 달린 나뭇잎

2. @Bean (직접 등록하는 방법)

BeanConfiguration

package com.sparta.myselectshop.config;

import com.sparta.myselectshop.repository.ProductRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration		//1. 클래스 선언 위에 @Configuration 설정
public class BeanConfiguration {
    @Bean		//2. 함수 위에 @Bean 설정 (함수를 Bean으로 동록하는 과정)
    public ProductRepository productRepository() {
        String dbUrl = "jdbc:h2:mem:db";
        String username = "sa";
        String password = "";
        return new ProductRepository(dbUrl, username, password);
    }
}
// 3. @Bean 설정된 함수 호출(productRepository())하면, 객체 productRepository(빈) 가 스프링 IoC 컨테이너에 저장됨
//(스프링 서버가 뜰 때 스프링 IoC 에 '빈' 저장)
//@Bean이 설정된 함수명: public ProductRepository productRepository() {..} → productRepository
ProductRepository productRepository = beanConfiguration.productRepository();

스프링 IoC 에 '빈' 에 등록될 것이라는 표시

스프링 '빈' 사용 방법

1. @Autowired

방법

방법 1

@Component
public class ProductService {
		
    @Autowired		//주입하려는 변수(productRepository) 위에 달면, 스프링이 생성된 빈 객체를 자동으로 넣어줌(DI된다)
    private ProductRepository productRepository;
		
		// ...
}

방법 2

@Component
public class ProductService {

	//변수를 final로 만들고
    private final ProductRepository productRepository;

    @Autowired		//생성자 위에 @Autowired	를 추가
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
		
		// ...
}

@Autowired 적용 조건

스프링 IoC 컨테이너에 의해 관리되는 클래스에서만 가능

@Autowired 생략 조건

  1. 생성자 선언이 1개 일 때

    //오버로딩 되어 파라미터가 여러개가 된 생성자는 생략 불가
    public class A {
        @Autowired // 생략 불가
        public A(B b) { ... }
    
        @Autowired // 생략 불가
        public A(B b, C c) { ... }
    }
  2. Lombok 의 @RequiredArgsConstructor 를 사용

    @RestController
    @RequiredArgsConstructor // @RequiredArgsConstructor 붙이고, final로 선언된 멤버 변수를 자동으로 생성합니다.
    public class ProductController {
    
        private final ProductService productService;	//private final만 써도, @RequiredArgsConstructor가 private final를 찾아서 주입을 해준다(final로 선언해야함)
    
        // @RequiredArgsConstructor를 사용한다면, 아랫 부분은 생략 가능
            // @Autowired
            // public ProductController(ProductService productService) {
            //     this.productService = productService;
            // }
    }

2. ApplicationContext

스프링 IoC 컨테이너에서 빈을 수동으로 가져오는 방법

@Component
public class ProductService {

    private final ProductRepository productRepository;

    @Autowired
    public ProductService(ApplicationContext context) {
        // 1.'빈' 이름으로 가져오기
        ProductRepository productRepository = (ProductRepository) context.getBean("productRepository");
        // 2.'빈' 클래스 형식으로 가져오기
        // ProductRepository productRepository = context.getBean(ProductRepository.class);
        this.productRepository = productRepository;
    }

		// ...		
}

스프링 3계층 Annotation 적용하기

스프링 3계층 Annotation 은 모두 @Component이 추가로 들어가 있다.

@Controller, @RestController

@Service

@Component 대신 @Service를 입력해도, @Component가 추가로 자동 적용된다.

@Repository

JpaRepository를 상속받아도, 자동으로 @Repository 가 추가된다.
JpaRepository<"@Entity 클래스", "@Id 의 데이터 타입">를 상속받는 interface 로 선언.

스프링 프레임워크

'스프링 프레임워크' 란?

  • Enterprise applications(기업용 애플리케이션) 개발 편의성 제공
    - 고객 대상 웹 서비스 ex) 구글, 네이버, 쿠팡 등
  • 스프링은 결국 기업용 애플리케이션의 요구사항 해결에 초점을 맞춘 프레임워크
  • 기업용 애플리케이션 특성
    1. 신뢰성이 중요 (ex. 병원에서 수술 시 환자 기록이 바뀐다면?)
    2. 서버의 안정성 유지 중요 (ex. 복권 실시간 추첨에 서버 다운 된다면? )
    3. 데이터 관리가 중요
      • 막대한 양의 데이터 관리 필요
      • 여러 사용자가 동시 접속 시 데이터 일관성
      • → 대부분 DB (데이터베이스) 사용

Bonus 스케줄러 만들기

매일 새벽 1시에 관심 상품 목록 제목으로 검색해서, 최저가 정보를 업데이트하는 스케줄러

ProductController

package com.sparta.myselectshop.controller;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor     //의존성 주입
public class ProductController {

    //HTTP request 를 받아서,  Service 쪽으로 넘겨주고, 가져온 데이터들을 requestDto 파라미터로 보냄

    private final ProductService productService;

    // 관심 상품 등록하기
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto) {
        // 응답 보내기
        return productService.createProduct(requestDto);
    }

    // 관심 상품 조회하기
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts() {
        // 응답 보내기
        return productService.getProducts();
    }

    // 관심 상품 최저가 등록하기
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) {
        // 응답 보내기 (업데이트된 상품 id)
        return productService.updateProduct(id, requestDto);
    }

}

ProductService

package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.naver.dto.ItemDto;
import com.sparta.myselectshop.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

    @Transactional
    public ProductResponseDto createProduct(ProductRequestDto requestDto) {
        // 요청받은 DTO 로 DB에 저장할 객체 만들기
        Product product = productRepository.saveAndFlush(new Product(requestDto));

        return new ProductResponseDto(product);
    }

    @Transactional(readOnly = true)
    public List<ProductResponseDto> getProducts() {

        List<ProductResponseDto> list = new ArrayList<>();

        List<Product> productList = productRepository.findAll();
        for (Product product : productList) {
            list.add(new ProductResponseDto(product));
        }

        return list;
    }

    @Transactional
    public Long updateProduct(Long id, ProductMypriceRequestDto requestDto) {

        Product product = productRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
        );

        product.update(requestDto);

        return product.getId();
    }

    @Transactional  //설정해둔 myprice 값 보다 수정된 lprice 값이 작다면, '최저가' 표시가 뜨도록 js 에서 설정되어 있음
    public void updateBySearch(Long id, ItemDto itemDto) {
        //가지고 온 id 로 product 가 있는지 없는지 확인부터 한다
        Product product = productRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
        );
        //그리고 itemDto 를 넣어서 update 를 실시
        product.updateByItemDto(itemDto);
    }
}

Product

package com.sparta.myselectshop.entity;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.naver.dto.ItemDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@Entity // DB 테이블 역할을 합니다.
@NoArgsConstructor
public class Product extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 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;    //itemDto 의 lprice 값을 가져온 것을 기존의 lprice 쪽으로 update

    @Column(nullable = false)
    private int myprice;

    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();
    }

    public void updateByItemDto(ItemDto itemDto) {
        //itemDto 의 lprice 값을 가져와서,
        this.lprice = itemDto.getLprice();
    }

}

Scheduler

package com.sparta.myselectshop.scheduler;

import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.naver.dto.ItemDto;
import com.sparta.myselectshop.naver.service.NaverApiService;
import com.sparta.myselectshop.repository.ProductRepository;
import com.sparta.myselectshop.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class Scheduler {

    private final NaverApiService naverApiService;
    private final ProductService productService;
    private final ProductRepository productRepository;

    // 초, 분, 시, 일, 월, 주 순서
    @Scheduled(cron = "0 0 1 * * *")    //새벽 1시에 매번 자동으로 이 메소드가 실행됨 --> 구글링
    public void updatePrice() throws InterruptedException {
        log.info("가격 업데이트 실행");
        List<Product> productList = productRepository.findAll();    //productRepository 로 모든 product 를 가져온다
        for (Product product : productList) {   //for 문 돌면서,
            // 1초에 한 상품 씩 조회합니다 (NAVER 제한)
            TimeUnit.SECONDS.sleep(1);

            //product 에서 title 을 가져온다
            String title = product.getTitle();
            //title 을 통해서, naverApiService 를 사용해서 itemDtoList 를 가져온다
            List<ItemDto> itemDtoList = naverApiService.searchItems(title);
            //가장 상단에 있는 item 을 가져온다
            ItemDto itemDto = itemDtoList.get(0);

            //i 번째 관심 상품 정보를 업데이트합니다.
            //product 의 id 도 가져와서, 가장 상단에 있는 item 과 id 를 통해 update 를 실시
            Long id = product.getId();
            productService.updateBySearch(id, itemDto);
        }
    }
}
profile
개발자로 거듭나기!

0개의 댓글