과제를 받았다.
일단 https://developers.kakao.com/ 여기서 "내 애플리케이션"에서 app을 생성한 뒤, API KEY를 받아두었다.
아래는 과제 내용이다.
※ 순수 Java로만 구현하기 위해 진행한 과제이다. lombok,JPA도 안썼다!
- DB : mySQL, JDBC(!)
[과제 내용]
책 검색 및 데이터베이스 Java 애플리케이션 개발
이 프로그램은 Kakao Book Open API를 활용하여 책을 검색하고, 검색한 책 데이터를 데이터베이스에 저장하는 기능을 제공합니다.
1.문제 설명
단계 1: Kakao API 키 획득 <- 위에서 발급한 API KEY를 의미한다.
단계 2: 책 검색 API 사용
단계 3: Java 애플리케이션 구현
Kakao Book Open API와 상호작용하는 Java 애플리케이션을 설계합니다.
입력으로 책 제목을 제공하고 API에서 받아온 책 데이터를 JSON 형식으로 파싱합니다.
검색한 책에 관한 정보를 도서 제목, 가격, 출판사, 저자, 할인 가격 및 ISBN과 같이 적절한 정보로 출력합니다.
책 데이터는 기본 10개를 출력합니다.
단계 4: 데이터베이스 저장
검색 결과를 출력한 후, 사용자에게 데이터베이스에 저장할지 여부를 선택하도록 안내합니다.
사용자가 저장을 선택한 경우 (Y), 관련 책 정보를 데이터베이스에 저장하는 로직을 구현합니다.
데이터베이스에 저장된 책 목록을 도서 제목을 기준으로 오름차순 정렬하여 불러오는 로직을 구현합니다.
2.입력 및 출력 예시
[입력 화면]
도서를 검색할 제목을 입력하세요: 자바의 정석
[출력 결과]
데이터베이스에 저장하시겠습니까? Y/N
저장성공
[TABLE LIST]
생각보다 어렵지 않고 재밌어보였고, 구현하면서도 재밌게 했던 것 같다ㅋㅋ 생각보다 체질에 맞을지도,,
암튼 각설하고, 어떻게 구현했는지 코드를 보자.
[클래스 다이어그램]
클래스는 총 4개다.
- Main : 유저의 Input/Output을 관리하고 애플리케이션 전체 흐름을 관장하는 클래스이다.
- KakaoAPI : 카카오 API와 http 연결을 통해 응답을 받아오고 api_key를 관리하는 클래스이다.
- BookDAO : mysqlDB와 연결 및 CRUD를 관리하는 클래스이다.
- Book : 책 정보 및 속성을 관리하는 클래스이다.
[시퀀스 다이어그램]
시퀀스 다이어그램을 보면,
1. main 클래스의 main메서드에서 searchBookList 메서드를 실행한다.
searchBookList를 실행하면 사용자에게 검색할 keyword를 입력받는다.
만약 키워드가 빈 값이면 "키워드가 빈 값입니다"를 출력하고 종료시킨다.
키워드가 있다면 아래 2번을 실행한다.
2. 키워드가 있다면 kakaoAPI클래스의 findBookByKeyword 메서드를 실행한다.
- kakaoAPI에 요청할 URL을 생성한다.
String url = search_book_URL + "?query=" + URLEncoder.encode(keyword, "UTF-8");
requestApi 메서드를 실행하여 kakaoAPI와 HTTP 통신을 연결한다.
reqeustApi는 CloseablehttpClient를 활영하여 http request 메시지의 헤더 정보를 설정하고 요청을보낸다.
이후 httpResponse를 통해 위 url에 부합하는 정보를 얻어온다.
얻어온 정보를 버퍼에 담고, json으로 반환한다.
3. 반환한 json을에 담긴 책정보 중 필요한 key만 골라서 Book 객체에 담아준다.
title,price,publisher,sale_price,authors,isbn
book정보는 총 10권까지 받아오므로, 각 Book객체는 List타입의 BookList에 모아준다.
이후 받아온 book 정보를 콘솔창에 출력한다.
Npostman으로 쿼리에 "자바의 정석"을 넣고 get요청해 받은 책 정보.json
4. Main 클래스 main메서드에서 saveBookList메서드를 실행하여 DB에 저장한다.
유저가 "데이터베이스에 저장하시겠습니까?Y/N" 에 대한 응답을 Y로 입력했다면, 저장로직을 시작한다.
저장로직이 담긴 메서드 실행 : saveBooksList(ArrayList<Book> booklist)
BookDAO 객체를 생성하고 매개변수로 받은 booklist를 for문을 통해 책 1권씩 DB에 insert해준다.
위 insert는 BookDAO의 insertData 메서드를 실행시켜 진행한다.
5. BookDAO 클래스의 insertData 메서드를 실행하면 mysql DB에 데이터가 저장된다.
public int insertData(Book book) {
String SQL = "insert into task2(title,price,publisher,authors,discountPrice,ISBN) values(?,?,?,?,?,?)";
getConnection();
int cnt = 1;
try {
preparedStatement=connection.prepareStatement(SQL);
preparedStatement.setString(1,book.getTitle());
preparedStatement.setInt(2,book.getPrice());
preparedStatement.setString(3,book.getPublisher());
preparedStatement.setObject(4,book.getAuthors().get(0));
preparedStatement.setInt(5,book.getDiscountPrice());
preparedStatement.setString(6,book.getISBN());
cnt = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
dbclose();
}
return cnt;
}
6. BookDAO 클래스의 findAllBookList 메서드를 실행시켜 DB에 저장된 책을 오름차순으로 출력한다.
애플리케이션 입출력 화면
[Review]
뭐 요구사항을 만족하는데 큰 문제는 없지만,,몇가지 해결되지 않은 의문이 있다.
1. 순수 자바로 구현할 때 API_KEY는 어떻게 보호할 수 있을까?
2. kakaoAPI에서 json으로 정보를 받아오면, 파싱하는 과정이 번거로운 경우가 있다.
방금도 KakaoAPI.java 내 findBookListByKeyword 메서드를 통해 카카오API에서 jsonobject를 먼저 받아온 후, key값이 'documents'인 JSONArray만 추출하는 과정이 있었다. 이처럼 JSON 구조를 한꺼풀씩 벗겨나가는 게 상당히 번거로운데, Gson 라이브러리를 이용하면 이런 불편함을 해소할 수 있는지 ?
3. authours 타입만 JSONArray로 감싸져 있어서 파싱하는데 불편함을 겪었는데, String으로 형변환해서 간편하게 사용할 수 있는 방법이 있을지??
이와 같은 고민을 좀...하는 중이다. 더 좋게 해결할 수 있는 방법이 있을지..?
[전체 코드]
public class Main {
private static final Scanner scanner = new Scanner(System.in);
private static final KakaoAPI kakaoAPI = new KakaoAPI();
public static void main(String[] args) throws Exception {
ArrayList<Book> booklist = searchBookList();
saveBooksList(booklist);
scanner.close();
}
private static ArrayList<Book> searchBookList() throws IOException {
System.out.print("키워드를 입력하세요: ");
String keyword = scanner.nextLine();
ArrayList<Book> booklist = null;
if (!keyword.isEmpty()) {
System.out.println("도서 제목 " + " | " + "가격 " + " | " + "출판사 " + " | " + "작가 " + " | " + "할인 가격 " + " | " + "ISBN");
booklist = kakaoAPI.findBookListByKeyword(keyword);
} else {
System.out.println("키워드가 빈 값 입니다.");
}
return booklist;
}
private static void saveBooksList(ArrayList<Book> booklist) throws Exception {
if (booklist.size() != 0) {
System.out.println("데이터베이스에 저장하시겠습니까? Y/N");
String answerSaveDB = scanner.next();
if (answerSaveDB.equals("Y")) {
System.out.println("저장 시작");
BookDAO dao = new BookDAO();
for (int i = 0; i < booklist.size(); i++) {
int cnt = dao.insertData(booklist.get(i));
if (cnt > 0) {
System.out.println("저장성공 " + (i + 1));
} else {
System.out.println("저장실패");
}
}
System.out.println("[TABLE LIST]");
System.out.println("도서 제목 " + " | " + "가격 " + " | " + "출판사 " + " | " + "작가 " + " | " + "할인 가격 " + " | " + "ISBN");
dao.findAllBookList();
} else {
System.out.println("저장하지 않고 종료");
}
} else {
System.out.println("조회 서비스를 종료합니다.");
}
}
}
public class KakaoAPI {
private static String API_KEY = "aaaaaaaaaaaaaaaaaaaa";
private static String search_book_URL = "https://dapi.kakao.com/v3/search/book";
public ArrayList<Book> findBookListByKeyword(String keyword) throws IOException {
String url = search_book_URL + "?query=" + URLEncoder.encode(keyword, "UTF-8");
JSONObject BookInformationJson = requestApi(url);
JSONArray documents = BookInformationJson.getJSONArray("documents");
ArrayList<Book> booklist = new ArrayList<>();
if (documents.length() != 0) {
for (int i = 0; i < documents.length(); i++) {
JSONObject bookObject = documents.getJSONObject(i);
Book book = new Book((String) bookObject.get("title"), (Integer) bookObject.get("price"), (String) bookObject.get("publisher"), (Integer) bookObject.get("sale_price"), (JSONArray) bookObject.get("authors"), (String) bookObject.get("isbn"));
booklist.add(book);
System.out.println(book);
}
} else {
System.out.println("찾으려는 도서가 없습니다.");
}
return booklist;
}
private JSONObject requestApi(String apiUrl) {
String BookInformationJson = "";
try {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpGet getRequest = new HttpGet(apiUrl);
getRequest.setHeader("Authorization","KakaoAK "+API_KEY);
HttpResponse getResponse = httpClient.execute(getRequest);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getResponse.getEntity().getContent(),"UTF-8"));
BookInformationJson = bufferedReader.readLine();
httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
return new JSONObject(BookInformationJson);
}
}
public class BookDAO {
private Connection connection;
private Statement statement;
private PreparedStatement preparedStatement;
private ResultSet resultSet;
public void getConnection() {
String url = "jdbc:mysql://localhost:3306/aaaaaaa";
String username = "aaaaaa";
String password = "aaaaaa";
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
}
public int insertData(Book book) {
String SQL = "insert into task2(title,price,publisher,authors,discountPrice,ISBN) values(?,?,?,?,?,?)";
getConnection();
int cnt = 1;
try {
preparedStatement=connection.prepareStatement(SQL);
preparedStatement.setString(1,book.getTitle());
preparedStatement.setInt(2,book.getPrice());
preparedStatement.setString(3,book.getPublisher());
preparedStatement.setObject(4,book.getAuthors().get(0));
preparedStatement.setInt(5,book.getDiscountPrice());
preparedStatement.setString(6,book.getISBN());
cnt = preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
dbclose();
}
return cnt;
}
public void findAllBookList() throws Exception {
String SQL = "select * from task2 order by title";
getConnection();
int cnt = 1;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery(SQL);
while (resultSet.next()) {
String title = resultSet.getString("title");
int price = resultSet.getInt("price");
String publisher = resultSet.getString("publisher");
int discountPrice = resultSet.getInt("discountPrice");
String authors = resultSet.getString("authors");
String ISBN = resultSet.getString("ISBN");
System.out.println(title + " | " + price + " | " + discountPrice + " | " + authors + " | " + ISBN);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
dbclose();
}
}
public void dbclose() {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Book {
String title;
Integer price;
String publisher;
JSONArray authors;
Integer discountPrice;
String ISBN;
public Book(String title, Integer price, String publisher, Integer discountPrice, JSONArray authors, String ISBN) throws UnsupportedEncodingException {
this.title = title;
this.price = price;
this.publisher = publisher;
this.authors = authors;
this.discountPrice = discountPrice;
this.ISBN = ISBN;
}
@Override
public String toString() {
return this.title + " | " + this.price + " | " + this.publisher + " | " + this.authors.get(0) + " | " + this.discountPrice + " | " + this.ISBN;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public JSONArray getAuthors() {
return authors;
}
public void setAuthors(JSONArray authors) {
this.authors = authors;
}
public Integer getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(Integer discountPrice) {
this.discountPrice = discountPrice;
}
public String getISBN() {
return ISBN;
}
public void setISBN(String ISBN) {
this.ISBN = ISBN;
}
}
감사합니다.