[Book 개인프로젝트] 3. 네이버 책 api 사용 + 네이버 책 api로 가져온 JSON 형태의 값을 book 객체에 적용하는 법

이재민·2024년 6월 1일
0

JPA 

목록 보기
5/21

1. 네이버 책 api 사용법

https://this-circle-jeong.tistory.com/167
위에 링크에 너무 잘 되어있기 때문에 따라 하면 될 거 같다.

2. 네이버 책 api로 가져온 JSON 형태의 값을 book 객체에 적용하는 법

근데 나는 이 api를 사용하면서 하나의 문제에 봉착했다.

처음에는 그냥 넘어오는 JSON 객체를 BookVO(1대1 매핑을 위한 엔티티)로 1대1 매핑하려고 했다.

즉, 나는 넘어온 JSON 객체를 BookVO객체로 받은 다음 book.setBookVOisbn(~) 이런식으로 하나씩 book 객체에 담을 계획이었다.

@Entity
@Getter @Setter
public class Book {

    @Id @GeneratedValue
    @Column(name = "book_id")
    private Long id;

    private String title;

    private int price;

    //BookVO -> Book 변환 메서드 추가를 위한 객체들 추가
    @Column(name = "isbn", unique = true)
    private String isbn;
    private String author;
    private String publisher;
    private String image;
    private String pubdate;
    private String link;
}

하지만 넘어오는 JSON 형태를 보면 우리가 찾고자 하는 값들이 json 값 그 안에 또 json 형태의 items이라는 하나의 묶음으로 한 번 더 감싸져 있었기 때문에 바로 매핑하면 안 됐음.

이를 해결하기 위해서 먼저 챗지피티한테 도움을 요청했다.

문제 :

주어진 JSON 문자열의 구조를 살펴보면, items 필드 내에 있는 객체들이 BookVO 객체의 필드와 일치하도록 매핑되어야 합니다. 따라서 BookVO 클래스의 필드와 items 필드 내의 객체가 올바르게 매핑되었는지 확인해야 합니다. 현재는 BookVO 클래스가 단일 객체를 매핑하는 것이 아니라 items 필드 내의 객체를 매핑해야 합니다.

예를 들어, BookVO 클래스의 필드가 title, link, image 등이고, items 필드 내의 객체가 이와 같은 필드들을 가지고 있다면, BookVO 클래스에는 items 필드를 포함하지 않고, items 필드 내의 객체를 담을 수 있는 새로운 클래스가 필요합니다. 그리고 이 클래스에 @JsonProperty 어노테이션을 사용하여 필드와 JSON 필드를 매핑해야 합니다.

이러한 방식으로 JSON 문자열의 구조를 변경하여 매핑 가능하도록 만들어야 합니다. 이것이 2번째 해결 방법입니다.
따라서 현재의 BookVO 클래스를 유지하면서 items 필드 내의 객체를 매핑할 새로운 클래스를 작성하여 해결할 수 있습니다. 이러한 변경을 통해 JSON 문자열의 구조와 BookVO 클래스의 필드와 일치하도록 만들어야 합니다.

새로운 클래스를 만들어서 items 필드 내의 객체를 매핑하는 방법을 설명하겠습니다. 새로운 클래스는 items 필드 내의 각 객체를 나타내며, 이 클래스에 @JsonProperty 어노테이션을 사용하여 필드와 JSON 필드를 매핑합니다.
먼저, items 필드 내의 객체를 매핑할 새로운 클래스를 만들어 보겠습니다. 예를 들어, BookItem이라는 클래스를 만들어서 items 필드 내의 객체를 매핑할 수 있습니다.

-> 즉, API로 넘어오는 JSON형태의 값이랑 바로 BookVO랑 매칭시키면 안된다.
왜냐하면 우리가 생성한 BookVO는 단일 객체이지만, 매핑시키고자 하는 JSON값은 json안에 또 json형태의 items라는 소그룹 형태로 들어가 있기 때문이다. 그래서 새로운 클래스를 만들어서 item 필드만을 매핑하는 엔티티(단일 매핑 객체 - BookItem)를 통해서 접근하도록 한다.

