본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day76

by 루팽 2023. 3. 17.

쿠키 생성하기

  첫 번째 파라미터: 이름(Map의 key - 식별, 유일무이)

  두 번째 파라미터: 값(문자열만 가능함)

  생성했다고 해서 실제 로컬 PC에 내려가지 않음

Cookie c = new Cookie(”cmem_id”, “tomato”);

// 쿠키 타임 설정이 가능함(ex. 장바구니 보관은 n일동안 유지)
c.setMaxAge(60); // 단위는 초단위이다

// 쿠키의 적용 범위
c.setPath(); // 생략 가능

 

  요청

    http://localhost:9000/member/cindex.jsp

    upmu[0] = member

    upmu[1] = cindex.jsp

 

  응답

    void → doGet(req, res), doPost(req, res)

    String → “redirect:upmu[0] 업무명/upmu[1] 메소드이름 or 페이지이름

    ModelAndView

    pageMove[0] = member (upmu[0]과 같음)

    pageMove[1] = memberList or memberList.jsp or XXX.st3(또다른 URL)

 

글쓰기를 할 때 예시

  action(select) → jsp → 글쓰기버튼 → jsp(폼) → 저장 → action(insert) → 성공1, 실패0 → 성공하면 action(select) →(forward사용)→ jsp

 

글쓰기

  jsp(폼)

  → action(insert)

  → action(select: memberList.st3)

  →(forward: pojo는 ModelAndView[스프링], setAttribute, 스프링부트는 Model, ModelMap, ViewResolver)

  → jsp

 

글수정 - 글 상세 보기에서 시작

  action(select 1건)

  → 수정버튼

  → jsp(입력화면)

  → action(update)

  → action(select)

  → jsp

 

글삭제 - 글 상세 보기에서 시작

  삭제버튼

  → jsp(비밀번호 비교-오라클에서 가져온 비번과 상세보기 화면에서 가져온 비번 비교)

  → action(delete from 테이블명 where board_no=:x)

  → action(select)

  → jsp(새로고침)

  → window.location.href=”a.jsp”; (새로고침됨)

  → jsp는 SPA가 아니지만 React는 SPA

  → useState()덕분에 return으로 화면출력(State가 바뀔 때마다 return이 다시 호출, 화면이 그려짐, 리렌더링)

 

리액트의 핵심 컨셉

  페이지를 여러 개의 컴포넌트로 쪼갠다

  HackerNewsPage.jsx → HackerNewsList.jsx(select n건) → map을 사용해 for문 돌림 → HackerNewsRow.jsx(select 1건) → 출력 return(<><h3></h3></>);

 

  useState가 바뀌면 리액트는 비교알고리즘을 통해서 dataset을 비교하고 바뀐 값이 있으면 return을 다시 호출한다

  state가 변하면 화면도 리렌더링 된다

    useState(0)

    useState({}) - 객체(1건) select * from member where mem_id=tomato → 자바에선 MemberVo, Map<String, Object>

    useState([]) - 객체배열[{}, {}, {}]

 

글조회 - 글목록에서 시작

  키워드 입력 → 조회버튼 → action → jsp

 

SELECT
        mem_id, mem_name
    FROM BOOK_MEMBER
    WHERE mem_id =:x
    AND mem_pw =:y;
    
--파라미터의 개수 파악(2개)
--아이디, 비번

request.getParameter(); 반복돼서 불편

→ HashMapBinder 공통코드 작성함

→ (@RequestParam Map<String, Object>) 사용자가 입력한 값을 스프링 컨테이너가 자동으로 Map에 담아주는 어노테이션(해당 코드를 파라미터에 넣어줌)

 

프로젝트 sitemap

  루트페이지 / → root를 들어올 때 인증절차 필요(백엔드에서 처리해 줌)

  → AuthController(쿠키-그냥 제공됨, 세션-req필요)

 

리액트기준

  index.html → <div>만 가지고 있음

  index.js

<BrowserRouter>
	<App /> // 실제 시작이 되는 페이지
<BrowserRouter />

 

  그다음 App.jsx 연결됨

<Routes>
	<Route path="URL패턴" exact="true" />
</Routes>

 

  @GetMapping(), @PostMapping() → 파라미터 안에 들어오는 것이 path와 같은 것

  exact에 true가 있으면 URL이 정확히 일치해야 함

  Route를 통해 페이지 이동(jsx)

  → return가지고있고, 그 return으로 화면 출력됨

  → 화면전환을 위해서는 react-router-dom이 제공하는 <Link>을 쓰든지 UseNavigate를 사용해야 함

  → location.href 같은 거 쓰면 안 됨!! SPA를 위해 navigate 사용해야 한다!(useNavigate는 부분적 화면전환용!)

 

