국비 지원 개발자 과정_Day59
요구사항 정의서
1. 게시글 목록 페이지를 열 때 오라클 서버를 경유해 최신글 10건을 가져온다/내림차순으로 정렬하여 출력
2. 제목을 클릭하면 상세 보기 페이지로 이동하기 전에 오라클 서버를 경유해 사용자가 선택한 제목에 대한 한 건의 정보만 가져와서 read.jsp에 출력
1번에서 제목을 클릭할때 글 번호를 서블릿에 넘겨서 select문 where절에 조건값으로 사용
3. 댓글쓰기 버튼을 누르면 상세 보기 페이지에서는 글번호, 글그룹번호, 차수, 순번, 글제목 등 1번에서 선택된 글번호에 해당하는 모든 정보를 가지고 있으므로 글 내용을 입력받아 폼 전송 시에 댓글 쓰기에 필요한 정보인 글번호, 글그룹번호, 차수, 순번을 반드시 넘겨준다
위에서 가져온 글번호는 새글쓰기인지 댓글 쓰기인지를 판별하는 용도로 사용
새 글쓰기일 때
bm_no 채번 / bm_group 채번 / bm_pos은 0 / bm_step도 0
댓글 쓰기일 때
bm_no 채번 / bm_group은 가져온 값으로 추가 → read.jsp(댓글쓰기버튼) / bm_pos=bm_pos+1 / bm_step=bm_step+1
4. 상세 보기에서 글 수정을 누르면 기본정보를 출력하고, 사용자가 선택하여 수정받도록 처리
수정의 범위는 정해야 한다
단 수정의 경우 비번을 입력받아서 일치하는 경우에만 수정처리를 해준다
5. 상세 보기에서 글삭제를 누르면 정말 삭제하시겠습니까? 다이얼로그창으로 묻고, 비번이 일치하면 삭제처리한다
공통사항 1 - 비번은 언제 어디서 가져오는지?
상세 보기에서 비번을 미리 가지고 있도록 한다 → 화면에는 보이지 않아야 함 <input type=”hidden”>
공통사항 2 - 비번에 대한 비교는 무엇으로?
자바스크립트로 처리한다
정규화(Normalization)
중복을 최소화하게 데이터를 구조화하는 프로세스
정규화의 기본 목표는 관련이 없는 함수 종속성은 별개의 릴레이션으로 표현하는 것
이상 현상의 발생 가능성을 줄일 수 있지만 연산 시간이 증가함
제1 정규형(1NF)
릴레이션에 속한 모든 속성의 도메인이 더 이상 분해되지 않는 원자값으로만 구성
-각 칼럼이 하나의 속성만을 가져야 한다.
-하나의 컬럼은 같은 종류나 타입(type)의 값을 가져야 한다.
-각 컬럼이 유일한(unique) 이름을 가져야 한다.
-칼럼의 순서가 상관없어야 한다.
제2 정규형(2NF)
릴레이션이 제1 정규형에 속하고, 기본키가 아닌 모든 속성이 기본키에 완전 함수 종속(어떤 속성이 기본키에 대해 완전히 종속적인 것, 기본키의 부분집합이 결정자가 되어선 안됨)
-1 정규형을 만족해야 한다.
-모든 컬럼이 부분적 종속(Partial Dependency)이 없어야 한다.(모든 칼럼이 완전 함수 종속을 만족해야 함)
제3 정규형(3NF)
릴레이션이 제2 정규형에 속하고, 기본키가 아닌 모든 속성이 기본키에 이행적 함수 종속(A→B, B→C인 경우 A→C가 성립될 때)이 되지 않음
-2 정규형을 만족해야 한다.
-기본키를 제외한 속성들 간의 이행 종속성 (Transitive Dependency)이 없어야 한다.
FrontController-요청 제일 처음으로 받아줌
doGet / doPost
|| URLHandlerMapping ||
BoardController-req, res사용(FrontController만 서블릿임)
getBoardList → list.jsp / getBoardDetail → read.jsp / boardInsert / boardDelete / boardUpdate
BoardLogic
getBoardList / getBoardList / boardInsert / boardUpdate / boardDelete
BoardDao
getBoardList / getBoardList / boardMInsert / boardSInsert / boardUpdate / boardDelete
board.xml
<board.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.mybatis.mapper.BoardMapper">
<!-- 게시글목록 가져오는 쿼리문 -->
<select id="getBoardList" parameterType="java.util.HashMap"
resultType="map">
SELECT bm.bm_no,
bm.bm_title,
bm.bm_writer,
bs.bs_file,
bm.bm_hit,
bm.bm_pos,
bm.bm_step
FROM board_master_t bm, board_sub_t bs
WHERE bm.bm_no =
bs.bm_no(+)
<!-- 글번호로 검색 -->
<if test= 'bm_no > 0' >
AND bm_no = #{bm_no}
</if>
<!-- 글제목으로 검색 -->
<if test= 'cb_search!=null and cb_search.equals("bm_title")' >
AND bm_title LIKE '%'||#{keyword}||'%'
</if>
<!-- 글내용으로 검색 -->
<if test= 'cb_search!=null and cb_search.equals("bm_content")' >
AND bm_content LIKE '%'||#{keyword}||'%'
</if>
<!-- 작성자로 검색 -->
<if test= 'cb_search!=null and cb_search.equals("bm_writer")' >
AND bm_writer LIKE '%'||#{keyword}||'%'
</if>
ORDER BY bm.bm_group DESC, bm.bm_step ASC
</select>
<!-- board Master에 인서트 -->
<!-- pos와 step은 원글이면 0, 댓글이면 read.jsp에서 가진 값에 1을 더한 값으로 결정됨
조회수는 0(새글이니까), 날짜는 시스템 날짜정보로 등록 -->
<insert id="boardMInsert" parameterType="map">
INSERT INTO
board_master_t(bm_no, bm_title, bm_writer, bm_reg, bm_hit,
bm_group, bm_pos, bm_step)
VALUES(seq_board_no.nextval, #{bm_title}, #{bm_writer},
to_char(sysdate, 'YYYY-MM-DD'), 0, #{bm_group},
#{bm_pos}, #{bm_step})
</insert>
<!-- board Sub에 인서트 -->
<!-- 첨부파일을 추가하는 경우에만 실행됨, 없으면 해당없음
글번호는 새글쓰기에서 결정된 값이 대입되어야함, 새로채번하면 안됨! -->
<insert id="boardSInsert" parameterType="map">
INSERT INTO
board_sub_t(bm_no, bs_seq, bs_file, bs_size)
VALUES(#{bm_no}, 1, #{bs_file}, #{bs_size})
</insert>
<!-- board Master에 Step 업데이트 -->
<!-- 내가 쓰는 댓글 뒤에 댓글이 존재하는 경우만 실행됨
조건절에 들어오는 그룹번호와 step은 상세보기 화면에서 가져온 값이 대입됨 -->
<update id="bmStepUpdate" parameterType="map">
UPDATE board_master_t
SET bm_step = bm_step + 1
WHERE bm_group = #{bm_group}
AND bm_step > #{bm_step}
</update>
</mapper>
UML 다이어그램
통합 모델링 언어를 사용하여 시스템 상호작용, 업무흐름, 시스템구조, 컴포넌트 관계 등을 그린 도면
프로젝트 구조의 로드맵을 만들거나 개발을 위한 시스템 구축의 기본을 마련함
Class 다이어그램
시스템의 구조적인 모습을 그림
Use Case 다이어그램
요구 분석 과정에서 시스템과 외부와의 상호작용을 묘사함
Sequence 다이어그램
객체 간의 메시지 전달을 시간적 흐름에서 분석함
폭포수(Waterfall) 방법론
개발 단계가 위에서부터 아래로 폭포에서 물이 떨어지듯 순차적으로 진행됨
한 단계씩 진행함에 따라 다시 이전 단계로 가지 않고 계속 진행하기에, 다음 단계로 가기 전 완벽하게 요구사항을 반영하여 개발했다는 것을 전제로 함
요구사항 분석 → 설계 → 구현 → 검증(테스트) → 유지보수
애자일(Agile) 방법론
개발 단계를 명확하게 구분하지 않고 각 단계를 반복적으로 수행하면서 진행
요구사항을 추가하거나 제외하면서 개발
잦은 요구사항의 변경이나 요구사항 분석 및 설계를 완벽하게 하기 어려운 경우 적합
<RestHomeController.java>
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
public class RestHomeController {
@GetMapping("/home")
public String home() {
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
return "spring gradle test!";
}
}
// 테스트: http://localhost:7000/home
<index.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body>
Gradle Project - index.jsp 테스트
</body>
</html>
<!-- 테스트: http://localhost:7000/index.jsp -->
WEB-INF는 URL로(http://localhost:7000/WEB-INF/home/index.jsp 이런 식으로) 접근 X! 서버 내부에서만 접근이 가능하다
mvc패턴은 반드시 요청을 받아주는 페이지(소스)와 응답이 나가는 페이지가 완전히 분리되어야 한다
요청 → 서블릿이 받음(HttpServlet), 스프링에선 DispatchServlet
서블릿의 요청받기 위해 GetMapping(자바로는 doGet), 여기선 요청 url이름(””)을 붙임
@RestController → 마임타입 plain(text), 화면제공 x
@Controller → 마임타입 text/html, 화면제공 가능!
응답
<application.yml>
spring: mvc: view:를 통해 접두어로 /WEB-INF/view/ 접미어로 .jsp를 붙이도록 설정
server:
port: 7000
spring:
output:
ansi:
enabled: always
mvc:
view:
prefix: /WEB-INF/view/
suffix: .jsp
logging:
level:
'[com.example.demo]': DEBUG
<WEB-INF 접근 - HomeController.java>
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping("/home/*") // WEB-INF/view/home/ 접근하기위해
public class HomeController {
@GetMapping("index")
public String index() {
log.info("index호출");
return "home/index";
// 테스트: http://localhost:7000/home/index
}
@GetMapping("index2")
public String index2() {
log.info("index2호출");
return "redirect:index.jsp";
// 테스트: http://localhost:7000/home/index2
}
}
<WEB-INF 접근 - index,jsp 1>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>home[Gradle]</title>
</head>
<body>
<h3>home index.jsp 페이지입니다.</h3>
</body>
</html>
<WEB-INF 접근 - index,jsp 2>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>home[webapp]</title>
</head>
<body>
<h3>home index.jsp 페이지입니다.</h3>
</body>
</html>
<index.html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
<!--
yarn start 엔터하면 react 서버가 기동 - 포트번호 3000번
http://localhost:3000/ Ctrl+클릭 -> 페이지 요청 -> index.html
index.html에는 리액트로 랜더링된 html 태그들이 들어온다
들어오는 위치는 id가 root인 내부에 삽입된다
리액트 프로젝트 안에 package.json 안에 main 자바스크립트 코드가 등록됨 -> index.js
-->
<index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import HackerApp from "./HackerApp";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<>
{/* <App /> */}
<HackerApp />
</>
);
/*
HackerNews 데이터를 axios를 가져올 때 React.StrictMode가 있으면
두 번 요청이 발생하니 빈 태그로 변경함
여기서 빈 태그는 Fragment를 의미함 <React.Fragment>
*/
<HackerApp.jsx>
import axios from "axios";
import { useEffect, useState } from "react";
import HackerNews from "./components/HackerNews"
const HackerApp = () => {
const [newsList, setNewsList] = useState([]);
const NEWS_URL = "https://api.hnpwa.com/v0/news/1.json";
useEffect(() => {
axios.get(NEWS_URL).then(response => {
console.log(response.data);
// console.log(newsList);
// 6번에서 선언한 useState훅에 데이터 초기화함
setNewsList(response.data);
})
}, [])
return(
<HackerNews newsList={newsList} />
)
}
export default HackerApp;
<HackerNews.jsx>
const HackerNews = (props) => {
const {newsList} = props; // 구조분해할당
return(
<div>
{/* [{}, {} ...] 배열 안에 객체 - map으로 꺼낸다 */}
{newsList && Object.keys(newsList).map(key => (
<li>
No: {newsList[key].id}<br />
title: {newsList[key].title}<br />
user: 😀{newsList[key].user}<br />
comments_count: 💬{newsList[key].comments_count}<br />
time_ago: ⏰{newsList[key].time_ago}
</li>
))}
</div>
)
}
export default HackerNews;
<바벨.html 복습>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>바벨소개</title>
<!-- import React from "react"; -->
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.development.js"
></script>
<!-- import ReactDOM from "react-dom/client"; -->
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
></script>
<!-- ES5같은 버전에서도 동작하도록 지원받기위해서 바벨도 주입받음
CDN방식 - 로컬PC 캐쉬 사용함 -> 시스템 사용 발생
unpkg방식 - 네트워크통해 객체 주입 -> 속도향상
-->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<!-- 아래 내부노드에 리액트를 통해서 생산된 태그 들어옴
정확한 위치 접근위해 id부여
-->
<div id="root"></div>
<style>
.title {
color: green;
}
</style>
<!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
<script type="text/babel">
// 자바스크립트에서 html 노드를 접근하기 위해서 브라우저의 내장객체인 document는 querySelector 제공함
const rootElement = document.querySelector("#root"); // 아이디인 경우 반드시 #을 붙이기
// React에서 제공하는 createElement함수로 태그 생성가능
// 스타일 추가시에는 반드시 class 속성이 아니라 className으로 해야함
const element = React.createElement(
// DOM생성하기
"h1",
{
className: "title",
},
["Java", "React", "Spring"]
);
// 그려야 할 위치.render는 그려주는 역할(element는 그릴 것들)
ReactDOM.createRoot(rootElement).render(element);
</script>
</body>
</html>
<!--
const root = createRoot(container);
root.render(element);
주어진 container에 대해 React 루트를 만들고 해당 루트를 반환합니다.
반환된 루트로 render를 통해 React 엘리먼트를 DOM으로 렌더링할 수 있습니다
-->
<바벨3.html 복습>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>바벨소개</title>
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.development.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<style>
.title {
color: purple;
font-size: 58px;
}
</style>
<!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
<script type="text/babel">
const rootElement = document.querySelector("#root");
const subject = "React";
const 클래스이름 = "title";
// 태그안에 중괄호{}만 붙이면 어디서든 자바스크립트 코드 사용이 가능하다
// 심지어 태그의 데이터셋을 참조하는 주소번지도 전달할 수 있다 - 자바와비슷
const element = <h1 className={클래스이름}>{subject}</h1>;
ReactDOM.createRoot(rootElement).render(element);
</script>
</body>
</html>
<element1.html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.development.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root">
<h1>One</h1>
<h1>Two</h1>
<h1>Three</h1>
</div>
<script type="text/babel">
const rootElement = document.querySelector("#root");
const element = (
<div
className="box"
children={[
React.createElement("h1", null, "Zero"),
React.createElement("h1", null, "Two"),
React.createElement("h1", null, "Three"),
]}
/>
);
ReactDOM.createRoot(rootElement).render(element);
</script>
</body>
</html>