프로젝트 설계
필요한 기능 확인
- 키워드로 상품검색 -> 결과 목록으로 보여주기
- 관심 상품 등록
- 관심 상품 조회
- 관심 상품에 가격 등록 and 등록한 가격보다 낮은 경우 표시
- (추가) 스케줄러를 통해 하루에 한번씩 상품 정보 DB에 업데이트
API 설계
| 기능 | Method | URL | 반환 |
| 상품 검색, 결과 보여주기 | GET | /api/search?query=검색어 | List<ItemDto> |
| 관심 상품 등록 | POST | /api/products | Product |
| 관심 상품 조회 | GET | /api/products | List<Product> |
| 관심 상품 가격 등록, 가격보다 낮은 경우 표시 |
PUT | /api/products/{id} | id |
3계층 설계 (Controller, Service, Repository)
1. Controller
- ProductRestController: 관심 상품 관련 컨트롤러
- SearchRequestController: 검색 관련 컨트롤러
2. Service
- ProductService: 관심 상품 가격 변경
3. Repository (Product 만 DB에 저장)
- Product: 관심 상품 테이블
- ProductRepository: 관심 상품 조회, 저장
- ProductRequestDto: 관심 상품 등록하기
- ProductMypriceRequestDto: 관심 가격 변경하기
- ItemDto: 검색 결과 주고받기
서버 구축
1. 관심 상품 조회
- 요구조건 - 상품의 이름, 이미지, 링크 등 정보가 나와야 함
Timestamped.java (package : models)
@Getter // get 함수를 자동 생성합니다.
@MappedSuperclass // 멤버 변수가 컬럼이 되도록 합니다.
@EntityListeners(AuditingEntityListener.class) // 변경되었을 때 자동으로 기록합니다.
public abstract class Timestamped {
@CreatedDate // 최초 생성 시점
private LocalDateTime createdAt;
@LastModifiedDate // 마지막 변경 시점
private LocalDateTime modifiedAt;
}
Application.java
@EnableJpaAuditing // 시간 자동 변경이 가능하도록 합니다.
@SpringBootApplication // 스프링 부트임을 선언합니다.
public class Week04Application {
public static void main(String[] args) {
SpringApplication.run(Week04Application.class, args);
}
}
- @EnableJpaAuditing 추가해줘야 Timestamed 기능 사용 가능
Pruduct.java (package : models)
@Getter // get 함수를 일괄적으로 만들어줍니다.
@NoArgsConstructor // 기본 생성자를 만들어줍니다.
@Entity // DB 테이블 역할을 합니다.
public class Product extends Timestamped{ //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;
}
- 필요한 데이터 - title : 상품명, image : 상품이름, link : 상품링크, lprice : 최저가격, myprice : 사용자가 설정한 가격
ProductRepository.java (package : models)
public interface ProductRepository extends JpaRepository<Product, Long> {
}
- 관심 상품의 생성, 조회, 삭제를 담당
- JpaRepository<대상, id 자료형> -> Product 대상으로 JPA사용 / id는 Long 형태
ProductRestController.java (package : controller)
@RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
@RestController // JSON으로 데이터를 주고받음을 선언합니다.
public class ProductRestController {
private final ProductRepository productRepository;
// 등록된 전체 상품 목록 조회
@GetMapping("/api/products")
public List<Product> getProducts() {
return productRepository.findAll();
}
}
- Product의 정보를 조회하기 위해 리스트로 함수 만들고 findAll
- 찾아서 정보를 반환 -> productRepository에 있는 찾는 기능 사용 -> final 통해서 선언하고 반환값에서 사용
- 작업 후 ARC 에서 GET방식 localhost:8080/api/products 로 send -> 200 OK 확인
여기까지가 관심 상품 조회 기능 서버에서 구현!
2. 관심 상품 등록 & 관심 가격 등록
- 요구조건 : 상품 검색 후 버튼 누르면 관심 상품으로 등록 -> 검색해서 나온 이름, 이미지, 링크, 최저가를 가져오기
ProductRequestDto.java (package : models)
@Getter
public class ProductRequestDto {
private String title;
private String link;
private String image;
private int lprice;
}
- 관심상품을 등록할 때 사용
ProductMypriceRequestDto.java (package : models)
@Getter
public class ProductMypriceRequestDto {
private int myprice;
}
- 사용자가 설정한 가격을 변경할 때 사용
Product.java (package : models) 추가
// 관심 상품 생성 시 이용합니다.
public Product(ProductRequestDto requestDto) {
this.title = requestDto.getTitle();
this.image = requestDto.getImage();
this.link = requestDto.getLink();
this.lprice = requestDto.getLprice();
this.myprice = 0;
}
- requestDto를 받아서 생성해야 하니까 ProductRequestDto를 받아서 새로 뱉어주는 생성자가 필요!!
- 사용자 가격을 설정해서 현재가격이 낮을때만 알려줘야하기에 myprice 초기값 0으로 설정
ProductService.java (package : service)
@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;
}
}
- 관심 가격 정보가 전달이 됐을 때 id로 누구껀지 찾고 해당 id의 myprice를 update 해주는 메소드 생성
- update(어떤대상을 업데이트 해야 되는지?, 어떤 값을 업데이트 해야 되는지?) -> id, 가격정보를 갖고있는 Dto
- 다음으로 Product class에서 update 하는 메소드를 만들어줘야함
Product.java (package : models) 추가
// 관심 가격 변경 시 이용합니다.
public void update(ProductMypriceRequestDto requestDto) {
this.myprice = requestDto.getMyprice();
}
- myprice를 전달받은 getMyprice로 바꿔줘!!
ProductRestController.java (package : controller) 추가
// 신규 상품 등록 // 등록하는 api 추가
@PostMapping("/api/products")
public Product createProduct(@RequestBody ProductRequestDto requestDto) {
Product product = new Product(requestDto);
productRepository.save(product);
return product;
}
// 관심 상품 가격 등록
@PutMapping("/api/products/{id}")
public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto){
return productService.update(id, requestDto);
}
- Dto에 있는 정보를 재료로 product 에 담고 Repository를 통해 DB에 저장!(product를 저장)
- mypriceRequestDto에서 사용자가 입력한 가격을 받아와서 update
여기까지가 관심 상품 등록 & 관심 상품 가격 등록 기능 서버에서 구현!
3. 상품 검색 기능(1)
- 요구조건 : 사용자가 입력한 검색어에 따른 검색결과를 보여줘야함 / 검색결과 문자열을 DTO로 바꾸기
NaverShopSearch.java (package : utils) - API 사용부분 때 만들었던 것
public class NaverShopSearch {
public String search(String query) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "여러분이 발급받은 Client ID");
headers.add("X-Naver-Client-Secret", "여러분이 발급받은 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=" + 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 static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
naverShopSearch.search("아이맥");
}
}
- 아이맥 으로 검색 시 예제 // search 메소드에 검색어를 입력하면 위에서 String query로 받아줘야 함 (수정부분)
- 검색어가 rest.exchange - query 부분에 값이 들어가면 그 값으로 검색
검색 결과를 문자열에서 DTO로 바꾸기
★ org.json 패키지 설치 - 자바에서 json을 다루는데 도와주는 라이브러리
- mvnrepository.com/ 접속 후 json 검색 -> json in java 사용많은 버전에서 Gradle 복사
- 인텔리제이 좌측 - build.gradle - dependencies 안에 붙여넣고 dependencies Run 클릭
- 인텔리제이 우측 - Gradle - 새로고침 버튼 클릭 // 여기까지 해야 라이브러리 사용 가능
★ JSONObject, JSONArray 연습 (JSON을 자바에서 다루기 위해 필요한 클래스)
NaverShopSearch.java (package : utils) (main함수 부분)
public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
String result = naverShopSearch.search("아이맥"); // result값 입력(아이맥 검색값)
//추가된 부분
JSONObject rjson = new JSONObject(result);
}
- 아이맥으로 검색한 결과가 문자열로 result에 들어감 -> 검색 결과를 바탕으로 JSONObject로 rjson 생성
- rjson을 출력해보면 {]출력값 안에 "items" 키값으로 검색 결과에 배열이 들어가 있음