로그인에 필요한 페이지 2개 - true, false일 경우

  쿠키값 == null이면 로그인버튼(인증 전)

  쿠키값 != null이면 로그아웃버튼(값을 가지고 있음, 인증 후)

 

절대경로- 처음부터 다 적음

상대경로- 현재 내가 바라보는 경로에서부터 적음

  ./ → 현재 내가 작성하는 페이지 의미

  ../ → 현재 내가 있는 폴더에서 상위폴더를 의미

 

쿠키삭제하기(로그아웃 담당)

  로그인

    window.location.href=”./login.st3” → 상대경로

    window.location.href=”/member/login.st3” → 절대경로

 

  로그아웃

    window.location.href=”./logout.st3”

    st3 앞에 있는 이름은 메소드명 → logout은 MemberController.java에 선언된 메소드 이름이다

    → ./logout.st3으로 요청이 오면 ActionSupport가 인터셉트함(web.xml에 *.st3으로 설정)

    → upmu[0] = member, upmu[1] = logout

    → MemberController.java - Controller3.java(인터페이스) 추상클래스, 인터페이스 중심의 코드 전개를 위해 설계

    →HandlerMapping.java

 

<쿠키 로그아웃 - HandlerMapping.java>

package com.pojo.step3;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;

public class HandlerMapping {
	static Logger logger = Logger.getLogger(HandlerMapping.class);
	
	/***************************************
	 * @param upmu[](upmu[0]=업무명,폴더명 | upmu[1]=메소드명, 기능명, 페이지이름)
	 * @param request -> 1-1, 1-2와는 다르게 인터페이스를 implements하지 않는다
	 * @param response -> HttpServlet
	 * Q. 어디서 받아오는가?
	 * @return Object
	 * 테스트 -> <http://localhost:9000/board3/boardList.st3>
	 ***************************************/
	