1. json 형태의 값을 받는 단일 매핑 객체인 BookItem 엔티티 생성

BookItem 생성

package Book_Toy_Project.BOOK.Entity;

import Book_Toy_Project.BOOK.api.BookVO;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "BookItem")
public class BookItem {

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

    @JsonProperty("title")
    private String title;

    @JsonProperty("link")
    private String link;

    @JsonProperty("image")
    private String image;

    @JsonProperty("author")
    private String author;

    @JsonProperty("discount")
    private String discount;

    @JsonProperty("publisher")
    private String publisher;

    @JsonProperty("pubdate")
    private String pubdate;

    @JsonProperty("isbn")
    private String isbn;

    @JsonProperty("description")
    private String description;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "book_vo_id")
    private BookVO bookVO;
}

여기서 BookVO랑 BookItem 간의 의존관계 설정 이유

데이터의 일관성 유지: BookVO와 BookItem은 서로 연관된 데이터를 나타냅니다. 예를 들어, BookVO는 여러 책 정보를 담고 있고, BookItem은 각 책의 세부 정보를 나타냅니다. 이들 간의 관계를 설정하면 데이터의 일관성을 유지하고 관련된 정보를 쉽게 추적할 수 있습니다.

2. BookVO 객체

package Book_Toy_Project.BOOK.api;

import Book_Toy_Project.BOOK.Entity.BookItem;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.*;

import java.util.List;

@ToString
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@Entity
@Table(name = "BookVO")
public class BookVO {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    //외부와의 데이터 통신
    private Long id;

    // BookItem 클래스로 대체하여 items 필드 내의 객체를 매핑
    @OneToMany(mappedBy = "bookVO", cascade = CascadeType.ALL)
    private List<BookItem> items;
}

3. parseBook의 paresBookInfo 메서드 작성 (JSON -> BookITEM)

package Book_Toy_Project.BOOK.Service;

import Book_Toy_Project.BOOK.Entity.BookItem;
import Book_Toy_Project.BOOK.api.BookVO;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

@Service
@Slf4j
public class parseBook {

    public List<BookItem> parseBookInfo(String responseBody) {
        log.info("parseBookInfo_responseBody : {}", responseBody);

        List<BookItem> bookList = new ArrayList<>();

        // JSON 형식의 responseBody를 파싱하여 필요한 정보 추출
        try {

            // 1) responseBody가 null인지 검증
            if (responseBody == null) {
                throw new IllegalArgumentException("responseBody is null");
            }

            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 알려지지 않은 속성 무시 설정

            JsonNode root = objectMapper.readTree(responseBody);
            JsonNode itemsNode = root.get("items");

            // items 필드가 존재하고 배열인지 확인
            if (itemsNode != null && itemsNode.isArray()) {
                for (JsonNode itemNode : itemsNode) {
                    log.info("Parsing itemNode: {}", itemNode);
                    BookItem bookItem = objectMapper.treeToValue(itemNode, BookItem.class);
                    log.info("BookItem - title: {}, link: {}, image: {}", bookItem.getTitle(), bookItem.getLink(), bookItem.getImage());
                    bookList.add(bookItem);
                    log.info("bookList : {}", bookList);
                }
            }
            log.info("책 정보를 {}개 만큼 파싱하였습니다.", bookList.size());
            return bookList;

        } catch (JsonSyntaxException e) {
            log.error("JsonSyntaxException : {}", e.getMessage());
            return null;

        }catch (Exception e) {
            log.error("Exception : {}", e.getMessage());
            return null;
        }

    }
}


먼저

log.info("parseBookInfo_responseBody : {}", responseBody);

로 잘 들어왔는지 확인


ex)

