[학습목표]
1. Controller - Service - Repository 학습
2. 자바를 이용해 API를 이용하는 방법
3. 스프링 스케줄러를 이용하여, 서버에게 원하는 작업을 원하는 시간에 시키는 방법
분업 + 느슨한 결합
제일 바깥 쪽에서 요청을 받고, 응답을 되돌려주는 역할
중간에서 구체적인 작업 순서 결정
DB와 직접 소통함으로써 자료를 생성하고, 조회하고, 변경하고, 삭제함
{
"title": "<b>아디다스</b> 알파바운스 BB슬라이드 BA8775",
"link": "https://search.shopping.naver.com/gate.nhn?id=24457175865",
"image": "https://shopping-phinf.pstatic.net/main_2445717/24457175865.20201014195220.jpg",
"lprice": "27990",
"hprice": "",
"mallName": "네이버",
"productId": "24457175865",
"productType": "1",
"brand": "아디다스",
"maker": "아디다스",
"category1": "패션잡화",
"category2": "남성신발",
"category3": "슬리퍼",
"category4": ""
},
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "**********");
headers.add("X-Naver-Client-Secret", "**********");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=아디다스", HttpMethod.GET, requestEntity, String.class);
HttpStatus httpStatus = responseEntity.getStatusCode();
int status = httpStatus.value();
String response = responseEntity.getBody();
System.out.println("Response status: " + status);
System.out.println(response);
src > main > java > com.sparta.week04 > utils
키워드로 상품 검색하고 그 결과를 목록으로 보여주기
GET /api/search?query=검색어
return List<ItemDto>
관심 상품 등록하기
POST /api/products
관심 상품 조회하기
GET /api/products
관심 상품에 관심가격 등록하고, 그 가격보다 낮은 경우 표시하기
PUT /api/products/{id}
Controller
Service
Repository
여기서 DB에 저장되는 녀석은 Product 뿐이라는 점!
org.json
Java에서 json을 다루는데 도와주는 라이브러리
com > sparta > week04 > models
1) 각각의 모델들, Repository, Timestamped
Product.java
상품 테이블, update의 경우 사용자가 지정한 최저가를 변경하는 update
함수와 스케줄러로 변경되는 가격들을 불러오는 updateByItemDto
가 있습니다.
package com.sparta.week04.models;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@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;
public Product(ProductRequestDto requestDto) {
this.title = requestDto.getTitle();
this.link = requestDto.getLink();
this.lprice = requestDto.getLprice();
this.image = requestDto.getImage();
this.myprice = 0;
}
public void update(ProductMypriceRequestDto requestDto) {
this.myprice = requestDto.getMyprice();
}
public void updateByItemDto(ItemDto itemDto) {
this.lprice = itemDto.getLprice();
}
}
Timestamped
생성날짜, 수정날짜를 만들기 위한 class
package com.sparta.week04.models;
@Getter
// get 함수를 자동 생성합니다.
@MappedSuperclass
// 멤버 변수가 컬럼이 되도록 합니다.
@EntityListeners(AuditingEntityListener.class)
// 변경되었을 때 자동으로 기록합니다.
public abstract class Timestamped {
@CreatedDate // 최초 생성 시점
private LocalDateTime createdAt;
@LastModifiedDate // 마지막 변경 시점
private LocalDateTime modifiedAt;
}
ProductRepository.java
package com.sparta.week04.models;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
com > sparta > week04 > models
ProductRequestDto.java
package com.sparta.week04.models;
import lombok.Getter;
@Getter
public class ProductRequestDto {
private String title;
private String link;
private String image;
private int lprice;
}
ItemDto.java
package com.sparta.week04.models;
import lombok.Getter;
import org.json.JSONObject;
@Getter
public class ItemDto {
private String title;
private String link;
private String image;
private int lprice;
public ItemDto(JSONObject itemJson) {
this.title = itemJson.getString("title");
this.link = itemJson.getString("link");
this.image = itemJson.getString("image");
this.lprice = itemJson.getInt("lprice");
}
}
ProductMypriceDto.java
package com.sparta.week04.models;
import lombok.Getter;
@Getter
public class ProductMypriceRequestDto {
private int myprice;
}
com > sparta > week04 > service
ProductService.java
package com.sparta.week04.service;
...
import javax.transaction.Transactional;
@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;
}
@Transactional // DB 가 업데이트 되어야한다.
public Long updateBySearch(Long id, ItemDto itemDto) {
Product product = productRepository.findById(id).orElseThrow(
() -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
);
product.updateByItemDto(itemDto);
return id;
}
}
com > sparta > week04 > utils
NaverShopSearch.java
NAVER API 사용하여 값을 불러오는 class
컴포넌트 등록
쉽게 얘기하면 스프링한테 권한을 주는 것! 필요할 때 알아서 써! 라고 하는 것
스프링이 해당 컴포넌트를 자동으로 가져오고 사용할 수 있게 권한을 주는 것
Repository 나 Service 모두 컴포넌트 등록이 되어있어서 스프링이 자유롭게 사용할 수 있음
스프링에 내가 마음대로 가져다 쓸 수 있는 클래스 목록이 나열되어 있음
그 목록에 있는 것들이 컴포넌트로 등록된 것들
컨트롤러, 서비스, 엔티티 등이 컴포넌트로 등록이 되어 있음
NaverShopSearch 의 경우 수동으로 등록을 해줘야 한다.
itemDto 형태는 JSON, ${}
괄호 안에 써야하는 건 문자열
JSON을 제대로 넣으면 오류가 난다.
그래서 JSON이 아닌 String 형태로 넣어준다.
JSON을 자바에서 다루기 위해, JSONObject, JSONArray 클래스가 필요함. import해오기
1) maven central 검색
2) json 검색
3) JSON In Java 클릭
4) 숫자 가장 높은 버전 클릭
5) Gradle 탭 클릭
6) 내용 복사 후 build.gradle > dependencies 안에 붙여넣기
7) dependencies 옆 run 버튼 클릭
=> import 완료!
package com.sparta.week04.utils;
...
@Component
public class NaverShopSearch {
public String search(String query) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "p234pvd2_wQpIjp6KlKi");
headers.add("X-Naver-Client-Secret", "bsVXyLy17e");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<String>(body, headers);
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
HttpStatus httpStatus = responseEntity.getStatusCode();
int status = httpStatus.value(); // 응답 상태 코드
String response = responseEntity.getBody();
System.out.println("Response status: " + status);
System.out.println(response);
return response;
}
public List<ItemDto> fromJSONtoItems(String result) {
JSONObject rjson = new JSONObject(result);
JSONArray items = rjson.getJSONArray("items");
List<ItemDto> itemDtoList = new ArrayList<>();
// JSONArray 에서는 length 로 꺼냄
for (int i = 0; i < items.length(); i++) {
JSONObject itemJson = (JSONObject) items.get(i);
// JSONObject itemJson = items.getJSONObject(i);
ItemDto itemDto = new ItemDto(itemJson);
itemDtoList.add(itemDto);
}
return itemDtoList;
}
}
Scheduler.java
스케줄러를 통해 매일 오전 1시에 새로운 가격을 요청해서 가져온다.
package com.sparta.week04.utils;
...
@RequiredArgsConstructor // final 멤버 변수를 자동으로 생성합니다.
@Component // 스프링이 필요 시 자동으로 생성하는 클래스 목록에 추가합니다.
public class Scheduler {
private final ProductRepository productRepository;
private final ProductService productService;
private final NaverShopSearch naverShopSearch;
// 초, 분, 시, 일, 월, 주 순서
// cron : 시간이 맞을때 작동울 해라
// 0~23시까지 가능 1시 0분 1초부터 1시 59분 59초까지 매초 실행 * * 1 * * *
// 1시 0분 0초일 때 실행
@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); // 타임 단위 기준으로 초마다 한번씩 잠깐 쉬어라. = 1초에 한번 씩 for 문이 돌게 된다.
// 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);
}
}
}
ProductRestController.java
package com.sparta.week04.controller;
...
@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);
return productRepository.save(product);
}
// 최저가 변경 API
@PutMapping("/api/product/{id}")
public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) {
return productService.update(id, requestDto);
}
}
SearchRequestController.java
네이버 API 값 불러오는 API
package com.sparta.week04.controller;
...
@RequiredArgsConstructor // final 로 선언된 클래스를 자동으로 생성합니다.
@RestController // JSON 으로 응답함을 선언합니다.
public class SearchRequestController {
private final NaverShopSearch naverShopSearch;
@GetMapping("/api/search")
public List<ItemDto> getItems(@RequestParam String query) {
String resultString = naverShopSearch.search(query);
return naverShopSearch.fromJSONtoItems(resultString);
}
}