MERGE INTO 문
-- 하나의 SQL문으로 INSERT, UPDATE, DELETE 작업을 수행할 수 있다.
-- 형식
-- MERGE INTO 테이블명
-- USING DUAL
-- ON (컬럼명 = 값 AND 컬럼명 = 값) // 이 조건에 맞는 행이 있다면
-- WHEN MATCH THEN
-- UPDATE
-- SET 컬럼명 = 값 // UPDATE를 하고
-- WHEN NOT MACHTED THEN
-- INSERT (컬럼명, 컬럼명, 컬럼명)
-- VALUES (값, 값, 값) // 업다면 INSERT를 진행한다.
MERGE INTO SAMPLE_BOARD_BOOK_CART_ITEMS
USING DUAL
ON (BOOK_NO = 100022 AND USER_ID = 'hong')
WHEN MATCHED THEN
UPDATE
SET ITEM_AMOUNT = ITEM_AMOUNT + 1
WHEN NOT MATCHED THEN
INSERT (ITEM_NO, BOOK_NO, USER_ID)
VALUES (SAMPLE_CARTS_SEQ.NEXTVAL, 100022, 'hong');
기존
실행 후
MERGE INTO SAMPLE_BOARD_BOOK_CART_ITEMS
USING DUAL
ON (BOOK_NO = 100010 AND USER_ID = 'hong')
WHEN MATCHED THEN
UPDATE
SET ITEM_AMOUNT = ITEM_AMOUNT + 1
WHEN NOT MATCHED THEN
INSERT (ITEM_NO, BOOK_NO, USER_ID)
VALUES (SAMPLE_CARTS_SEQ.NEXTVAL, 100010, 'hong');
없던 목록도 추가된다.
carts.xml
<insert id="insertCartItem" parameterClass="com.sample.vo.CartItem">
merge into sample_board_book_cart_items
using dual
on (book_no = #bookNo# and user_id = #userId#)
when matched then
update
set
item_amount = item_amount + 1,
item_updated_date = sysdate
when not matched then
insert (item_no, book_no, user_id)
values (sample_carts_seq.nextval, #bookNo#, #userId#)
</insert>
장바구니에 담을 수 있는 권수가 늘어난다.
delete.jsp
<%
User user = (User) session.getAttribute("loginedUser");
if (user == null) {
response.sendRedirect("")
}
%>
이 부분을 워낙 많이 쓰기 때문에 하나의 jsp에 담아 사용하기 편리하게 한다.
common/logincheck.jsp
<%
User loginUser = (User) session.getAttribute("loginedUser");
if (loginUser == null) {
response.sendRedirect("/web-board/user/loginform.jsp?error=deny");
return;
}
%>
addItem.jsp / deleteItem.jsp / list.jsp에 위의 delete.jsp와 같은 부분을 지우고 추가
<%@ include file="../common/logincheck.jsp" %>
// 사용자 아이디 조회(loginUser는 logincheck.jsp에서 정의한 변수다.)
String userId = loginUser.getId(); //user를 loginUser로 변경해 주었다.
// CartItemDao 객체를 생성하고, getCartItemsByUserId(String userId) 메소드를 실행해서 장바구니 아이템 목록을 조회한다.
CartItemDao cartItemDao = new CartItemDao();
List<CartItemDto> dtoList = cartItemDao.getCartItemsByUserId(loginUser.getId()); // 이것 역시 loginUser로 변경해 주었다.
초록색 방식 = include한 곳에서 정상동작
빨간색 방식 = 오류 발생
Static include (정적 include)
home.jsp에 header.jsp가 그대로 포함되어 하나의 자바파일(jsp파일)로 된다.
그래서 밑에서 System.out.println(user)가 사용 가능하다.(변수 공유 가능)
변수를 공유하고 싶을 때 사용
그러나 변수명 중복 선언에 주의해야 한다! (header.jsp와 home.jsp에서 같은 이름의 변수를 사용하면 안된다!)
Dynamic include (동적 include)
head.jsp.java / home.jsp.java 파일이 각각 하나씩 생긴다.
home.jsp.java에 잠깐 들렸다가 다시 head.jsp.java로 돌아간다.
(home.jsp.java 코드에서 header.jsp를 include하는 메소드가 실행된다.)
그래서 밑에서 System.out.println(user)가 사용 불가능하다. (변수 공유 불가)
변수를 공유하고 싶지 않을 때 사용
로그인했는지 확인만 가능하고 이후에 해당 사용자정보를 이용해서 다른 로직은 처리할 수 없다.
변수이름 충돌을 신경쓸 필요가 없다.
header.jsp에 정의된 변수를 home.jsp에서 사용할 수 없다.
logincheck.jsp는 코드가 그대로 들어와 있다.
header.jsp는 코드가 포함되어있지 않다.
특정 장바구니 아이템 삭제하기와 장바구니 아이템 전부 삭제하기
deleteItem.jsp
<%
// 요청객체에서 요청파라미터 정보 조회
int itemNo = StringUtils.stringToInt(request.getParameter("itemNo"));
// itemNo에 해당하는 장바구니 아이템정보를 삭제한다.
CartItemDao cartItemDao = new CartItemDao();
cartItemDao.deleteCartItemByNo(itemNo);
// 재요청 URL을 응답으로 보낸다.
response.sendRedirect("list.jsp");
%>
carts.xml
// 특정 장바구니 아이템 삭제하기
delete from
sample_board_book_cart_items
where
item_no = #value#
<delete id="deleteCartItemsByUserId" parameterClass="string"> // 장바구니 아이템 전부 삭제하기
delete from
sample_board_book_cart_items
where
user_id = #value#
</delete>
CartItemDao.java
public void deleteCartItemByNo(int itemNo) {
SqlMapper.delete("carts.deleteCartItemByNo", itemNo);
}
public void deleteCartItemsByUserId(String userId) {
SqlMapper.delete("carts.deleteCartItemsByUserId", userId);
}
특정 장바구니 아이템 삭제 전
특정 장바구니 아이템 삭제 후
전체삭제
cart/list.jsp
<div class="">
<a href="clear.jsp" class="btn btn-secondary btn-sm" >전체 삭제</a>
<a href="" class="btn btn-secondary btn-sm" >전체 구매</a>
<a href="../book/list.jsp" class="btn btn-outline-primary btn-sm float-end">쇼핑계속</a>
</div>
cart/clear.jsp
<%@ include file="../common/logincheck.jsp"%>
<%
// 로그인한 사용자의 아이디 조회
String userId = loginUser.getId();
CartItemDao cartItemDao = new CartItemDao();
// 로그인한 사용자의 모든 장바구니 아이템을 삭제한다.
cartItemDao.deleteCartItemsByUserId(userId);
// 재요청 URL을 응답으로 보낸다.
response.sendRedirect("list.jsp");
%>
특정 장바구니 아이템을 삭제하기 위해서는 item_no가 필요하고
장바구니 아이템을 전부 삭제하기 위해서는 user_id가 필요하다.
그래서 session객체에서 그 정보들을 가지고 온다.
전체 삭제 전
전체 삭제 후
요청 파라미터에서 값을 전달하는 방법
1. <a href="sample.jsp?no=100&page=3">링크</a> --> &과 no로 보내는 방법 (값이 미리 정해져 있어야 한다.)
GET sample.jsp?no=100&page=3 HTTP/1.1
Accept:text/html,application/xml
Accept-Encoding: ...
Accpet-Language ...
----------------------------------------------------------------------
2. <form method="get" action="sample.jsp"> --> get 방식으로 보내는 방법
<input type="number" name="minPrice" />
<input type="number" name="maxPrice" />
<input type="text" name="keyword" />
<button type="submit">제출 </button>
</form>
GET sample.jsp?minPrice=10000&maxPrice=50000&keyword=미니카HTTP/1.1
Accept:text/html,application/xml
Accept-Encoding: ...
Accpet-Language ...
----------------------------------------------------------------------
3. <form method="post" action="sample.jsp"> --> post 방식으로 보내는 방법
<input type="number" name="minPrice" />
<input type="number" name="maxPrice" />
<input type="text" name="keyword" />
<button type="submit">제출 </button>
</form>
POST sample.jsp HTTP/1.1
Accept:text/html,application/xml
Accept-Encoding: ...
Accpet-Language ...
userId=hong&userPwd=zxcv1234
선택삭제는 사용자가 무엇을 선택할지 모르므로 번호를 미리 넣어놓을 수 없다.
그래서 1번과 같은 방법은 불가능하다.
그렇기 때문에 form 태그를 사용해야 한다!
추가 / 업데이트 시에는 POST 방식
삭제 시에는 GET 방식을 사용한다.
form 태그로 장바구니 리스트 밑 모든 것이 포함되어야 한다.
--
값이 정해져 있다면 1번 방법을 사용. (무언가를 눌렀을 때 딱 이 값이 삭제되거나 이 값이 보여져야 할 때)
값이 안 정해져 있다면 2번 혹은 3번 방법을 사용. (form을 만들어서 그 것을 다 포함해서 그 중에 선택된 것을 삭제해야 할 때)
cart/list.jsp (table시작부터 div 닫는 곳 까지 하나의 form으로 감싸준다.)
<p>장바구니 목록을 확인하세요.</p>
<form method="get" action="deleteItems.jsp">
<table class="table">
...
...
...
<div class="">
<a href="clear.jsp" class="btn btn-secondary btn-sm" >전체 삭제</a>
<button type="submit" class="btn-secondary btn-sm">선택 삭제</button>
<a href="../book/list.jsp" class="btn btn-outline-primary btn-sm float-end">쇼핑계속</a>
</div>
</form>
위의 체크박스에는 name과 value를 주지 않았다.
그러나 값이 넘어가기 위해서는 name과 value가 있어야 한다.
for문이 돌면서 체크박스가 생기게 하고, 아이템 번호가 생기게 하였다.
체크박스는 같은 것 끼리는 이름이 같아야 한다. (값만 다르다.)
name과 value가 deleteItems.jsp로 제출된다.
<td><input type="checkbox" name="itemNo" value="<%=dto.getItemNo() %>" /></td>
cart/deleteItems.jsp
<%@ include file="../common/logincheck.jsp" %>
<%
// 입력폼에서 체크한 체크박스의 값을 요청객체에서 조회하기
String[] values = request.getParameterValues("itemNo");
// 체크된 아이템이 없으면 values는 null이다. values가 null이면 장바구니리스트를 재요청하는 URL을 응답으로 보낸다.
if (values == null) {
response.sendRedirect("list.jsp");
return;
}
// CartItemDao 객체를 생성하고, 전달받은 장바구니 아이템을 삭제하는 수행문을 반복실행한다.
CartItemDao cartItemDao = new CartItemDao();
for (String value : values) {
int itemNo = StringUtils.stringToInt(value);
cartItemDao.deleteCartItemByNo(itemNo);
}
// 재요청 URL을 응답으로 보낸다.
response.sendRedirect("list.jsp");
%>
선택 삭제 전
선택 삭제 후
delteItem과 deleteItems는 같기 때문에 deleteItem을 없애준다.
(하나를 지우더라도 어차피 삭제하는 것은 같기 때문에 delteItems로 해놓으면 하나를 지우던 두개를 지우던 세개를 지우던 다 가능하다.)
cart/list.jsp
<a href="deleteItem.jsp?itemNo=<%=dto.getItemNo() %>" class="btn btn-secondary btn-sm">삭제</a>을
<a href="deleteItems.jsp?itemNo=<%=dto.getItemNo() %>" class="btn btn-secondary btn-sm">삭제</a>로 변경
apach/commons/codec/download/1.15.bin 다운 & WEB-INF에 CODEC-1.15.jar 복사 붙여넣기
여러 프로젝트에서 사용되는 공통기능(암/복호화, 인코딩 등)들을 제공해준다.
컴퍼넌트 : 특별한 목적에 맞는 기능들을 제공해준다.
비밀번호 암호화
user/register.jsp
// 비밀번호 암호화하기
String secretPassword = DigestUtils.sha256Hex(password);
// User 객체를 생성해서 조회된 값을 저장한다.
User user = new User();
user.setId(id);
user.setPassword(secretPassword);
user.setName(name);
user.setEmail(email);
SQL DEVELOPER에서
SAMPLE_BOARD_USERS의 USER_PASSWORD를 CHAR 타입으로 / 64자로 만들어준다.
그리고 회원가입을 하면,
위와 같이 비밀번호가 암호화가 된다.
그런데, 여기서 문제는 기존 회원가입 했던 아이디들을 로그인하려면 ZXCV1234 포함 64자 띄어쓰기가 있으므로 그것을 해결하고자
sql devleloper
UPDATE SAMPLE_BOARD_USERS
SET
USER_PASSWORD = (SELECT USER_PASSWORD
FROM SAMPLE_BOARD_USERS
WHERE USER_ID = 'jung');
COMMIT;
를 해주면
위와 같이 모든 아이디의 비밀번호가 변경된다.
그런데 여기서 또 비밀번호를 알아낼 수 있으므로 "salt" 소금을 쳐주는 방식(id와 email이 password에 합쳐지게 한다.)
user/register.jsp
// 비밀번호 암호화하기
String salt = id + email;
String secretPassword = DigestUtils.sha256Hex(salt+password);
ryu와 ahn도 같은 비밀번호인 zxcv1234이지만, salt로 인해 비밀번호가 달라진다.
salt를 어떤 방식으로 지정하는지 알 수 없으므로 이러면 비밀번호를 알아내기가 힘들어진다.
그러나 아직 로그인이 되지 않으므로 login.jsp에서 salt비밀번호와 비교하기 위해
현재 로그인하는 비밀번호에 salt를 똑같이 해주면 그 둘을 비교하게 되고 그 둘이 같다면 로그인이 되게 한다.
user/login.jsp
String salt = savedUser.getId() + savedUser.getEmail();
String secretPassword = DigestUtils.sha256Hex(salt+password);
// 조회된 사용자정보의 비밀번호와 입력한 비밀번호가 일치하지 않으면 로그인화면을 재요청하는 URL을 응답으로 보낸다.
if (!savedUser.getPassword().equals(secretPassword)) {
response.sendRedirect("loginform.jsp?error=fail");
return;
비밀번호가 같아도 각 아이디마다 다른 salt비밀번호를 가지게 되고 로그인이 된다.
비대칭키 암호화 알고리즘 : 공인인증서
비대칭키 암호화 알고리즘 = 공개키(은행이 가지고 있는 것) + 개인키(내가 가지고 있는 것)
공개키는 맞는 개인키에만 풀리고 / 개인키는 맞는 공개키로만 풀린다.
DigestUtil
- 단방향 암호화알고리즘을 사용해서 평문을 암호문으로 변환한다.
- 인코딩만 가능 / 디코딩은 불가능
Base64
- 64개의 문자로 데이터를 표현한 것
- 특수문자는 포함되지 않는다.
- url 뒤에 붙여서 표현하기 안전하지 않은 문자들이 있다.
jakarta.servlet.GenericServlet <Abstract Class> // 추상 클래스
- Servlet 인터페이스를 구현하는 추상 클래스다.
- Servlet 인터페이스의 주요 메소드들을 대부분 구현하고 있다.
- Servlet 인터페이스에 정의되어 있지 않은 추가 메소드를 제공한다.
- 주요 메소드
void int() {...} // 구현 메소드
String getinitParameter(String name) {...}
- 서블릿 초기화 파라미터값을 반환한다.
ServletConfig getServletConfig() {...}
- ServletConfig 객체를 반환한다.
ServletContext getServiceContext() {...}
- ServletContext 객체를 반환한다.
대부분 HTTP라는 프로토콜을 통해서 클라이언트와 서버에서 통신(요청/응답)을 한다.
그래서 서블릿은 HTTP와 연동이 되도록 만든다.
request는 요청하는 것 / response는 응답해줄 것이다.
servlet을 만드는 것은 service라는 메소드를 재정의하는 것이다.
jakarta.Servlet.http.HttpServlet<Abstract Class>는HTTP 서블릿은 Generic클래스를 기반으로 만들어졌고 HTTP와 연동이 가능하다.
jakarta.Servlet.http.HttpServlet<Abstract Class>
- HTTP 프로토콜에 특화된 서블릿을 개발할 때 상속받는 클래스다.
- 주요 메소드 (dnlsms Servlet 메소드였지만 아래는 모두 HttpServlet 메소드다.)
void doGet(HttpServletRequest request.HttpServletResponse response) {...}
- GET 방식의 요청은 이 메소드를 재정의해서 처리한다.
void doPut(HttpServletRequest request.HttpServletResponse response) {...}
- PUT 방식의 요청은 이 메소드를 재정의해서 처리한다.
void doPost(HttpServletRequest request.HttpServletResponse response) {...}
- POST 방식의 요청은 이 메소드를 재정의해서 처리한다.
void doDelete(HttpServletRequest request.HttpServletResponse response) {...}
- DELETE 방식의 요청은 이 메소드를 재정의해서 처리한다.
void service(HttpServletRequest request.HttpServletResponse response) {...}
- 클라이언트의 요청을 분석해서 요청방식에 맞는 doXXX 메소드를 실행한다.
* 요청방식에 상관없이 항상 실행되는 메소드다.
* service() 메소드의 본래 기능을 버리고, 클라이언트의 요청을 처리하는 코드로 재정의한다.
public class HelloServlet extends HttpServlet {
//HttpServlet클래스의 service(request, response) {
// 클라이언트의 요청을 처리하고, 응답을 제공하는 코드
}
}
/com.sample.servlet/HelloServlet.java // 클래스 생성 후 extends 해주고 s + ctrl + shif 해서 service를 실행해 Http를 재정의 해준 뒤 응답을 보내준다.
package com.sample.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<!doctype html>");
out.println("<html lang='ko'>");
out.println("<meta charset='utf-8'>");
out.println("<title>헬로 서블릿</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>헬로 서블릿</h1>");
out.println("<p>나는 서블릿입니다.</p>");
out.println("</body>");
out.println("</html>");
}
}
- JSP 발명 전에는 이렇게 적었어야 했다.
- JSP의
<%@ page language="java" contentType="text/html; charset=UTF-8"이
서블릿의 response.setContentType("text/html; charset=utf-8");이 된다.
- PrintWriter는 브라우저와 연결되어 있는 출력장치(객체)다.
서블릿은 동적인 컨텐츠를 위해서는 사용하지 않는다. (서블릿으로 HTTP응답해주는 것을 안만드는 이유는 너무 힘들기 때문이다.)
JSP는 URL에 JSP경로를 적어 실행하였다.
그러나 서블릿은 웹 자원이 아닌 자바의 자원이라 위와 같은 방법으로는 실행하지 못한다. 그래서 서블릿은 요청 URL과 매핑시켜줘야 한다. (그게 @WebServlet("/hello")다.)
JSP는 내가 이 곳에 들어갈 코드만 적으면 됐지만, 서블릿은 내가 다 해줘야 한다.
아래와 같이 하나의 데이터만 가지는 경우도 있고 여러 데이터를 가지는 경우도 있다.
이 중 여러 데이터를 가질 때 처리하는 방법은
<form>
이름
<input type="type" name="name" />
전화번호
<input type="type" name="tel" />
이메일
<input type="type" name="email" />
보유기술
<input type="checkbox" name="skill" value="java">자바
<input type="checkbox" name="skill" value="sql">SQL
<input type="checkbox" name="skill" value="c">c
<input type="checkbox" name="skill" value="c#">c#
<input type="checkbox" name="skill" value="python">파이썬
<input type="checkbox" name="skill" value="bigdata">빅데이터
경력증명서
<input type="file" name="attachedFile" />
<input type="file" name="attachedFile" />
<input type="file" name="attachedFile" />
<input type="file" name="attachedFile" />
<input type="file" name="attachedFile" />
수행프로젝트
<input type"text" name="project" />
<input type"text" name="project" />
<input type"text" name="project" />
<input type"text" name="project" />
<button type="submit">제출</button>
</form>