parseBookInfo_responseBody : {
	"lastBuildDate":"Thu, 11 Apr 2024 09:43:50 +0900",
	"total":1,
	"start":1,
	"display":1,
	"items":[
		{
			"title":"자바 ORM 표준 JPA 프로그래밍 (스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임워크)",
			"link":"https:\/\/search.shopping.naver.com\/book\/catalog\/32436007738",
			"image":"https:\/\/shopping-phinf.pstatic.net\/main_3243600\/32436007738.20221229072907.jpg",
			"author":"김영한",
			"discount":"38700",
			"publisher":"에이콘출판",
			"pubdate":"20150728",
			"isbn":"9788960777330",
			"description":"자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것을 다룬다. 또한, 스프링 프레임워크와 JPA를 함께 사용하는 방법을 설명하고, 스프링 데이터 JPA, QueryDSL 같은 혁신적인 오픈 소스를 활용해서 자바 웹 애플리케이션을 효과적으로 개발하는 방법을 다룬다.\n\n다음 링크에서 온라인 강의를 수강할 수 있다.\n\n■ 강의 링크: https:\/\/www.inflearn.com\/roadmaps\/149\n■ 온라인 강의 목록\n-자바 ORM 표준 JPA 프로그래밍 - 기본편: https:\/\/www.inflearn.com\/course\/ORM-JPA-Basic\n-실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발: https:\/\/www.inflearn.com\/course\/스프링부트-JPA-활용-1\n-실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화: https:\/\/www.inflearn.com\/course\/스프링부트-JPA-API개발-성능최적화#\n-실전! 스프링 데이터 JPA: https:\/\/www.inflearn.com\/course\/스프링-데이터-JPA-실전\n-실전! Querydsl: https:\/\/www.inflearn.com\/course\/Querydsl-실전"
		}
	]
}

objectMapper로 itemNode 잘 가져왔는지 확인

" log.info("Parsing itemNode: {}", itemNode);"

2024-04-11T09:43:50.724+09:00  INFO 26224 --- [nio-8080-exec-2] B.BOOK.Service.parseBook
: Parsing itemNode: {"title":"자바 ORM 표준 JPA 프로그래밍 (스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임워크)",
"link":"https://search.shopping.naver.com/book/catalog/32436007738",
"image":"https://shopping-phinf.pstatic.net/main_3243600/32436007738.20221229072907.jpg",
"author":"김영한",
"discount":"38700",
"publisher":"에이콘출판",
"pubdate":"20150728",
"isbn":"9788960777330",
"description":"자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것을 다룬다. 또한, 스프링 프레임워크와 JPA를 함께 사용하는 방법을 설명하고, 스프링 데이터 JPA, QueryDSL 같은 혁신적인 오픈 소스를 활용해서 자바 웹 애플리케이션을 효과적으로 개발하는 방법을 다룬다.\n\n다음 링크에서 온라인 강의를 수강할 수 있다.\n\n■ 강의 링크: https://www.inflearn.com/roadmaps/149\n■ 온라인 강의 목록\n-자바 ORM 표준 JPA 프로그래밍 - 기본편: https://www.inflearn.com/course/ORM-JPA-Basic\n-실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발: https://www.inflearn.com/course/스프링부트-JPA-활용-1\n-실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화: https://www.inflearn.com/course/스프링부트-JPA-API개발-성능최적화#\n-실전! 스프링 데이터 JPA: https://www.inflearn.com/course/스프링-데이터-JPA-실전\n-실전! Querydsl: https://www.inflearn.com/course/Querydsl-실전"}

각각 잘 들어가고 있는지 확인
ex)

log.info("BookItem - title: {}, link: {}, image: {}", bookItem.getTitle(), bookItem.getLink(), bookItem.getImage());

2024-04-11T09:49:31.503+09:00  INFO 13456 --- [nio-8080-exec-1] B.BOOK.Service.parseBook                 : BookItem - title: 자바 ORM 표준 JPA 프로그래밍 (스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임워크), link: https://search.shopping.naver.com/book/catalog/32436007738, image: https://shopping-phinf.pstatic.net/main_3243600/32436007738.20221229072907.jpg

bookList 확인

log.info("bookList : {}", bookList);