public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
String result = naverShopSearch.search("아이맥"); // result값 입력(아이맥 검색값)
JSONObject rjson = new JSONObject(result);
//추가된 부분
JSONArray items = rjson.getJSONArray("items");
}
- JSONOject 안에 있는 items 키 값의 값은 [] 배열로 되어있어서 JSONArray로 꺼내와야함
- JSONArray로 만들어진 items(는) = rjson안에 배열중 key값이 items인 배열
public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
String result = naverShopSearch.search("아이맥"); // result값 입력(아이맥 검색값)
JSONObject rjson = new JSONObject(result);
JSONArray items = rjson.getJSONArray("items");
//추가된 부분
for (int i=0; i<items.length(); i++) { //JSONArray에서는 size가 아닌 length사용
JSONObject itemJson = (JSONObject) items.get(i); // = items.getJSONObject(i)
System.out.println(itemJson);
}
}
- items 배열 전체가 아니라 배열 안에 상품 하나하나가 필요 / items : [{상품정보1}, {상품정보2}...]
- 그래서 1) for문을 사용해서 배열을 돌면서 꺼내기 2) 꺼낼때는 JSONObject를 사용 (배열안에 중괄호로 감싸져있음)
- for문 밑줄 코드 : JSONObject 형태로 itemJson 생성 (Object가 합쳐져 Array가 되있기 때문에)
itemJson은 = items에서 i번째에 있는 JSONObject 데이터를 꺼내서 담아줌 - itemJson 출력 시 밑에 사진처럼 상품마다의 정보가 JSONObject로 출력

