🌿Elasticsearch 검색 기능 추가
엘라스틱서치로 도서를 검색하는 기능을 구현해 보도록 하자.
이는 기존 프로젝트에 엘라스틱서치 검색 기능을 추가하는 작업이다.
프로젝트 생성
- com.test.controller
- com.test.persistence
- com.test.domain
- com.test.mapper
패키지를 추가하고 servlet-context.xml에서 패키지를 인식하도록 수정한다.
파일 추가
com.test.controller
- BookController.java
com.test.persistence
- BookDAO.java(I) > 오라클 데이터 처리
- BookDAOImpl.java(C)
- BookRepository.java(I) > 엘라스틱서치 데이터 처리
- BookRepositoryImpl.java(C)
com.test.domain
- BookDTO.java
com.test.mapper
ProjectMapper.java(I)
- views
- list.jsp
- add.jsp
- view.jsp
ElasticsearchProject
- script.sql
시나리오
1. 스프링 프로젝트가 완성된 상태(도서 관리 애플리케이션)
2. 도서 목록보기, 도서 상세보기, 도서 추가하기가 구현됨
3. 모든 업무 > 오라클 연동(MyBatis)
4. 도서 검색하기 > 엘라스틱서치로 구현
- 목록보기 > 오라클
- 상세보기 > 오라클
- 추가하기 > 오라클, 엘라스틱서치
- 검색하기 > 엘라스틱서치
- 삭제하기 > 오라클, 엘라스틱서치
- 수정하기 > 오라클, 엘라스틱서치
🌿프로젝트 설정
1. 의존성 추가
<!-- elasticsearch-rest-high-level-client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.15</version>
</dependency>
pom.xml에 elasticsearch-rest-high-level-client 의존성을 추가해 주었다.
2. 이미 구축된 오라클 데이터 베이스의 일부 정보를 엘라스틱서치로 전송
검색할 정보를 엘라스틱서치로 전송하는 작업을 사전에 진행해야 한다.
tblBook의 모든 데이터를 넘기는 건 아니고, 검색 대상 컬럼과 검색 결과 화면으로 출력하는 부분을 넘기게 된다.
눈에 보이지 않는 컬럼값을 엘라스틱서치에 옮겨 담을 필요는 없다. 따라서 tblBook에서 엘라스틱서치로 옮기게 되는 값은 seq, title, image, discount, author, publisher 컬럼이다.
3.1 오라클(tblBook)을 엘라스틱서치(book)로 복사
PUT book
{
"mappings" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"discount" : {
"type" : "long"
},
"image" : {
"type" : "keyword"
},
"publisher" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"seq" : {
"type" : "long"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
Oracle의 tblBook을 옮겨 담을 book을 만들고, 매핑 정보를 넣었다.
이제 오라클에서 만든 tblBook 테이블을 엘라스틱서치 book 인덱스로 복사하도록 하자. 이때 Logstash(Beats)를 사용하게 된다. Logstash와 Beats는 기능의 범위와 속도에 차이가 있다.
Logstash를 사용하기 위해서는 관련된 파일이 필요하다.
input {
jdbc {
jdbc_driver_library => "/home/ubuntu/ojdbc6.jar"
jdbc_driver_class => "Java::oracle.jdbc.driver.OracleDriver"
jdbc_connection_string => "jdbc:oracle:thin:@192.168.112.1:1521/xe"
jdbc_user => "hr"
jdbc_password => "java1234"
statement => "select seq, title, image, discount, author, publisher from tblBook"
jdbc_validate_connection => "true"
}
}
filter {
mutate {
remove_field => ["@version", "@timestamp"]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "book"
document_id => "%{seq}"
}
}
Logstash의 역할은 다양한 소스(input)로부터 내보내기를 할 수 있다. 즉, Logstash는 누군가로부터 input을 하고, 다른 누군가로 output을 하는 기능을 한다.
오라클의 데이터베이스를 데이터 소스로 가져와서 엘라스틱서치로 내보내기 작업을 하게 되며, 꼭 오라클에서 엘라스틱서치로 뿐만 아니라 DB에서 DB로 옮기기도 하는 중개인 역할을 하기도 한다.
DB로 연결을 할 때 JDBC를 사용하기 때문에 이 코드는 JDBC 연결 문자열과 관련된 내용이다. 이 설정을 Logstash에게 알려주면 어떤 데이터를 가져와야 하는지를 이해하고 알아서 데이터를 가져온다.
filter를 이용하면 모든 데이터를 그대로 가져오지 않고 데이터를 변형하여 가져올 수 있다. version과 timestamp값이 자동으로 생기기 때문에 이를 지워달라는 의미로 filter를 사용하였다.
output은 어디로 내보낼지에 대한 내용이며, elasticsearch로 내보내겠다는 의미로 사용했다. 이때 기본키를 이전하기 위해 document_id => "%{seq}"를 반드시 작성해야 한다.
a. ojdbc6.jar를 Ubuntu(WSL)에 복사하기
$ explorer.exe .
현재 위치에서 윈도우 창을 열라는 의미이다. 이는 현재 Ubuntu가 현재 위치한 실제 폴더이다.
이렇게 탐색기를 이용해서 작업을 하는 것도 가능하다. 탐색기가 열리면 해당 폴더에 ojdbc6.jar를 찾아 복사 붙여 넣기 한다.
b. Logstash Config 파일 생성 및 데이터 옮기기
/home/ubuntu/book.conf 위 경로에서 book.conf 파일을 생성한다.
이를 메모장으로 열어서 코드를 복사 붙여넣기 한다. 여기서 수정해야 하는 건 jdbc_connection_string, jdbc_user, jdbc_password이다.
$ ip route show | grep -i default | awk '{print $3}'
WSL은 또 다른 IP 주소가 있다고 했다. 위 코드를 Ubuntu에서 실행하여 WSL에서 오라클의 내부 IP 주소를 확인할 수 있다.
이제 방화벽의 고급 설정 > 인바운드 규칙에서 1521번을 열어 주도록 한다.
$ sudo /usr/share/logstash/bin/logstash -f book.conf
이제 데이터를 옮기는 코드를 실행한다.
Dev Tools에서 GET book/_search로 데이터가 잘 옮겨진 것을 확인할 수 있다.
3.2 동의어와 불용어를 추가하여 오라클(tblBook)을 엘라스틱서치(book)로 복사
PUT book
{
"mappings" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"discount" : {
"type" : "long"
},
"image" : {
"type" : "keyword"
},
"publisher" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"seq" : {
"type" : "long"
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
},
"analyzer": "nori_token"
}
}
},
"settings": {
"analysis": {
"analyzer": {
"nori_token": {
"type": "custom",
"tokenizer": "nori_tokenizer",
"filter": ["my_synonym", "my_stop"]
}
},
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms_path": "/etc/elasticsearch/synonym.txt"
},
"my_stop": {
"type": "stop",
"stopwords_path": "/etc/elasticsearch/stop.txt"
}
}
}
}
}
3.1 방법과 다르게 3.2 방법에서는 한글 검색을 위한 nori를 설정하고, 동의어를 설정할 synonym.txt를 my_stop으로 제거할 불용어를 stop.txt에 저장하도록 한다.
만약 동의어와 불용어가 제대로 적용되지 않는다면 경로를 확인해 보도록 한다.
a. synonym.txt, stop.txt 생성
$ sudo vi /etc/elasticsearch/synonym.txt
$ sudo vi /etc/elasticsearch/stop.txt
동의어와 불용어 파일을 생성하고 단어를 추가하였다.
불용어 파일을 만들면 반드시 마지막 줄은 빈 칸으로 개행이 되어 있어야 한다.
b. nori 설치하기
$ cd /usr/share/elasticsearch
$ sudo bin/elasticsearch-plugin install analysis-nori
$ sudo /usr/share/logstash/bin/logstash -f book.conf
nori를 설치한 뒤, Elasticsearch를 재시작하고 PUT book 코드를 실행해 주면 된다. 또한 현재 데이터가 없는 상태이므로 다시 Logstash에 데이터를 넣어주어야 한다.
참고로 sudo /usr/share/logstash/bin/logstash -f book.conf를 할 때에는 cd ~로 나가 주어야 한다.
🌿전체 코드
🍃BookController.java
package com.test.controller;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.test.domain.BookDTO;
import com.test.persistence.BookDAO;
import com.test.persistence.BookRepository;
@Controller
public class BookController {
@Autowired
private BookDAO dao; //오라클 담당자
@Autowired
private BookRepository repo; //엘라스틱서치 담당자
//목록보기
@GetMapping(value = "/list.do")
public String list(Model model, String word) {
if (word == null || word.equals("")) {
//검색(X) > 목록보기 > 오라클에서 조회
List<BookDTO> list = dao.list();
model.addAttribute("list", list);
} else {
//검색(O) > 검색하기 > 엘라스틱서치에서 조회
List<Map<String,Object>> list = repo.search(word);
model.addAttribute("list", list);
model.addAttribute("word", word);
}
return "list";
}
//추가하기(폼)
@GetMapping(value = "/add.do")
public String add(Model model) {
return "add";
}
//추가하기(처리)
@PostMapping(value = "/addok.do")
public String addok(Model model, BookDTO dto) {
dao.add(dto); //DB -> seq 발생
String seq = dao.getSeq();
dto.setSeq(seq);
repo.add(dto); //ES
return "redirect:/list.do";
}
//상세보기
@GetMapping(value = "/view.do")
public String view(Model model, String seq) {
BookDTO dto = dao.get(seq);
model.addAttribute("dto", dto);
return "view";
}
}
오라클 담당자와 엘라스틱서치 담당자
@Autowired
private BookDAO dao; //오라클 담당자
@Autowired
private BookRepository repo; //엘라스틱서치 담당자
//목록보기
@GetMapping(value = "/list.do")
public String list(Model model, String word) {
if (word == null || word.equals("")) {
//검색(X) > 목록보기 > 오라클에서 조회
List<BookDTO> list = dao.list();
model.addAttribute("list", list);
} else {
//검색(O) > 검색하기 > 엘라스틱서치에서 조회
List<Map<String,Object>> list = repo.search(word);
model.addAttribute("list", list);
model.addAttribute("word", word);
}
return "list";
}
오라클 담당자와 엘라스틱서치 담당자를 따로 만들어서 구현하도록 했다.
자바로 엘라스틱서치로 값을 가져오면 DTO로 따로 저장하지 않고, HashMap으로 저장하게 된다. 이때 Map<String,Object>와 BookDTO는 똑같은 것이다.
key와 값으로 따로 관리해야 하기 때문에 String에는 key(필드명)가, Object에는 값이 들어가게 된다. 이게 같을 수 있는 이유는 멤버변수의 이름과 엘라스틱서치에 들어간 필드명이 같기 때문이다.
그래서 JSP는 두 개가 다른 것인지 구분하지 못한다.
DTO를 JSON 형태로 변경
@Override
public void add(BookDTO dto) {
try {
ObjectMapper om = new ObjectMapper();
//DTO.toString() > JSON String
String data = om.writeValueAsString(dto);
//ElasticSearch 클라이언트 생성
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("172.24.129.47", 9200, "http")));
IndexRequest request = new IndexRequest("book")
.source(data, XContentType.JSON)
.setRefreshPolicy("wait_for");
request.id(dto.getSeq());
client.index(request, RequestOptions.DEFAULT);
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
jackson의 ObjectMapper를 이용하면 DTO를 JSON 형태로 변경해서 보낼 수 있다.
BookDAO.java(I) > 오라클 데이터 처리
package com.test.persistence;
import java.util.List;
import com.test.domain.BookDTO;
public interface BookDAO {
List<BookDTO> list();
BookDTO get(String seq);
void add(BookDTO dto);
String getSeq();
}
BookDAOImpl.java(C)
package com.test.persistence;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.test.domain.BookDTO;
import com.test.mapper.ProjectMapper;
@Repository
public class BookDAOImpl implements BookDAO {
@Autowired
private ProjectMapper mapper;
@Override
public List<BookDTO> list() {
return mapper.list();
}
@Override
public BookDTO get(String seq) {
return mapper.get(seq);
}
@Override
public void add(BookDTO dto) {
mapper.add(dto);
}
@Override
public String getSeq() {
return mapper.getSeq();
}
}
BookRepository.java(I) > 엘라스틱서치 데이터 처리
package com.test.persistence;
import java.util.List;
import java.util.Map;
import com.test.domain.BookDTO;
public interface BookRepository {
List<Map<String, Object>> search(String word);
void add(BookDTO dto);
}
🍃BookRepositoryImpl.java(C)
package com.test.persistence;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.domain.BookDTO;
@Repository
public class BookRepositoryImpl implements BookRepository {
@Override
public List<Map<String, Object>> search(String word) {
try {
//검색 결과를 저장할 리스트
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
//ElasticSearch 클라이언트 생성
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("172.24.129.47", 9200, "http")));
//검색을 수행할 인덱스 선택
SearchRequest searchRequest = new SearchRequest("book");
//검색 요청을 구성하는 빌더 생성
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().size(100);
//검색 쿼리 설정 (***)
//가장 흔하게 사용하는 패턴인 bool query를 사용하여 검색어에 대한 매칭 조건 구성
//bool query (must(match 검색어) + should(match_phrase 검색어))
searchSourceBuilder.query(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("title", word)) //title 필드에 검색어가 포함된 문서 검색
.should(QueryBuilders.matchPhraseQuery("title", word)) //title 필드에 정확한 검색어가 포함된 문서 검색
);
//검색 요청에 빌더 설정
searchRequest.source(searchSourceBuilder);
//실제 검색 요청 수행
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//검색 결과에서 검색된 문서들 가져오기
SearchHits searchHits = searchResponse.getHits();
//검색된 각 문서에 대한 정보 처리
for (SearchHit hit : searchHits) {
//문서의 소스 맵 가져오기
Map<String,Object> map = hit.getSourceAsMap();
//문서의 id 및 검색 스코어 정보 추가
map.put("id", hit.getId());
map.put("score", hit.getScore());
//리스트에 추가
list.add(map);
}
//검색 결과 리스트 반환
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void add(BookDTO dto) {
try {
ObjectMapper om = new ObjectMapper();
//DTO.toString() > JSON String
String data = om.writeValueAsString(dto);
//ElasticSearch 클라이언트 생성
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("172.24.129.47", 9200, "http")));
//인덱스 리퀘스트 생성 및 설정
IndexRequest request = new IndexRequest("book")
.source(data, XContentType.JSON)
.setRefreshPolicy("wait_for");
//문서 seq(id) 설정
request.id(dto.getSeq());
//인덱스에 문서 추가
client.index(request, RequestOptions.DEFAULT);
//클라이언트 종료
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
bool query (must, should)
//검색 쿼리 (***)
//검색에서 가장 흔하게 사용하는 패턴을 적용했다.
//bool query (must(match 검색어) + should(match_phrase 검색어))
searchSourceBuilder.query(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("title", word))
.should(QueryBuilders.matchPhraseQuery("title", word))
);
검색 쿼리로 가장 흔한 패턴인 bool query를 사용하였다.
GET spring/_search
{
"query": {
"match": {
"message": "lazy dog"
}
}
}
스코어가 가장 높은 게 lazy jumping dog이다. 우리가 찾은 건 lazy dog인데, 검색 결과로 lazy jumping dog 문장이 더 짧고, 참조되는 문서가 더 많은 등의 중요도로 찾은 결과이다. 그런데 우리 입장에서는 lazy jumping dog보다 lazy dog이 더 우선순위가 되는 게 맞다.
이때 should를 이용하여 스코어를 조정할 수 있다.
GET spring/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"message": "lazy dog"
}
}
],
"should": [
{
"match_phrase": {
"message": "lazy dog"
}
}
]
}
}
}
should를 사용하면 검색의 직관성을 더 높일 수 있다.
예로 들어 "프로젝터 리모컨"을 검색어로 했을 때, "프로젝터"와 "리모컨"이라는 단어를 찾겠다는 게 match 검색이라면, should 옵션을 넣어서 "프로젝트 리모컨"이라는 검색어의 스코어를 높여 사용자가 원하는 검색 결과를 먼저 보여주고, 그다음부터 연관된 검색 결과를 보여주도록 할 수 있다.
물론 이는 검색 결과에 따라서 달라지는 것이므로, 원하는 검색 결과가 이와 다르다면 다른 쿼리를 사용해야 한다.
엘라스틱서치에서 검색한 결과가 화면에 출력된다.
하지만 현재 한글 "자바"가 아닌 "Java"가 들어간 경우에만 검색이 되고 있는 상태이다. 또한 프로그램을 검색했을 때, 프로그래밍이 들어간 결과를 출력하고 싶기 때문에 수정이 필요하다.
이를 위해서는 "3.2 동의어와 불용어를 추가하여 오라클(tblBook)을 엘라스틱서치(book)로 복사" 파트를 진행해 주면 된다.
BookDTO.java
package com.test.domain;
import lombok.Data;
@Data
public class BookDTO {
private String seq;
private String title;
private String link;
private String description;
private String image;
private String author;
private String discount;
private String publisher;
private String isbn;
private String pubdate;
}
ProjectMapper.java(I)
package com.test.mapper;
import java.util.List;
import com.test.domain.BookDTO;
public interface ProjectMapper {
List<BookDTO> list();
BookDTO get(String seq);
void add(BookDTO dto);
String getSeq();
}
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
#list th:nth-child(1) { width: 100px; }
#list th:nth-child(2) { width: auto; }
#list th:nth-child(3) { width: 100px; }
#list th:nth-child(4) { width: 100px; }
#list th:nth-child(5) { width: 100px; }
#list td { text-align: center; }
#list td:nth-child(1) img { width: 90px; }
#list td:nth-child(2) { text-align: left; }
#list td:nth-child(3)::after { content: '원'; }
</style>
</head>
<body>
<!-- list.jsp -->
<h1 class="main">Book Project <small>List</small></h1>
<div class="seperate">
<div>
<button type="button" class="add" onclick="location.href='/project/add.do';">도서추가하기</button>
<button type="button" class="list" onclick="location.href='/project/list.do';">결과초기화</button>
</div>
<div>
<form method="GET" action="/project/list.do">
<input type="text" name="word" class="middle" required placeholder="제목 검색" value="${word}">
<input type="submit" value="검색하기">
</form>
</div>
</div>
<table id="list">
<tr>
<th>표지</th>
<th>제목</th>
<th>가격</th>
<th>저자</th>
<th>출판사</th>
</tr>
<c:forEach items="${list}" var="dto">
<tr>
<td><img src="${dto.image}"></td>
<td><a href="/project/view.do?seq=${dto.seq}">${dto.title}</a></td>
<td><fmt:formatNumber value="${dto.discount}" pattern="#,###" /> </td>
<td>${dto.author}</td>
<td>${dto.publisher}</td>
</tr>
</c:forEach>
</table>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<!-- add.jsp -->
<h1 class="main">Book Project <small>Add</small></h1>
<form method="POST" action="/project/addok.do">
<table class="vertical">
<tr>
<th>제목</th>
<td><input type="text" name="title" class="long" value="자바로 배우는 쉬운 자료구조"></td>
</tr>
<tr>
<th>링크</th>
<td><input type="text" name="link" class="full" placeholder="http://url" value="https://search.shopping.naver.com/book/catalog/32493312281?query=%EC%9E%90%EB%B0%94&NaPm=ct%3Dlpxxoldk%7Cci%3D58363bda8a8059524a144e953b8631444223ac16%7Ctr%3Dboksl%7Csn%3D95694%7Chk%3D623186dc20ad078827050e3a1e5487fa9ded76b0"></td>
</tr>
<tr>
<th>설명</th>
<td><textarea name="description" class="full">『자바로 배우는 쉬운 자료구조』은 알고리즘에 대해 C와 자바 프로그래밍으로 구체화 시키는 방법을 다룬다. 자료구조와 알고리즘을 어렵고 추상적인 이론으로만 다루지 않고 쉽게 이해할 수 있도록 다양한 그림을 통해 풀어 설명하고 자바 프로그래밍을 통하여 실제적으로 사용할 수 있도록 하였다.</textarea></td>
</tr>
<tr>
<th>표지</th>
<td><input type="text" name="image" class="full" placeholder="http://url" value="https://shopping-phinf.pstatic.net/main_3249331/32493312281.20230926084641.jpg?type=w300"></td>
</tr>
<tr>
<th>저자</th>
<td><input type="text" name="author" class="short" value="이지영"></td>
</tr>
<tr>
<th>가격</th>
<td><input type="number" name="discount" min="0" max="100000" value="23560"></td>
</tr>
<tr>
<th>출판사</th>
<td><input type="text" name="publisher" class="short" value="한빛아카데미"></td>
</tr>
<tr>
<th>ISBN</th>
<td><input type="text" name="isbn" class="short" value="9788998756420"></td>
</tr>
<tr>
<th>출판일</th>
<td><input type="text" name="pubdate" class="short" placeholder="20230101" value="20130730"></td>
</tr>
</table>
<div>
<button type="button" class="list" onclick="location.href='/project/list.do';">돌아가기</button>
<button type="submit" class="add">도서추가하기</button>
</div>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
view.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
table td img { height: 200px; object-fit: cover; }
</style>
</head>
<body>
<!-- view.jsp -->
<h1 class="main">Book Project <small>View</small></h1>
<div>
<button type="button" class="list" onclick="history.back();">돌아가기</button>
</div>
<table class="vertical">
<tr>
<th>제목</th>
<td>${dto.title}</td>
</tr>
<tr>
<th>링크</th>
<td><a href="${dto.link}" target="_blank">네이버 서점으로 이동하기</a></td>
</tr>
<tr>
<th>설명</th>
<td>${dto.description}</td>
</tr>
<tr>
<th>표지</th>
<td><img src="${dto.image}"></td>
</tr>
<tr>
<th>저자</th>
<td>${dto.author}</td>
</tr>
<tr>
<th>가격</th>
<td>${dto.discount}</td>
</tr>
<tr>
<th>출판사</th>
<td>${dto.publisher}</td>
</tr>
<tr>
<th>ISBN</th>
<td>${dto.isbn}</td>
</tr>
<tr>
<th>출판일</th>
<td>${dto.pubdate}</td>
</tr>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
script.sql
select * from tblBook;
select * from tabs order by table_name;
--ddl
create table tblBook (
seq number primary key,
title varchar2(1000) not null,
link varchar2(500) null,
description varchar2(4000) not null,
image varchar2(500) null,
author varchar2(300) null,
discount number null,
publisher varchar2(300) null,
isbn varchar2(100) null,
pubdate varchar2(30) null
);
--dml
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (1, '자바의 꿈 (이태복 시집)', 'https://search.shopping.naver.com/book/catalog/32466841922', '이태복 시인은 “정들어 고향이 된 자바 땅에”(「적도 나무꾼 일기」) 둥지를 틀고 하늘의 별자리를 바라보고 마을의 전설을 듣는다. 자바 아이들의 고무줄놀이를 구경하고 가믈란 음악을 듣고 오래 익은 술 같은 이웃들과 함께 살아간다. 일제가 식민지 여성들을 위안부로 짓밟은 역사를 암바라와 위안소에서 확인하며 일찍이 박인환 시인이 조선과 인도네시아 민중들에게 제국주의 국가들에 맞서 “최후의 한 사람까지 싸”(박인환 「인도네시아 인민에게 주는 시」)우자고 촉구한 목소리도 새긴다. 결국 이태복 시인은 자연에 겸손하고 사람들과 나눌 줄 알고 역사의식을 가질 때 인간은 행복할 수 있음을 일깨워준다. “돈의 종”(「살라띠가의 가을」)에서 벗어나 “달려도 달려도 끝없는/누런 들녘”(「1월 자바 들녘」)에서 민들레 같은 농부로 피었다 지는 꿈을 가지고 있는 시인은 한없이 행복하다. “호야불로 작은 등대를 세우고/희망의 심지를 돋”(「살라띠가의 호야불」)우는 하루하루가 자바 사람들의 미소처럼 밝은 것이다. -맹문재(시인, 안양대 교수)', 'https://shopping-phinf.pstatic.net/main_3246684/32466841922.20221227203319.jpg', '이태복', 8100, '시산맥사', '9791162430675', '20190718');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (2, '이펙티브 자바 (프로그래밍인사이트)', 'https://search.shopping.naver.com/book/catalog/32436239326', '자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응 자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브러리의 최신 기능을 십분 활용하도록 내용 전반을 철저히 다시 썼다. 모던 자바가 여러 패러다임을 지원하기 시작하면서 자바 개발자들에게는 구체적인 모범 사례가 더욱 절실해졌고, 관련 조언을 이 책에 담아낸 것이다. 3판에는 자바 7, 8, 9에서 자바 언어와 라이브러리에 추가된 특성들을 녹여냈다. 특히 그동안 객체 지향에 치중하던 자바에 새로 도입된 함수형 프로그래밍 요소도 자세히 알아본다. 람다(lambda)와 스트림(stream)만을 다룬 장을 포함하여 새로운 아이템도 많이 추가되었다. 새롭게 다루는 주제들 - 함수형 인터페이스, 람다식, 메서드 참조, 스트림 - 인터페이스의 디폴트 메서드와 정적 메서드 - 제네릭 타입에서의 다이아몬드 연산자를 포함한 타입 추론 - @SafeVarargs 애너테이션 - try-with-resources 문 - Optional T 인터페이스, java.time, 컬렉션의 편의 팩터리 메서드 등의 새로운 라이브러리 기능', 'https://shopping-phinf.pstatic.net/main_3243623/32436239326.20230919125641.jpg', '조슈아 블로크', 32400, '인사이트', '9788966262281', '20181101');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (3, '자바 (Seventh Edition)', 'https://search.shopping.naver.com/book/catalog/32467024674', '▶ 이 책은 자바를 다룬 이론서입니다. 자바의 기초적이고 전반적인 내용을 학습할 수 있도록 구성했습니다.', 'https://shopping-phinf.pstatic.net/main_3246702/32467024674.20221019150629.jpg', 'Horstmann, Cay S.', 31500, '한티미디어', '9788964211830', '20140303');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (4, '자바시장', 'https://search.shopping.naver.com/book/catalog/33435744838', '한국인들의 미국의류시장 개척기, 자바시장. 조국을 떠난다는 것. 그곳에서 살아간다는 것. 흔한 듯 흔하지 않은 이민세대들의 이야기. ‘자바시장’은 그 이야기를 하고 있다. 이민자이기 때문에 느낄 수밖에 없는 향수, 한국인이기 때문에 당할 수밖에 없었던 차별. 그러한 고난의 시간 속에서 LA에 한인 타운을 구축한 한국인들. 나아가 미국의 의류산업에까지 영향을 미칠 수 있는 자바시장을 이룩해낸 한국인들의 자랑스러운 모습을 작가는 가슴 뭉클하게 그려내고 있다. 조국을 떠나 새로운 삶의 터전을 맨몸으로 일구어내야 했던 이민자들. 힘겨운 시간을 이겨내고 힘껏 뿌리박은 한인들의 모습을 생동감 있고 또 사실감 있게 그릴 수 있었던 것은 작가가 그들의 곁에서 그들과 함께 호흡하고 생활한 결과물이다. 멀리 떨어져 있는 것으로만 여겼던, 그래서 다르다고만 생각했던 아득한 그들의 이야기를 우리 눈앞에 생생하게 보여주는 것. 그것이 ''자바시장''이다.', 'https://shopping-phinf.pstatic.net/main_3343574/33435744838.20221019152940.jpg', '박계상', 11700, '푸른사상', '9788956407159', '20090930');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (5, '오쿠자바의 노래시', 'https://search.shopping.naver.com/book/catalog/35303027404', '러시아 음유시의 대가, 오쿠자바 그의 따스한 목소리를 듣는다. 오쿠자바는 러시아 현대 문학에서 손꼽히는 시인으로 음유시가의 전통을 계승했다고 평가받는다. 자신의 시에 직접 곡을 붙이고, 기타 반주를 하며 대중 앞에서 노래하는 것을 즐겨해 그의 작품들을 노래시라고 부른다. 독자들에게 오쿠자바 노래시의 진면목을 알리기 위해 악보를 함께 실었다. 아르바트 거리와 사랑에 대한 예찬, 전쟁에 대한 비판 등 따뜻한 인간애를 바탕으로 한 그의 나직한 목소리는 신산한 삶의 고통을 위로하는 힘을 지녔다.', 'https://shopping-phinf.pstatic.net/main_3530302/35303027404.20231115072138.jpg', '불라트 오쿠자바', 10800, '지식을만드는지식(지만지)', '9788964065877', '20100915');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (6, '내가 글자 바보라고? (난독증인 종이접기 천재)', 'https://search.shopping.naver.com/book/catalog/39183796620', '우리가 모두 함께 고민하고 생각해 볼 문제를 재밌는 동화로 풀어내는 ‘내일을여는어린이’ 시리즈의 서른네 번째 책, 난독증이라는 장애, 그리고 다른 이의 약점을 대하는 우리의 모습에 관한 책이다. 작품 속 주인공은 난독증이라는 장애가 있다. 난독증의 정식 명칭은 ‘음운 인식 장애’다. 글자를 읽고 쓰는 뇌의 기능이 제대로 작동하지 않으면서 생기는 장애이다. 그래서 일반 사람에게는 아무것도 아닌, 글자를 읽고 쓰는 일이 주인공 하민이와 같은 이들에게는 너무나 어렵고 힘든 일이다. ☐ 작품 내용 하민이는 초등학생이다. 글자를 읽고, 쓰고, 배우고, 공부해야 할 때지만 하민이는 그게 너무 싫다. 글자가 너무너무 싫다. 왜냐하면 난독증이기 때문이다. ‘음운 인식 장애’란다. 하민이는 답답하고 억울하다. 하기 싫어서 안 하는 게 아닌데, 글자들이 자꾸 날아가 버리고 돌아가 버리고 바뀌어 버리는데 어떡하라고. 왜 나만 이런 약점이 있을까, 하민이는 너무나 힘들다. 결국 친구들에게 약점을 들키고 싶지 않아 자꾸만 자신을 숨긴다. 숨기는 방법은 간단하다. 아무것도 하지 않는 것. 친구들과 사귀지도 않고, 발표도 하지 않고. 그리고 자신이 잘하는 것, 종이접기에 몰두한다. 바로 유튜버 ‘지니핑거’! 영상 속 지니핑거는 현란한 손기술로 새로운 것들을 접어 낸다. 하민이는 지니핑거일 때는 자신감이 넘친다. 문득 하민이는 자신이 접은 카멜레온을 보며 생각한다. 세상에 자신의 약점을 드러내지 않기 위해 몸을 숨기는 카멜레온과 자신이 닮았다고. 그리고 또 단짝 친구 지안이를 보며 생각한다. 몸집이 작고 축구도 못하는 지안이가 툭하면 성질을 내고 친구들을 괴롭히는 모습을 보며, 약한 자신을 숨기려고 목 주위를 부풀려 다른 이들을 위협하는 목도리도마뱀 같다고. 지안이의 생일 파티 날. 자신은 난독증이고 유튜버 지니핑거라고 고백하려고 마음 먹은 날이었지만, 하필 이름이 너무 어려운 ‘킹콩점핑 키즈 카페’를 제대로 찾지 못해 계획을 모두 망쳐 버렸다. 자꾸만 지안이와 멀어져 버린다. ', 'https://shopping-phinf.pstatic.net/main_3918379/39183796620.20230926084842.jpg', '공윤경', 11700, '내일을여는책', '9788977469945', '20230404');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (7, 'Do it! HTML+CSS+자바스크립트 웹 표준의 정석 (한 권으로 끝내는 웹 기본 교과서)', 'https://search.shopping.naver.com/book/catalog/32466540266', '웹 분야 1위! 그만한 이유가 있다! 키보드를 잡고 실습하다 보면 웹 개발의 3대 기술이 끝난다! 웹의 기본을 한 권으로 끝낼 수 있는 책이 탄생했다! 8년 연속 웹 분야 도서 1위인 《Do it! HTML5+CSS3 웹 표준의 정석》의 고경희 저자는 ‘HTML, CSS, 자바스크립트를 한 권으로’ 배우고 싶다는 많은 독자의 요구를 모아 최신 경향을 반영한 이 책을 집필했다. 웹 개발을 시작하는 입문자도 쉽게 실습할 수 있고, 중요한 문법만 다시 공부하고 싶은 중ㆍ고급자에게도 도움이 된다. 친절한 설명은 기본! 핵심만 쏙쏙 뽑아서 실무에 바로 사용할 수 있는 예제도 풍성하게 담겨 있다. 또한 최신 웹 표준 기술인 HTML5, CSS3, 자바스크립트(ES6)를 기준으로 설명하며 책의 모든 내용을 압축한 최종 프로젝트 〈웹 사이트 만들기〉 PDF 전자책을 무료로 추가 제공한다. 수많은 독자가 입을 모아 ‘웹 분야의 교과서’라고 부르는 이유를 지금 확인해 보자!', 'https://shopping-phinf.pstatic.net/main_3246654/32466540266.20230920071258.jpg', '고경희', 9000, '이지스퍼블리싱', '9791163032212', '20210122');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (8, '모두의 자바 (하루 30분, 쉽게 배우는 자바 프로그래밍)', 'https://search.shopping.naver.com/book/catalog/32443142348', '이보다 쉬울 수 없다! 1:1 과외처럼 따라 하며 익히는 자바 기본기! 자바는 어렵다? 자바나 프로그래밍을 모르더라도 혼자서 배워볼 수는 없을까? 프로그래밍이 처음인 사람도, 자바가 처음인 사람도 부담 없이 쉽게 따라 하며 자바와 프로그래밍을 배울 수 있다! 각 Lesson은 반드시 알아야 하는 내용만으로 짧게 구성하여 하루 30분이면 충분히 학습할 수 있게 했다. 본문은 완성된 소스 코드를 제시하는 게 아니라 소스 코드가 완성되어 가는 과정을 하나하나 보여주면서 실제로 한줄 한줄 따라 입력해볼 수 있게 구성했다. 또한, 프로그래머스(programmers.co.kr) 사이트에서 저자의 동영상 강의와 온라인 실습 환경을 무료로 이용할 수 있다. 책, PC, 스마트폰, 어디에서든 자바를 학습해보자!', 'https://shopping-phinf.pstatic.net/main_3244314/32443142348.20221228073334.jpg', '강경미', 18000, '길벗', '9791165213213', '20201030');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (9, '이것이 자바다 (교육 현장에서 가장 많이 쓰이는 JAVA 프로그래밍의 기본서)', 'https://search.shopping.naver.com/book/catalog/34238594620', 'JAVA 17 버전으로 업그레이드해서 돌아왔다! 7년 동안 꾸준히 사랑받은 자바 베스트셀러 1위, 『이것이 자바다』 개정판! 『이것이 자바다』는 기본 개념에 충실한 설명으로 2015년 초판이 출간된 이후부터 지금까지 프로그래밍 언어를 배우려는 사람들에게 큰 사랑을 받아왔다. 특히 교육 기관에서 가장 많이 사용하는 자바 대표 입문서로서의 역할을 톡톡히 하면서 명실상부한 자바의 교과서로 이름을 알렸다. 이번 개정판은 기존 Java 8 버전에 최신 Java 17 LTS 버전까지 아우르는 내용으로 업그레이드하였으며, 더욱 풍부해진 708개의 실전 예제를 통해 이론으로 학습한 모든 내용을 직접 코드를 실행하며 따라 할 수 있도록 구성했다. 입문자뿐만 아니라 현직 개발자들도 항상 가까이에 두는 기본서로 꼽을 만큼 내실 있는 내용으로 꽉 채운 이번 개정판은 다시 한 번 독자들을 자바 정복의 길로 안내할 것이다.', 'https://shopping-phinf.pstatic.net/main_3423859/34238594620.20230620094626.jpg', '신용권 임경균', 32400, '한빛미디어', '9791169210027', '20220905');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (10, '자바 정복 (개정판)', 'https://search.shopping.naver.com/book/catalog/32489552157', '[도서 특징] 500개의 예제로 배우는 자바 문법의 모든 것! Java 17 버전을 기준으로 자바 문법을 총정리한 도서이다. 자바 문법을 체계적으로 안내하고 있으며 텍스트 블록, 패턴 매칭, 레코드 등을 최신 문법을 포함하였다. 자바 문법의 핵심을 파악할 수 있는 500여 개의 예제를 수록하여 자바 입문자도 혼자서 학습할 수 있다. 예제를 통해 핵심 문법과 응용을 실습한 후 클래스의 개념과 객체지향 등 이해하기 쉽지 않은 개념들을 알기 쉽게 설명한다. 또한, 실무에서 바로 활용할 수 있는 주요 클래스를 체계적으로 정리하고, 컬렉션과 고급 문법에 대한 레퍼런스를 제공하여 자바 개발자라면 항상 옆에 두고 참고할 만하다.', 'https://shopping-phinf.pstatic.net/main_3248955/32489552157.20230913071019.jpg', '김상형', 23400, '시대인', '9791138323666', '20220506');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (11, '자바 ORM 표준 JPA 프로그래밍 (스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임워크)', 'https://search.shopping.naver.com/book/catalog/32436007738', '자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것을 다룬다. 또한, 스프링 프레임워크와 JPA를 함께 사용하는 방법을 설명하고, 스프링 데이터 JPA, QueryDSL 같은 혁신적인 오픈 소스를 활용해서 자바 웹 애플리케이션을 효과적으로 개발하는 방법을 다룬다. 다음 링크에서 온라인 강의를 수강할 수 있다. ■ 강의 링크: https://www.inflearn.com/roadmaps/149 ■ 온라인 강의 목록 -자바 ORM 표준 JPA 프로그래밍 - 기본편: https://www.inflearn.com/course/ORM-JPA-Basic -실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발: https://www.inflearn.com/course/스프링부트-JPA-활용-1 -실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화: https://www.inflearn.com/course/스프링부트-JPA-API개발-성능최적화# -실전! 스프링 데이터 JPA: https://www.inflearn.com/course/스프링-데이터-JPA-실전 -실전! Querydsl: https://www.inflearn.com/course/Querydsl-실전', 'https://shopping-phinf.pstatic.net/main_3243600/32436007738.20221229072907.jpg', '김영한', 38700, '에이콘출판', '9788960777330', '20150728');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (12, '모던 자바스크립트 Deep Dive (자바스크립트의 기본 개념과 동작 원리)', 'https://search.shopping.naver.com/book/catalog/32472713016', '269개의 그림과 원리를 파헤치는 설명으로 ‘자바스크립트의 기본 개념과 동작 원리’를 이해하자! 웹페이지의 단순한 보조 기능을 처리하기 위한 제한적인 용도로 태어난 자바스크립트는 과도하다고 느껴질 만큼 친절한 프로그래밍 언어입니다. 이러한 자바스크립트의 특징은 편리한 경우도 있지만 내부 동작을 이해하기 어렵게 만들기도 합니다. 하지만 자바스크립트는 더 이상 제한적인 용도의 프로그래밍 언어가 아닙니다. 오랜 변화를 거쳐 이제 자바스크립트는 프런트엔드와 백엔드 영역의 프로그래밍 언어로 사용할 수 있는 명실상부한 범용 애플리케이션 개발 언어로 성장했습니다. 따라서 자바스크립트를 학습하는 방식도 이에 걸맞게 변화해야 하며, 이 책은 자바스크립트의 기본 개념과 동작 원리를 깊이 있게 학습하고자 하는 독자를 위해 기획되었습니다. 《모던 자바스크립트 Deep Dive》에서는 자바스크립트를 둘러싼 기본 개념을 정확하고 구체적으로 설명하고, 자바스크립트 코드의 동작 원리를 집요하게 파헤칩니다. 따라서 여러분이 작성한 코드가 컴퓨터 내부에서 어떻게 동작할 것인지 예측하고, 명확히 설명할 수 있도록 돕습니다. 또한 최신 자바스크립트 명세를 반영해 안정적이고 효율적인 코드를 작성할 수 있는 기본기를 다지고, 실전에서 쓰이는 모던 자바스크립트 프레임워크나 도구를 완벽하게 이해하고 활용할 수 있게 도와줍니다.', 'https://shopping-phinf.pstatic.net/main_3247271/32472713016.20221227210200.jpg', '이웅모', 5400, '위키북스', '9791158392239', '20200925');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (13, '자바사라사', 'https://search.shopping.naver.com/book/catalog/37260959620', '이 책은 일본동양사를 다룬 역사문화서이다. 자바사라사의 전반적인 내용이 수록되어 있다.', 'https://shopping-phinf.pstatic.net/main_3726095/37260959620.20230131165343.jpg', '다케다 린타로', 13500, '보고사', '9791165873950', '20221231');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (14, '자바 정복 (자바 프로그래밍 토탈 솔루션: JDK11 기준, 람다, 스트림, 모듈화)', 'https://search.shopping.naver.com/book/catalog/32459333624', '『자바 정복』은 자바8의 람다, 스트림과 자바 9의 모듈화를 포괄하여 최신 JDK 11을 기준으로 자바 문법을 총정리한 도서다. 순서대로 읽을 수 있도록 자습서 형식으로 구성하였으며 짧고 핵심적인 예제 480개로 자바 문법을 체계적으로 소개한다. 각장 마지막에는 문법 응용을 위한 연습문제를 제공하며 한 학기 강의에 적합하도록 구성하였다.', 'https://shopping-phinf.pstatic.net/main_3245933/32459333624.20221227205556.jpg', '김상형', 10000, '소엔', '9791196524609', '20181120');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (15, '코딩 자율학습 HTML + CSS + 자바스크립트 (기초부터 반응형 웹까지 초보자를 위한 웹 개발 입문서)', 'https://search.shopping.naver.com/book/catalog/32462974669', '코딩을 몰라도 걱정 제로, 이 책 하나로 충분히 웹 개발을 시작할 수 있다! 이 책은 코딩 초보자가 문법을 빠르고 재밌게 배울 수 있도록 실무에서 주로 사용하는 내용을 쏙쏙 골라 다양한 예제와 함께 다룹니다. 개발 환경 설정부터 HTML, CSS, 자바스크립트 기초까지 한 권에 담았고, 마지막에는 실무에서 유용하게 활용할 수 있는 나만의 포트폴리오 페이지를 만들어 배운 내용을 완성합니다. 단순한 코딩 및 결과 확인식 설명에서 벗어나 원리를 이해하며 학습할 수 있어서 외우지 않아도 자연스럽게 이해되며, 베타 학습단과 함께 내용을 검증해 초보자 눈높이에 맞춰 설명하므로 코딩 초보자도 비전공자도 충분히 웹 개발에 입문할 수 있습니다.', 'https://shopping-phinf.pstatic.net/main_3246297/32462974669.20230912084525.jpg', '김기수', 23090, '길벗', '9791165219468', '20220425');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (16, '난생처음 자바 프로그래밍 (실생활 예제부터 OpenCV까지 이해하기 쉬운 JAVA 입문서)', 'https://search.shopping.naver.com/book/catalog/40871281660', '코딩의 기초부터 차근차근 알려주는 비전공자를 위한 자바 입문서 이 책은 초보자에게 어려운 자바 개념을 다양한 삽화와 도식을 통해 시각적으로 설명하여 비전공자도 쉽게 이해할 수 있도록 구성하였습니다. 또한 각 장마다 수록된 실생활 예제 [LAB]과 [실전 예제]를 통해 자바 실력은 물론 문제 해결 능력까지 기를 수 있습니다. OpenCV 라이브러리를 활용한 완성도 높은 이미지 처리 프로젝트를 경험하며 실전 감각을 익힐 수 있습니다. ※ 본 도서는 대학 강의용 교재로 개발되었으므로 연습문제 해답은 제공하지 않습니다.', 'https://shopping-phinf.pstatic.net/main_4087128/40871281660.20230906071510.jpg', '우재남', 26100, '한빛아카데미', '9791156646624', '20230630');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (17, '퍼펙트 자바 (제2판)', 'https://search.shopping.naver.com/book/catalog/32496661886', '『퍼펙트 자바』는 자바 언어를 처음으로 이해하고자 하는 독자를 고려하여 기초적인 문법 해설과 예제를 중심으로 기술한 책이다. 기본적인 자바 문법을 정복할 수 있으며, 기초적인 구문 사용 방법을 터득하여 실무에서 활용될 수 있는 소규모 프로그램을 작성할 수 있다. 또한 다양한 해제와 해설을 수록했다.', 'https://shopping-phinf.pstatic.net/main_3249666/32496661886.20221019143042.jpg', '강환수', 23750, '청람', '9788959724215', '20140910');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (18, '그림으로 정리한 알고리즘과 자료구조 (자바 + 파이썬)', 'https://search.shopping.naver.com/book/catalog/32506021644', '세상을 이해하는 중요한 기준인 알고리즘과 자료구조의 모든 개념과 아이디어를 그림으로 표현하여 쉽게 이해할 수 있도록 하였고, 각 알고리즘과 자료구조의 원리를 자바와 파이썬 코드로 제시하여 상세히 알 수 있도록 하였다. 또 각 장 마지막에서 ‘요약’ 코너를 통해 어떤 내용을 학습했는지 핵심을 정리하여 알고리즘의 개념을 확실히 자신의 것으로 만들 수 있다.', 'https://shopping-phinf.pstatic.net/main_3250602/32506021644.20221213170229.jpg', '조민호', 10000, '정보문화사', '9788956747880', '20180810');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (19, '손안의 자바 (초보자를 위한 자바 프로그래밍의 핵심 + 알파)', 'https://search.shopping.naver.com/book/catalog/32506091350', '정보시스템 감리를 통해 70여 개 이상의 프로젝트 소스 코드를 검사했다. 이 소스코드 검사를 통해 실제 프로젝트에서 사용하는 문법이 무엇이며, 개발자들이 잘 실수하는 것이 무엇인지 알게 됐다. 본 책은 이러한 경험을 바탕으로 작성한 책이다. 초보 개발자들에게 실제 프로젝트에서 사용하는 핵심 문법 위주로 자바를 소개하며, 최신 버전의 자바의 기능과 관련해 앞으로 사용될 것 같다고 판단되는 장치들을 포함시켰다. 많은 자바 입문자가 이 책을 읽고 효율적인 길을 걸을 수 있기를 바란다. ★ 이 책에서 다루는 내용 ★ - 초보자를 위한 자바 문법 핵심 + 알파 - 실제 프로젝트에서 사용하는 문법 위주의 자바 프로그래밍 - 좋은 프로그램을 위한 기본적인 가이드 - 개발자들이 실수하기 쉬운 개념 - 정부가 주관하는 수십 개의 프로젝트에서 사용하지 않는 문법 제외', 'https://shopping-phinf.pstatic.net/main_3250609/32506091350.20221228073231.jpg', '김지훈 이현우', 27000, '에이콘출판', '9791161751832', '20180717');
insert into tblBook (seq, title, link, description, image, author, discount, publisher, isbn, pubdate) values (20, '자바라 자바', 'https://search.shopping.naver.com/book/catalog/32466543425', '『자바라 자바』는 객체지향의 핵심 개념과 원리를 이해시키기 위하여 현실세계로부터의 친숙한 예를 통해 상세한 예를 제시한다. 노트필기가 필요 없이 바로 본서가 노트가 될 수 있도록 프로그래밍에 있어서 기본적으로 필요한 내용들을 중심으로 구성하였다.', 'https://shopping-phinf.pstatic.net/main_3246654/32466543425.20221227203116.jpg', '한기태', 23750, '정익사', '9788935305063', '20130311');
🌿프로젝트를 AWS에 배포
MobaXterm에서 환경 확인 및 파일 설치를 진행한다.
1. 환경 확인
a. Tomcat 동작 유무
$ systemctl status tomcat
b. Oracle 동작 유무
$ systemctl status oracle-xe
2. 파일 설치
엘라스틱서치 설치
$ sudo apt-get update
$ curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch |sudo gpg --dearmor -o /usr/share/keyrings/elastic.gpg
$ echo "deb [signed-by=/usr/share/keyrings/elastic.gpg] https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
$ sudo apt-get update
$ sudo apt-get install elasticsearch
키바나, 로그스테시 설치
$ sudo apt-get install kibana
$ sudo apt-get install logstash
키바나와 로그스테시 설치를 해 주었다.
자동 시작
$ sudo systemctl enable elasticsearch
$ sudo systemctl enable kibana
Ubuntu를 실행하면 elasticsearch, kibana가 자동으로 실행되도록 설정해 주었다.
3. 외부 접속 허용
- http://AWS IP:9200
$ sudo vi /etc/elasticsearch/elasticsearch.yml
$ sudo systemctl restart elasticsearch
elasticsearch가 외부에서도 접속을 허용하도록 yml 파일의 network와 cluster 부분을 수정했다.
Kibana 외부설정
- http://AWS IP:5601
$ sudo vi /etc/kibana/kibana.yml
$ sudo systemctl restart kibana
elasticsearch와 마찬가지로 kibana에서도 외부 연결을 허용하도록 server를 수정했다.
Kibana는 켜지는 데 시간이 걸리는 편이다.
🍃war 파일 생성 및 프로젝트 배포
DBUtil.java
package com.test.java;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBUtil {
private static Connection conn;
public static Connection open() {
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String id = "hr";
String pw = "java1234";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(url, id, pw);
return conn;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Connection open(String server, String id, String pw) {
String url = "jdbc:oracle:thin:@" + server + ":1521:xe";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(url, id, pw);
return conn;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Hello.java
package com.test.java;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello.do")
public class Hello extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
Connection conn = DBUtil.open();
Statement stat = conn.createStatement();
String sql = "select count(*) as cnt from tabs";
ResultSet rs = stat.executeQuery(sql);
if(rs.next()) {
req.setAttribute("cnt", rs.getInt("cnt"));
}
} catch (Exception e) {
e.printStackTrace();
}
RequestDispatcher dispatcher = req.getRequestDispatcher("/hello.jsp");
dispatcher.forward(req, resp);
}
}
hello.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<!-- hello.jsp -->
<h1>결과</h1>
<div>테이블 수: ${cnt}</div>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
lib 폴더에 jstl-1.2.jar과 ojdbc6.jar 파일을 넣어주었다.
war 파일 생성
- *.jar > 압축 파일
- *.war > 웹 애플리케이션 압축 파일(=zip)
내보내기 할 때 Destination의 이름이 root-context의 이름이 된다.
- /opt/tomcat/apache-tomcat-9.0.83/webapps/
경로에 내보내기한 war 파일을 넣어준다.
- http://AWS IP:8080/test/hello.do
로컬에서는 테이블 수가 75개인데, AWS에서는 테이블 수가 7개인 것을 확인할 수 있다.
현재 AWS에는 데이터가 없는 상태이므로 테이블의 ddl과 dml을 AWS 계정으로 실행해서 데이터를 추가한다.
테이블 수가 7에서 8이 됐으므로 AWS에서 테이블이 잘 생성된 것이다.
elasticsearch.yml 수정
$ sudo vi /etc/elasticsearch/elasticsearch.yml
$ sudo systemctl restart elasticsearch
node.name: node-1 주석을 해제한다.
book 인덱스 매핑
$ curl -X PUT "localhost:9200/book" -H "Content-Type: application/json" -d '{ "mappings" : { "properties" : { "author" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "discount" : { "type" : "long" }, "image" : { "type" : "keyword" }, "publisher" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "seq" : { "type" : "long" }, "title" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } }, "analyzer": "nori_token" } } }, "settings": { "analysis": { "analyzer": { "nori_token": { "type": "custom", "tokenizer": "nori_tokenizer", "filter": ["my_synonym", "my_stop"] } }, "filter": { "my_synonym": { "type": "synonym", "synonyms_path": "synonym.txt" }, "my_stop": { "type": "stop", "stopwords_path": "stop.txt" } } } } }'
book.conf 파일 수정
$ sudo find /u01 -name ojdbc6.jar
$ sudo vi book.conf
- /u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar
input {
jdbc {
jdbc_driver_library => "/u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar"
jdbc_driver_class => "Java::oracle.jdbc.driver.OracleDriver"
jdbc_connection_string => "jdbc:oracle:thin:@localhost:1521/xe"
jdbc_user => "hr"
jdbc_password => "java1234"
statement => "select seq, title, image, discount, author, publisher from tblBook"
jdbc_validate_connection => "true"
}
}
filter {
mutate {
remove_field => ["@version", "@timestamp"]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "book"
document_id => "%{seq}"
}
}
ojdbc6가 들어 있는 경로를 먼저 확인한 뒤에, book.conf 파일을 수정한다.
위 코드는 경로가 수정되어 있는 상태이기 떄문에 그대로 사용하면 된다.
logstash로 데이터 이동 준비
$ sudo /usr/share/logstash/bin/logstash -f book.conf
logstash로 옮기는 과정까지 시간이 꽤 걸리는 편이다.
Synonym으로 등록한 "자바 => Java"가 적용되는 것으로 보아 Elasticsearch 검색 기능이 적용된 프로젝트를 AWS로 배포하는 데 성공했다는 것을 알 수 있다.