2024-04-11T09:49:31.504+09:00  INFO 13456 --- [nio-8080-exec-1] B.BOOK.Service.parseBook                 : bookList : [Book_Toy_Project.BOOK.Entity.BookItem@41003faa]

41003faa - 객체의 해시 코드이며, 객체를 식별하는 데 사용됨


4. Controller부분에서 로직처리

        // 응답 본문 파싱하여 BookVO 객체로 변환
        log.info("응답 본문을 파싱하여 BookVO 객체로 변환합니다.");
        List<BookItem> bookItems = parseBook.parseBookInfo(resp.getBody());
        if (!bookItems.isEmpty()) {
            BookVO bookVO = new BookVO();
            bookVO.setItems(bookItems);
            log.info("파싱된 BookVO 객체: {}", bookVO);

            //BookV0 객체를 Book 객체로 변환
            log.info("BookV0 객체를 Book 객체로 변환");
            Book book = bookService.convertToEntity(bookVO);
            log.info("변환된 Book 객체 : {}", book);

            if (book.getName() == null || book.getIsbn() == null) {
                // 유효성 검사 실패 시 적절한 응답 반환
                log.error("북 이름, isbn이 필요합니다.");
                throw new Exception("북 이름, isbn 값이 필요합니다");
                //return ResponseEntity.badRequest().body("Book name and ISBN are required.");
            }

            try {
                log.info("책 정보를 db에 저장합니다.");
                //책 정보를 db에 저장
                bookService.saveBook(book);

                // 적절한 응답 반환
                return "redirect:/home";

            } catch (DuplicateIsbnException e) {
                // 중복된 ISBN이 발생한 경우 적절한 응답 반환
                log.error("중복된 ISBN이 발생했습니다: {}", e.getMessage());
                throw new DuplicateIsbnException("중복된 ISBN입니다.", e);
                //return ResponseEntity.badRequest().body("중복된 ISBN입니다.");

            } catch (Exception e) {
                // 저장 중에 예외가 발생한 경우 적절한 응답 반환
                log.error("저장 중 예외가 발생하였습니다.");
                throw new Exception("장바구니에 이미 해당 책이 있습니다. 추가할 수 없습니다.", e);
                //return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("장바구니에 이미 해당 책이 있습니다. 추가할 수 없습니다");
            }

        }else {
            log.info("bookList가 제대로 반환되지 않았습니다");
            //return ResponseEntity.badRequest().body("책 정보를 찾을 수 없습니다.");
            throw new Exception("책 정보를 찾을 수 없습니다.");
        }
    }

parseBookInfo 메서드로 List_BookItem형태의 bookItem 가져온 후
비어있지 않을 때 bookVO.setItems로 가져온 bookItems 객체를 매핑해줌

그 다음에 convertToEntity를 통해 BOOKVO->BOOK 객체로 변환한 후 " bookService.saveBook(book);"를 통해 DB에 저장


3. 정리

1) JSON 형태를 바로 BOOK 객체로 가져올 수 없음
JSON -> BookVO(외부 들어오는 값 처리) -> BOOK(내부 로직 처리 위한 객체)
근데 JSON 형태를 바로 BookVO 객체로 변환할 수 없으니 BookItem을 통해 한 번 더 처리
총, JSON -> BookItem(단일 매핑 객체) -> BookVO(외부 들어오는 값 처리) -> BOOK(내부 로직 처리 위한 객체)

2) (별표) 로그를 통해 중간중간 상황을 확인하자!

+) 추가 convertEntity

    public Book convertToEntity(BookVO bookVO) {
        BookItem item = bookVO.getItems().get(0); // 첫 번째 BookItem을 가져옴
        Book book = new Book();
        book.setName(item.getTitle());
        book.setIsbn(item.getIsbn());
        book.setAuthor(item.getAuthor());
        book.setPublisher(item.getPublisher());
        book.setImage(item.getImage());
        book.setPubdate(item.getPubdate());
        book.setStockQuantity(Integer.parseInt(item.getDiscount()));
        book.setLink(item.getLink());

        return book;
    }
profile
복학생의 개발 일기

0개의 댓글

관련 채용 정보