	public static Object getController(String[] upmu, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info(upmu[0]+", "+upmu[1]);
		Controller3 controller = null;
		String path = null;
		Object obj = null;
		ModelAndView mav = null;
		
		// common
		if("common".equals(upmu[0])) {
			controller = new CommonController();
			// 우편번호 조회
			if("zipcodeList".equals(upmu[1])) {
				obj = controller.zipcodeList(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			} // end if zipcodeList if문
		}
		
		// 게시판 구현
		else if("board3".equals(upmu[0])) {
			controller = new Board3Controller();
			
			// 게시글 전체 목록 -> html 화면 출력(text/html)
			if("boardList".equals(upmu[1])) {
				obj = controller.boardList(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// json 게시글 전체 목록 -> json 화면 출력(application/json)
			else if("jsonBoardList".equals(upmu[1])) {
				obj = controller.jsonBoardList(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 입력 - 새글쓰기와 댓글쓰기
			else if("boardInsert".equals(upmu[1])) {
				obj = controller.boardInsert(req, res);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 이미지 업로드 - 리액트 quill editor 이미지 추가
			else if("imageUpload".equals(upmu[1])) {
				obj = controller.imageUpload(req, res);
				logger.info("imageUpload 호출 => " + obj instanceof String);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 이미지 다운로드 - 리액트 quill editor 이미지 추가
			else if("imageDownload".equals(upmu[1])) {
				obj = controller.imageDownload(req, res);
				logger.info("imageDownload 호출 => " + obj instanceof String);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 리액트 -
			else if("imageGet".equals(upmu[1])) {
				obj = controller.imageGet(req, res);
				logger.info("imageGet 호출 => " + obj instanceof String);
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 수정 - 첨부파일 수정 유무 고려하기
			else if("boardUpdate".equals(upmu[1])) {
				obj = controller.boardUpdate(req, res);
			// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 삭제 - 첨부파일 삭제 유무 고려하기
			else if("boardDelete".equals(upmu[1])) {
				obj = controller.boardDelete(req, res);
			// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			}
			
			// 글 상세보기
			else if("boardDetail".equals(upmu[1])) {
				obj = controller.boardDetail(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				else if(obj instanceof String) {
					return (String)obj;
				}
			}
		} // end of 게시판 구현
		
		// 인증 관리 구현
		else if("auth".equals(upmu[0])) {
		}
		
		// 회원 관리 구현
		else if("member".equals(upmu[0])) {
			controller = new MemberController();
			// 로그인
			if("login".equals(upmu[1])) {
				logger.info("login 호출");
				obj = controller.login(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			} // end of login
			// 로그아웃
			else if("logout".equals(upmu[1])) {
				logger.info("logout 호출");
				obj = controller.logout(req, res);
				// 리턴타입이 ModelAndView인 경우
				if(obj instanceof ModelAndView) {
					return (ModelAndView)obj;
				}
				// 리턴타입이 String인 경우
				if(obj instanceof String) {
					return (String)obj;
				}
			} // end of logout
		} // end of member
		
		// 주문 관리 구현
		else if("order".equals(upmu[0])) {
		}
	
		return obj;
	}
}

 

<쿠키 로그아웃 - Controller3.java>

package com.pojo.step3;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// javascript기반의 UI API
// 리액트는 바닐라스크립트 + ES6(주요이슈-spread, module, arrow),ES7 + html 섞어쓰기
public interface Controller3 {
	public Object login(HttpServletRequest req, HttpServletResponse res);
	public Object logout(HttpServletRequest req, HttpServletResponse res);
	public Object zipcodeList(HttpServletRequest req, HttpServletResponse res);
	public Object jsonBoardList(HttpServletRequest req, HttpServletResponse res);
	public Object boardList(HttpServletRequest req, HttpServletResponse res);
	public Object boardDetail(HttpServletRequest req, HttpServletResponse res);
	public Object imageUpload(HttpServletRequest req, HttpServletResponse res);
	public Object imageDownload(HttpServletRequest req, HttpServletResponse res);
	public Object imageGet(HttpServletRequest req, HttpServletResponse res);
	public Object boardInsert(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
	public Object boardUpdate(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
	public Object boardDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}

 

<쿠키 로그아웃 - MemberController.java>

package com.pojo.step3;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.util.HashMapBinder;

public class MemberController implements Controller3 {
	Logger logger = Logger.getLogger(MemberController.class);
	MemberLogic memberLogic = new MemberLogic();

	@Override
	public Object login(HttpServletRequest req, HttpServletResponse res) {
		logger.info("login 호출");
		Map<String, Object> rMap = new HashMap<>();
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		rMap = memberLogic.login(pMap);
		logger.info(rMap);
		Cookie cmem_id = new Cookie("cmem_id", rMap.get("MEM_ID").toString());
		cmem_id.setPath("/");
		cmem_id.setMaxAge(60 * 60);
		res.addCookie(cmem_id);
		Cookie cmem_name = new Cookie("cmem_name", rMap.get("MEM_NAME").toString());
		cmem_name.setPath("/");
		cmem_name.setMaxAge(60 * 60);
		res.addCookie(cmem_name);
		return "redirect:./cindex.jsp"; // => member.cindex.jsp
	}

	@Override
	public Object logout(HttpServletRequest req, HttpServletResponse res) {
		logger.info("logout 호출");
		/*
		 * 쿠키는 삭제하는 메소드가 따로 없다
		 * 생성자에 두번째 파라미터에 빈 문자열로 처리하고
		 * 시간을 0으로 초기화해줘야함
		 * 도메인도 동일하게 맞춰야 삭제가 됨(주의)
		 */
		Cookie cmem_id = new Cookie("cmem_id", "");
		cmem_id.setPath("/");
		cmem_id.setMaxAge(0);
		res.addCookie(cmem_id);
		Cookie cmem_name = new Cookie("cmem_name", "");
		cmem_name.setPath("/");
		cmem_name.setMaxAge(0);
		res.addCookie(cmem_name);
		// navigate = useNavigate("./cindex.jsp") -> 리액트 화면전환용(부분적)
		return "redirect:./cindex.jsp";
	}

	@Override
	public Object zipcodeList(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object jsonBoardList(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardList(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardDetail(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageUpload(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageDownload(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageGet(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardInsert(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}

	@Override
	public Object boardUpdate(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}

	@Override
	public Object boardDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}
}

 

<쿠키 로그아웃 - cindex.jsp>

<%@page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
	String cmem_id = null;
	String cmem_name = null;
	// 서버측에서 클라이언트(사용자) 쿠키값 보내달라는 요청
	Cookie cs[] = request.getCookies();
	int size = 0;
	// NullPointerException 방어코드
	if(cs!=null){
		size = cs.length;		
	}
	for(int i=0;i<size;i++){
		// 쿠키이름을 가져온다 - 셍성자의 첫번째 파라미터 자리값
		String c_name = cs[i].getName();
		// 서버측에서 클라이언트로부터 넘겨받은 문자열을 비교함
		if("cmem_id".equals(c_name)){
			// 쿠키이름이 cmem_id인 쿠키의 값을 담기
			cmem_id = cs[i].getValue();
		}
		// 한 번더 쿠키값을 꺼내온다 -> 사용자의 이름을 불러줌
		if("cmem_name".equals(c_name)){
			cmem_name = cs[i].getValue();
		}
	}
	out.print("쿠키에서 꺼내 온 값 ===> " + cmem_id + "   ,   " + cmem_name);
	if(cmem_id == null){
%>
<script>
	alert("로그인을 먼저 해주세요")
</script>
<%
	}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Web application[쿠키인증실습 webapp]</title>
<%@ include file="../common/easyUI_common.jsp"%>
<style type="text/css">
a {
	text-decoration: none;
}
</style>
<script>
	// 로그인 버튼
	const login =() =>{
		/*
		테스트: com.mvc.dao.MemberDao.java
		xml: com.mybatis.mapper에 member.xml
		SELECT mem_name FROM book_member
		WHERE mem_id =:id
		AND mem_pw =:pw
		*/
		// 사용자가 입력한 아이디 가져오기
		const user_id = $("#_easyui_textbox_input1").val();
		// 사용자가 입력한 비밀번호 가져오기
		const user_pw = $("#_easyui_textbox_input2").val();
		console.log(user_id + user_pw);
		window.location.href = "./login.st3?mem_id=" + user_id + "&mem_pw=" + user_pw;
	}
	
	// 로그아웃 버튼
	const logout = () => {
		// 아래 코드에 ./가 없으면 pageMove배열 매칭이 안되니 주의
		window.location.href = "./logout.st3";
	}	
</script>
</head>
<body>
	<h2>웹 어플리케이션 실습</h2>
	<div style="margin: 20px 0;"></div>
	<div class="easyui-layout" style="width: 1000px; height: 500px;">
		<!-- =============== 메뉴 구성 [로그인화면과 트리메뉴] 시작 =============== -->
		<div id="p" data-options="region:'west'" title="KH정보교육원"
			style="width: 200px; padding: 5px">
<%
	// 쿠키값이 null인 경우 - 로그인을 아직 안함
	if(cmem_id == null){
%>
			<!-- =============== [[ 로그인 화면 ]] =============== -->
			<div id="d_login" align="center">
				<div style="margin: 3px 0"></div>
				<!-- create table member mem_id varchar2(10) DB에서 이런형식 -->
				<input id="tb_id" type="text" style="width: 170px">
				<script>
					$('#tb_id').textbox({
						iconCls : 'icon-man',
						iconAlign : 'right',
						prompt : '아이디'
					})
				</script>
				<div style="margin: 3px 0"></div>
				<input id="pb_pw" type="text" style="width: 170px">
				<script>
						$('#pb_pw').textbox({
							iconCls: 'icon-lock',
							iconAlign: 'right',
							prompt : '비밀번호'
						});
				</script>
				<div style="margin: 3px 0"></div>
				<a id="btn_login" href="javascript:login()">로그인</a>
				<script>
					$('#btn_login').linkbutton({
						iconCls : 'icon-man'
					});
				</script>
				<a id="btn_member" href="javascript:memberShip()">회원가입</a>
				<script>
					$('#btn_member').linkbutton({
						iconCls : 'icon-add'
					});
				</script>
			</div>
			<!-- =============== [[ 로그인 화면 ]] =============== -->
<%}
	else { // 로그인을 한 상태일 경우 %>
			<!-- =============== [[ 로그아웃 화면 ]] =============== -->
			<div id="d_logout" align="center">
			<div style="margin: 3px 0"></div>
			<span><%=cmem_name %>님 환영합니다!</span>
				<div style="margin: 3px 0"></div> <!-- 외부여백을 위, 아래에 3px -->
					<a id="btn_logout" href="javascript:logout()">로그아웃</a>
					<script>
						$('#btn_logout').linkbutton({
							iconCls : 'icon-remove'
						});
					</script>
					<a id="btn_member" href="javascript:memberShip()">정보수정</a>
					<script>
						$('#btn_member').linkbutton({
							iconCls : 'icon-edit'
						});
					</script>
			</div>
			<!-- =============== [[ 로그아웃 화면 ]] =============== -->
<%} %>
			<!-- =============== 메뉴 구성 [로그인화면과 트리메뉴] 끝 =============== -->
			<div style="margin: 3px 0"></div>
			<ul id="tre_gym" class="easyui-tree">
				<li data-options="state:closed"><span>회원관리</span>
					<ul>
						<li><a href="#">회원목록</a></li>
						<li><a href="#">회원등록</a></li>
						<li><a href="#">회원삭제</a></li>
					</ul></li>
				<li data-options="state:'closed'"><span>쪽지관리</span>
					<ul>
						<li><a href="#">받은쪽지함</a></li>
						<li><a href="#">보낸쪽지함</a></li>
					</ul></li>
				<li data-options="state:'closed'"><span>기타</span>
					<ul>
						<li><a href="#">우편번호검색기</a></li>
						<li><a href="#">게시판</a></li>
					</ul></li>
			</ul>
		</div>
		<!-- 메인화면 [게시판, 온라인시험, 쪽지관리(받은쪽지함, 보낸쪽지함), 회원관리(회원목록, 회원등록, 회원삭제), 우편번호검색기] 시작 -->
		<div data-options="region:'center', iconCls:'icon-ok'"
			title="TerrGYM2023">
			<div style="margin: 5px 0;"></div>
			Home > 회원관리 > 회원목록
			<hr>
			<div style="margin: 25px 0;"></div>
		</div>
		<!-- 메인화면 [게시판, 온라인시험, 회원관리, 우편번호검색기] 끝 -->
	</div>
</body>
</html>
<!-- 
	부트스트랩 - 리액트수업 -> Spring과 리액트 연계 수업 -> 프로젝트적용
		반응형 지원, CSS라이브러리 사용
		CSS - JS거의없음
	
	jEasyUI
		이벤트처리(jquery - 레거시시스템)
		자바스크립트 - 표준아님 -> jquery기반이라서
		자바스크립트 기반의 UI솔루션 사용하기 - 큰 도움
		개발자도구 활용 - 디버깅 -> 왜냐하면 html을 래핑하기때문에
		vue.js, reactjs
		jEasyUI는 html으로 화면처리, html+js 섞어쓰기, js(최소한의 태그선언 필요)로 화면처리 가능
		
	JSTL - 1.1 -> 1.2 -> 소개(사용x)
	
	로그인 테스트 흐름도
		1. intro 아래 index.jsp 실행
		2. 아이디와 비밀번호를 입력받는다
		3. 로그인 버튼을 누른다 -> 자바스크립트의 login() 호출
		4. login.do 호출한다 -> 로그인 처리를 하는 서블릿 호출 - doGet(), doPost()
		5. com.mvc.dao.MemberDao 클래스의 인스턴스화
		6. 메소드 호출 - 로그인처리
		7. MemberDao의 login(Map[member_id(사용자가 입력한 아이디)와 mem_pw(사용자가 입력한 비번)가 있음]) 호출
		8. 리턴타입으로 Map을 받아온다(mem_id, mem_name)
		9. 8번에서 돌려받은 Map에서, 오라클 서버에서 조회된 아이디와 이름을 세션에 담기
		10. 페이지 이동은 sendRedirect나 forward 둘 다 모두 가능하다
				단, forward로 응답을 처리한 경우, 인증 후에 다른 서비스를 forward로 처리하는것이 불가하다(주의!)
 -->

 

커넥션 풀(Connection Pool)

데이터베이스와 연결된 커넥션을 미리 만들어놓고 이를 pool로 관리하는 것

필요할 때마다 커넥션 풀의 커넥션을 이용하고 반환하는 기법

미리 만들어놓은 커넥션을 이용하면 비용 줄이고 빠르게 접속 가능

커넥션 수를 제한할 수 있어서 과도한 접속으로 인한 서버 자원 고갈 방지 가능

DB접속모듈을 공통화(분리)해 DB 서버의 환경이 바뀔 경우 유지보수를 쉽게 할 수 있음

 

HikariCP

가벼운 용량과 빠른 속도를 가지는 JDBC의 커넥션 풀 프레임워크

SpringBoot는 커넥션 풀 관리를 위해 HikariCP를 사용함

 

https://start.spring.io/ → Dependencies 추가하고 EXPLORE 누른 다음 pom.xml의 dependency 복사해서 프로젝트의 pom.xml에 붙여 넣기(maven 빌드의 경우)

추가된 것 확인 경로 C:\Users\user1\.m2\repository\org\springframework

한글처리 → WEB-INF-lib에 orai18n-19.3.0.0.jar 넣어주기

 

클라이언트가 요청(스프링부트에서는 XXX.st3같이 붙이지 않을 예정)

  → DispatcherServlet(서블릿을 경유 - 간섭, 관여, interceptor)

  → 여러 업무(xxxController) @Controller(view로 이어짐-React) 혹은 @RestController(String → JSON) 사용

  → 업무별 로직(xxxLogic) 공통된 관심사가 분리되어야 하는 곳, 트랜잭션처리에 관여 @Service

  → Dao(xxxDao) 한 개 혹은 여러 개의 Dao @Repository

 

클래스이름 앞 어노테이션

  @Controller @RestController @RequestMapping @Autowired @Service @Repository 

 

메소드 앞 어노테이션

  @GetMapping @PostMapping RestAPI전부(@DeleteMapping, @PutMapping)

 

메소드 파라미터 안 클래스

  @Model, @ModelMap(view를 위해 존재, ModelAndView의 대체안, scope는 request)

 

메소드 파라미터 안 어노테이션

  @RequestParam (Map, String, VO 등 올 수 있음)

 

xml에서는 namespace 권장 → beans:bean의 beans:부분

Properties prop = new Properties(”/board3/boardList.sp”. “board-controller(아이디)”) → 자바에선 이렇게 스프링에선 prop 사용

bean은 클래스이다 id는 위의 prop 아이디와 일치해야 한다

bean 안의 인스턴스 변수, name=”boardLogic”은 스프링부트의 @Autowired BoardLogic boardLigic = null;과 같다

PropertiesMethodNameResolver는 URL과 메소드 이름을 매칭해 준다

 

xml과 xml 사이의 의존성주입

xml과 java 사이의 의존성주입 → MemberDao에서 SqlSessionTemplate.xml 주입받은 경우

java와 java 사이의 의존성주입 → @Autowired

 

@Configutation

  xml이 아닌 것도 객체주입 가능

  → 사용자가 정의한 클래스도 ApplicationContext나 BeanFactory의 관리(라이프사이클 관리)를 받을 수 있다

 

@Autowired

  setter객체주입법 대신 만들어짐, 로컬사용

  서블릿 컨테이너에 등록되어 있는 빈을 끌어다가 사용하는 어노테이션

 

@Bean

  독립적, 글로벌 사용

  서블릿 컨테이너에 빈을 등록하는 어노테이션

 

Spring Bean

Spring의 DI Container에 의해 관리되는 POJO(Plain Old Java Object)를 Bean이라고 부름

POJO(Plain Old Java Object)로써 Spring 애플리케이션을 구성하는 핵심 객체

Spring IoC 컨테이너(또는 DI 컨테이너)에 의해 생성 및 관리됨

class, id, scope, constructor-arg 등을 주요 속성으로 지님

Bean의 구성 요소를 바탕으로 등록되어 있는 Bean을 싱글톤 객체로 생성하여 관리

 

@Configuration 어노테이션은 클래스 앞에

@Bean 어노테이션은 메소드 앞에

 

스프링 컨테이너는 @Configuration이 붙어있는 클래스를 자동으로 빈으로 등록해 두고, 해당 클래스를 파싱 해서(읽어서) @Bean이 있는 메소드를 찾아서 빈을 생성해 줌

@Bean을 사용하는 클래스에는 반드시 @Configuration 어노테이션을 활용하여 해당 클래스에서 Bean을 등록하고자 함을 명시해야 함

@Configuration 안에서 @Bean을 사용해야 싱글톤을 보장받을 수 있으므로 @Bean 어노테이션은 반드시 @Configuration과 함께 사용해야 함

 

@Bean, @Configuration

수동으로 스프링 컨테이너에 빈을 등록하는 방법

개발자가 직접 제어가 불가능한 라이브러리를 빈으로 등록할 때 불가피하게 사용

유지보수성을 높이기 위해 애플리케이션 전범위적으로 사용되는 클래스나 다형성을 활용하여 여러 구현체를 빈으로 등록할 때 사용

1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 명시해 주어야 싱글톤이 보장됨

 

@Component

자동으로 스프링 컨테이너에 빈을 등록하는 방법

스프링의 컴포넌트 스캔 기능이 @Component 어노테이션이 있는 클래스를 자동으로 찾아서 빈으로 등록함

대부분의 경우 @Component를 이용한 자동 등록 방식을 사용하는 것이 좋음

@Component 하위 어노테이션으로 @Configuration, @Controller, @Service, @Repository 등이 있음

@Configuration
@PropertySource("classpath:/application.properties") // classpath -> resource -> log4j.properties
public class DatabaseConfiguration {
	private static final Logger logger = LogManager.getLogger(DatabaseConfiguration.class);

	// 물리적으로 떨어져있는 오라클서버(application.properties에 정의됨)와 연결통로 확보
	// POJO에서는 MyBatisConfig.xml이 hikariConfig()역할을 대신함
	@Bean
	public DataSource dataSource() {
		// 인터페이스=구현체클래스 (new HikariConfig()) -> application.properties
		DataSource dataSource = new HikariDataSource(hikariConfig());
		logger.info("datasource : {}", dataSource);
		return dataSource;
	}
}

 

property의 name은 setter 메소드로 사용(관리받을 수 있음)

오라클서버와 커넥션에 필요한 정보

SqlSessionTemplate - SelectOne, SelectList, SelectMap

생성자 객체주입법 constructor-arg → 객체주입받음

 

<RestMemberController.java>

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.logic.BoardLogic;
import com.example.demo.logic.MemberLogic;
import com.google.gson.Gson;

@RestController
@RequestMapping("/member/*")
public class RestMemberController {
	Logger logger = LoggerFactory.getLogger(RestMemberController.class);
	
	@Autowired
	private MemberLogic memberLogic = null;
	
	@GetMapping("jsonMemberList")
	public String jsonMemberList(Model model) {
		logger.info("jsonMemberList 호출");
		String temp = null;
		List<Map<String, Object>> mList = new ArrayList<>();
		Map<String, Object> pMap = new HashMap<>();
		mList = memberLogic.memberList(pMap);
		Gson g = new Gson();
		temp = g.toJson(mList);
		return temp;
	}
}

 

<MemberLogic.java>

package com.example.demo.logic;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.MemberDao;

/*
 * 모델계층(MemberLogic[직접 오라클과 연동하지 않음] + MemberDao[데이터셋])
 * 공통된 관심사 분리(AspectJ 프레임워크 - 오픈소스 참고)
 * 트랜잭션 처리 지원받음
 */
@Service
public class MemberLogic {
	Logger logger = LoggerFactory.getLogger(MemberLogic.class);

	@Autowired
	private MemberDao memberDao = null;
	
	public List<Map<String, Object>> memberList(Map<String, Object> pMap) {
		logger.info("memberList 호출");
		List<Map<String,Object>> mList = new ArrayList<>();
		mList= memberDao.memberList(pMap);
		return mList;
	}
	
	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap.toString());
		int result = 0;
		result = memberDao.memberInsert(pMap);
		return result;
	}
}

 

<MemberDao.java>

package com.example.demo.dao;

import java.util.List;
import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

// @Repository -> MVC패턴 공부를위해 Service를 사용함
@Service
public class MemberDao {
	Logger logger = LoggerFactory.getLogger(MemberDao.class);
	
	@Autowired
	private SqlSessionTemplate sqlSessionTemplate = null;

	public List<Map<String, Object>> memberList(Map<String, Object> pMap) {
		logger.info("memberList 호출");
		List<Map<String,Object>> mList = sqlSessionTemplate.selectList("memberList", pMap);
		return mList;
	}
	
	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap.toString());
		int result = 0;
		return result;
	}
}

 

<member.xml>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo">
	<select id="getToday" resultType="string" parameterType="string">
		SELECT to_char(sysdate, 'YYYY-MM-DD') FROM dual
	</select>
	<select id="login" parameterType="map" resultType="string">
		select mem_name from book_member
		<where>
			<if test='mem_id!=null and mem_id.length()>0'>
				AND mem_id = #{mem_id}
			</if>
			<if test='mem_pw!=null and mem_pw.length()>0'>
				AND mem_pw = #{mem_pw}
			</if>
		</where>
	</select>
	<select id="memberList" parameterType="map" resultType="map">
		select mem_id, mem_name from book_member
		<where>
			<if test='mem_id!=null and mem_id.length()>0'>
				AND mem_id = #{mem_id}
			</if>
		</where>
	</select>
</mapper>

 

<DatabaseConfiguration.java>

package com.example.demo;

import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

/*
 * @Configuration
 * 사용자 정의 클래스도 ApplicationContext나 BeanFactory의 관리를 받을 수 있다 - 의존성 주입(Dependency Injection)
 * 
 * application.properties - maven방식
 * application.yml - gradle방식(반복코드 없음, json포맷이라서)
 * 
 * resource - mapper
 * 	member.xml, board.xml, order.xml
 * 
 * static - CDN
 * 	css, js, images(정적인것들을 static에 넣음)
 */

@Configuration
@PropertySource("classpath:/application.properties") // classpath -> resource -> log4j.properties
public class DatabaseConfiguration {
	private static final Logger logger = LogManager.getLogger(DatabaseConfiguration.class);

	// @Bean - @Configuration으로 선언된 클래스에서만 사용가능한 어노테이션
	@Bean
	@ConfigurationProperties(prefix = "spring.datasource.hikari") // application.properties의 접두어
	public HikariConfig hikariConfig() { // 인스턴스화
		return new HikariConfig(); // 생성자호출 - 메모리에 로딩됨 - 변수와 메소드를 누릴 수 있음(주입해줌)
	}

	// 물리적으로 떨어져있는 오라클서버(application.properties에 정의됨)와 연결통로 확보
	// POJO에서는 MyBatisConfig.xml이 hikariConfig()역할을 대신함
	@Bean
	public DataSource dataSource() {
		// 인터페이스=구현체클래스 (new HikariConfig()) -> application.properties
		DataSource dataSource = new HikariDataSource(hikariConfig());
		logger.info("datasource : {}", dataSource);
		return dataSource;
	}

	// @Autowired 의존성 주입
	@Autowired
	private ApplicationContext applicationContext; // 빈관리 - 이른 인스턴스화 - BeanFactory의 자손 클래스임(기능 더 많다)

	/*
	 	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="WEB-INF/mybatis-config.xml"/>
		<property name="dataSource" ref="data-source-target"/> 
		</bean>
		위의 코드를 대체하는 것의 아래의 @Bean
	 */
	@Bean
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);
		//classpath는 src/main/resources이고 쿼리가 있는 xml 위치를 설정해주면 된다.
		sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:/mapper/**/*.xml"));
		return sqlSessionFactoryBean.getObject();
	}

	/*
	 	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory"/>
		</bean>
		위의 코드를 대체하는 것의 아래의 @Bean
	 */
	@Bean
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
}

 

<application.properties>

server.port=8000

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

#spring.http.encoding.charset=UTF-8
#MultipartRequest
spring.servlet.multipart.enabled=true
spring.servlet.multipart.file-size-threshold=2KB
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB
#Hikari
spring.datasource.hikari.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.hikari.jdbcUrl=jdbc:oracle:thin:@127.0.0.1:1521:orcl11
spring.datasource.hikari.username=scott
spring.datasource.hikari.password=tiger
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.minimum-Idle=5
spring.datasource.hikari.maximum-pool-size=12
spring.datasource.hikari.ldle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
spring.datasource.hikari.auto-commit=true

 

표현언어(Expression Language)

표현 언어는 값을 표현하는 데 사용되는 새로운 스크립트 언어로서 JSP2.0 규약에서 새롭게 추가된 기능

JSP에서 출력을 위해서 표현식을 사용함

${“Hello EL” } <br> <!—EL 표현 언어 -->
<%= “Hello EL” %> <br> <!—JSP 표현식 -->
<% out.println(“Hello EL”); %> <br> <!—JSP 스크립트릿 -->

 

<EL(표현언어)실습 - cookieCreate.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키 생성하기</title>
</head>
<body>
<%
	// 아래 코드는 서버가 바라보는 물리적인 위치에 들어있음
	// 서버측에서 실행됨 -> html -> 다운로드(SSR) -> 동적처리(실시간)
	Cookie c1 = new Cookie("notebook", "삼성갤럭시북");
	c1.setMaxAge(60*5);
	c1.setPath("/");
	Cookie c2 = new Cookie("hp", "아이폰14");
	c2.setMaxAge(60*2);
	c2.setPath("/");
	Cookie c3 = new Cookie("coffee", "아메리카노");
	c3.setMaxAge(60*3);
	c3.setPath("/");
	response.addCookie(c1);
	response.addCookie(c2);
	response.addCookie(c3);
%>
</body>
</html>

 

<EL(표현언어)실습 - cookiePrint.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>EL(표현언어)실습</title>
</head>
<body>
쿠키이름 : ${cookie.notebook.name}, ${cookie.hp.name}, ${cookie.coffee.name}
<br />
쿠키값 : ${cookie.notebook.value}, ${cookie.hp.value}, ${cookie.coffee.value}
</body>
</html>

댓글