public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
String result = naverShopSearch.search("아이맥"); // result값 입력(아이맥 검색값)
JSONObject rjson = new JSONObject(result);
JSONArray items = rjson.getJSONArray("items");
for (int i=0; i<items.length(); i++) { //JSONArray에서는 size가 아닌 length사용
JSONObject itemJson = (JSONObject) items.get(i); // = items.getJSONObject(i)
System.out.println(itemJson);
//추가된 부분
String title = itemJson.getString("title");
String image = itemJson.getString("image");
int lprice = itemJson.getInt("lprice");
String link = itemJson.getString("link");
}
}
- itemJson에서 필요한 데이터만 뽑기
- itemJson.getString("title") : 데이터뽑을곳.get뽑을데이터의 형태("키값") // 변수선언해서 거기에 값 넣기
ItemDto.java (package : models)
@Getter
public class ItemDto {
private String title;
private String link;
private String image;
private int lprice;
public ItemDto(JSONObject itemJson) { //생성자를 통해 itemJson의 정보를 받기
this.title = itemJson.getString("title");
this.link = itemJson.getString("link");
this.image = itemJson.getString("image");
this.lprice = itemJson.getInt("lprice");
}
}
- 검색 결과를 돌려주려면 데이터를 물고 나르는 클래스가 필요해서 생성
- 생성자를 통해서 JSONObject인 itemJson의 정보를 받기
NaverShopSearch.java (package : utils) 수정
public static void main(String[] args) {
NaverShopSearch naverShopSearch = new NaverShopSearch();
String result = naverShopSearch.search("아이맥");
JSONObject rjson = new JSONObject(result);
JSONArray items = rjson.getJSONArray("items");
List<ItemDto> ret = new ArrayList<>();
for (int i=0; i<items.length(); i++) {
JSONObject itemJson = items.getJSONObject(i);
ItemDto itemDto = new ItemDto(itemJson); //itemJson을 통해 ItemDto만들기
// ItemJson에 담긴 정보 -> itemDto로 흡수
ret.add(itemDto);
}
return ret;
}
==========================================================================
위쪽이 수정 전(main함수에 다 넣은경우) 아래가 수정 후(반복되는 작업 메소드로..)
==========================================================================
public class NaverShopSearch {
public String search(String query) { // 기존 search부분
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "zdqMoIkFaK8uKvC2oNY2");
headers.add("X-Naver-Client-Secret", "LiZfsgtuD5");
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) { //반환:List<ItemDto>
JSONObject rjson = new JSONObject(result); //ret값을 Object형태인 rjson에 담기
JSONArray items = rjson.getJSONArray("items"); //rjson안에 items키값인 배열꺼내기
List<ItemDto> ret = new ArrayList<>(); //검색결과 여러개 나타내기위해 리스트 생성
for (int i=0; i<items.length(); i++) {
JSONObject itemJson = items.getJSONObject(i); //i번째 Object형태 값 꺼내기
ItemDto itemDto = new ItemDto(itemJson); //itemJson을 통해 ItemDto만들기
// ItemJson에 담긴 정보 -> itemDto로 흡수
ret.add(itemDto);
// 위에서 만든itemDto list형태에 값을 넣어줌 (여러개 출력위해)
}
return ret;
}
- 문자열로 받고 JSONObject로 바꾸고 List로 반환하는 작업이 항상 쓰이기 때문에 메소드로 따로 빼서 정리
여기까지 하고 Controller에서
search메소드에서 검색어를 문자열로 받고 fromJSONtoItems메소드에 input값을 줘서 돌려주면 검색이 가능해짐
4. 상품 검색 기능(2)
- 요구조건 : 1) 사용자가 입력한 검색어를 컨트롤러가 전달받기. 2) 전달받은 검색어를 네이버 API에 요청 후 응답
★위에서 만든 NaverShopSearch.java를 컴포넌트로 등록 (스프링에게 권한을 주는 것!)
- 컴포넌트로 등록이 되면 스프링이 권한을 획득해서 원할 때 마다 알아서 가져다 쓸 수 있음
NaverShopSearch.java (package : utils) 컴포넌트 등록하는법
@Component // 컴포넌트 등록
public class NaverShopSearch {
SearchRequestController.java (package : 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); //navershopSearch에서 검색어로 찾기
return naverShopSearch.fromJSONtoItems(resultString);
//search로 찾은값을 fromJSONtoItems메소드를 통해 반환
}
}
- 상품 검색 결과를 보여주기 위한 Controller
- 여기까지 작업 후 ARC에서 기능 확인 - GET방식 localhost:8080/api/search?query=검색어 send -> 200 OK 확인
여기까지 하면 검색어에 따른 결과값 가져오는 기능 서버 구현!
5. 상품 가격 자동 업데이트 - 스케줄러 만들기
- 자동으로 상품 DB정보를 하루에 정해놓은 시간에 업데이트 하게 만들기
Scheduler.java (package : utils)
@RequiredArgsConstructor // final 멤버 변수를 자동으로 생성합니다.
@Component // 스프링이 필요 시 자동으로 생성하는 클래스 목록에 추가합니다.
public class Scheduler {
private final ProductRepository productRepository;
private final ProductService productService;
private final NaverShopSearch naverShopSearch;
// 초, 분, 시, 일, 월, 주 순서
@Scheduled(cron = "0 0 1 * * *") //정해진 시간에 밑에 메소드를 실행하도록 도와줌 *은 언제든지 상관x
public void updatePrice() throws InterruptedException { // 오류발생 시 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);
}
}
}
- 그다음 updateBySearch함수를 productService에 만들기
ProductService.java (package : service) (아래부분 추가)
@Transactional // 메소드 동작이 SQL 쿼리문임을 선언합니다.
public Long updateBySearch(Long id, ItemDto itemDto) {
Product product = productRepository.findById(id).orElseThrow(
() -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
);
product.updateByItemDto(itemDto);
return id;
}
- 그다음 Product클래스에 updateByItemDto 함수 만들기
Product.java (package : models)
public void updateByItemDto(ItemDto itemDto){
this.lprice = itemDto.getLprice();
}
- 가격만 업데이트 하면됌
Application.java
@EnableScheduling // 스프링 부트에서 스케줄러가 작동하게 합니다.
@EnableJpaAuditing // 시간 자동 변경이 가능하도록 합니다.
@SpringBootApplication // 스프링 부트임을 선언합니다.
public class Week04Application {
public static void main(String[] args) {
SpringApplication.run(Week04Application.class, args);
}
}
- @EnableScheduling - 스케줄러 작동하도록 추가
'미니프로젝트' 카테고리의 다른 글
| 4. 나만의 셀렉샵 - 화면 구현 (스프링 부트) (0) | 2021.03.25 |
|---|---|
| 3. 나만의 셀렉샵 - 클라이언트 구축 (스프링부트) (0) | 2021.03.25 |
| 1. 나만의 셀렉샵 - 자바를 통해 API 이용하기 (스프링부트) (0) | 2021.03.24 |
| 2. 익명의 메모장 - 클라이언트 구축 (스프링부트) (0) | 2021.03.21 |
| 1. 익명의 메모장 - 서버구축 (스프링부트) (0) | 2021.03.21 |
댓글