오늘은
Spring과 React를 동시에 공부하다보니
재미도 두배인것같습니다
Spring은 기존과 비슷하면서도 훨씬 깊게 들어가는 느낌이고
React는 기존의 것들을 종합해서 새로운 것을 배우는 느낌입니다
파이널 프로젝트 때 이 두가지를 활용하여 코딩을 할 생각을 해보니
흥미진진하면서도 역시나 걱정이 많이 되는 것 같습니다
Spring 공부
예외 및 오류 처리
주요 클래스 = ControllerAdvice
Advice= 메인X 지원O
- 500(코드 및 구문 오류)
web.xml - 추가 설정
- 404(페이지 없음)
package com.khit.web.exception;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;
@ControllerAdvice // 오류를 처리하는 주요 클래스
public class GlobalExceptionAdvice {
// 예외 처리(코드 및 구문 오류)
@ExceptionHandler(Exception.class)
public String handleException(Exception exception, Model model) {
model.addAttribute("exception", exception);
// forward 방식 사용
return "/exception/global_error";
}
// 예외 처리(페이지 찾을 수 없음)
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handle404(NoHandlerFoundException ex) {
return "/exception/error_404";
}
}
web.xml
<!-- 404 에러 처리 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
- 403(권한 에러)
- 200(정상)
=> 아래쪽 근본원인 부분에 hint가 많다
정규표현식
-> 비밀번호에 사용될 수 없는 특수문자 존재
회원 중복 검사
- ajax(비동기통신) : 제이쿼리 라이브러리 import필요
- onblur() = 마우스가 떠났을 때(focus가 떠났을 때) 이벤트가 일어남
- $.ajax({
실행문
})
- Controller 에서의 반환형 = json 객체
=> @ResponseBody SampleDTO checkUserId( ){ }
const checkUser = function(){
//alert("가입");
// 아이디는 4자 이상 15자 이하 까지 입력해주세요
// 비밀번호는 영문자, 숫자, 특수문자 포함 8자 이상 입력해주세요
// 이름은 한글로 입력해주세요
// 나이는 숫자를 입력해주세요
let form = document.userform//document.getElementById("userform");
let id = document.getElementById("userId").value;
let pw = document.getElementById("userPasswd").value;
let name = document.getElementById("userName").value;
let age = document.getElementById("userAge").value;
// 정규 표현식
let regName = /^[가-힣]+$/; // 한글만
let regPw1 = /[0-9]+/; // 숫자
let regPw2 = /[a-zA-Z]+/; // 영문자
let regPw3 = /[~!@#$%^&*()\-=]+/; // 특수문자
if(id == "" || id.length < 4 || id.length > 15) {
alert("아이디는 4자 이상 15자 이하 까지 입력해주세요");
document.getElementById("userId").select();
return false;
}else if(pw == "" || pw.length < 8 || !regPw1.test(pw) || !regPw2.test(pw) || !regPw3.test(pw)) {
alert("비밀번호는 영문자, 숫자, 특수문자 포함 8자 이상 입력해주세요")
document.getElementById("userPasswd").select();
return false;
}else if(name == "" || !regName.test(name)) {// 한글 정규식에 일치하지 않으면
alert("이름은 한글로 입력해주세요")
document.getElementById("userName").select();
return false;
}else if(age == "" || isNaN(age)) {
alert("나이는 숫자를 입력해주세요")
document.getElementById("userAge").select();
return false;
}
else {
form.submit(); // 유효하면 폼 전송
}
} // checkUser 닫기
// ID 중복검사 순서
/*
1. 아이디의 입력 값 가져오기
2. 입력 값을 서버(controller)에 전송하고 중복된 아이디가 있는지 확인
*/
const checkId = function(){
//alert("중복검사");
let userId = document.getElementById("userId").value;
let checkResult = document.getElementById("check-result");
console.log(userId);
if (userId === "") {
checkResult.innerHTML = "아이디를 입력해주세요";
checkResult.style.color = "black"; // 원하는 스타일로 변경하세요
return;
}
$.ajax({
// 요청방식:post, url:/user/checkuserid, data:userId
type: "post",
url: "/user/checkuserid",
data: {"userId": userId},
// 서버 응답 : 성공 / 실패
success: function(response){
console.log(response);
if(response == "usable"){
checkResult.innerHTML = "사용가능한 아이디입니다";
checkResult.style.color = "blue";
}else if (userId == "") {
checkResult.innerHTML = "아이디를 입력해주세요";
}
else {
checkResult.innerHTML = "이미 사용중인 아이디입니다";
checkResult.style.color = "red";
}
},
error: function(error){
console.log("에러발생", error);
}
});
}
* Model
@ModelAttribute : 폼 전체 전달할 때
=> (@ModelAttribute) SampleDTO sampleDTO
@RequestParam() : 하나만 전달할 때
=> @RequestParam("name") String name
json객체 변환기 필요 - pom.xml에 의존성 주입
jackson-databind
<!-- jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
게시글 상세보기
- 조회수(updateHit) : sql - update boards set hit = hit+1 where id=#{id}
- 글상세보기 (findById)
* boardController
@GetMapping
public String getBoard(@RequestParam("id") Long id, Model model) {
// 조회수 증가
boardService.updateHit(id);
}
* boardMapper
<update id="updateHit">
update boards set hit = hit + 1
where id = #{id};
</update>
게시글 수정
- boardupdate.jsp
* boardupdate
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글 상세보기</title>
</head>
<body>
<jsp:include page="../layout/header.jsp" />
<div id="container">
<section id="detail">
<h2>글 상세보기</h2>
<form action="/board/update" method="post">
<input type="hidden" name="id" value="${board.id}">
<table class="tbl_detail">
<tr>
<td><input type="text" name="boardTitle"
value="${board.boardTitle}"></td>
</tr>
<tr>
<td>작성자 : ${board.userId}</td>
</tr>
<tr>
<td><textarea name="boardContent" >${board.boardContent}</textarea></td>
</tr>
<tr>
<td>조회수 : ${board.hit}</td>
</tr>
<tr>
<td>작성일 : <fmt:formatDate value="${board.createTime}"
pattern="yyy-MM-dd HH:mm:ss" /></td>
</tr>
<tr>
<td>
<button type="submit">수정 완료</button>
<button type="reset">수정 취소</button>
<a href="/board/"><button>목록</button></a>
</td>
</tr>
</table>
</form>
</section>
</div>
<jsp:include page="../layout/footer.jsp" />
</body>
</html>
* boardController
// 게시글 수정
@GetMapping("/update")
public String update(Model model, @RequestParam("id") Long id) {
BoardDTO baordDTO = boardService.findById(id);
model.addAttribute("board",baordDTO);
return "/board/boardupdate";
}
@PostMapping("/update")
public String update(@ModelAttribute BoardDTO boardDTO) {
boardService.update(boardDTO);
return "redirect:/board?id=" + boardDTO.getId();
}
* boardMapper
<update id="update">
update boards
set boardtitle = #{boardTitle}, boardcontent = #{boardContent}
where id = #{id}
</update>
게시글 삭제
- /delete
* boardController
// 게시글 삭제
@GetMapping("/delete")
public String delete(@RequestParam("id") Long id) {
boardService.delete(id);
return "redirect:/board/";
}
* boardMapper
<delete id="delete">
delete from boards where id = #{id};
</delete>
React 공부
<<List_Delete>>
* App
// app conponent에 onDelete 함수 생성
const onDelete = (targetId) => {
// targetId를 제외한 나머지 id를 가진 배열의 요소들만으로 다시 배열 생성
const newDiaryList = data.filter((it) => it.id !== targetId);
// 새로운 배열로 리스트를 전달해준다
setData(newDiaryList);
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
{/* DiaryList로 onDelete함수를 내려줌 */}
<DiaryList diaryList={data} onDelete={onDelete} />
</div>
);
* DiaryList
// onDelete함수를 사용하진 않지만 받아서
const DiaryList = ({ onDelete, diaryList }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다</h4>
<div>
{diaryList.map((it) => (
// DiaryItem에게 보내주어야 한다
<DiaryItem key={it.id} {...it} onDelete={onDelete} />
))}
</div>
</div>
);
};
* DiaryItem
// onDelete를 prop으로 받아준다
const DiaryItem = ({ author, content, create_date, emotion, id, onDelete }) => {
<button
onClick={() => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onDelete(id);
}
}}
>
삭제하기
</button>
};
<<List_Update>>
리스트 데이터 수정하기
= 배열을 이용한 React의 List에 item을 동적으로 수정 해보기
=> with 조건부 렌더링
* App
const App = () => {
// 수정할 targetId, newContent 두개의 매개변수를 받아온다
const onEdit = (targetId, newContent) => {
// setData 함수를 호추하고 map()함수를 적용해서
// 일치하는 아이디를 찾아 해당 원소가 수정되도록 해준다
setData(
data.map((it) =>
// 일치하는 경우 content를 newContent 로 수정, 일치하지 않으면 그냥 it
it.id === targetId ? { ...it, content: newContent } : it
)
);
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
{/* DiaryList로 onDelete함수를 내려줌 */}
{/* DiaryItem의 부모인 DiaryList로 onEdit함수를 보내준다 */}
<DiaryList onEdit={onEdit} diaryList={data} onRemove={onRemove} />
</div>
);
};
* DiaryList
// onDelete함수를 사용하진 않지만 받아서
// onEdit prop까지 받아서 DiaryItem으로 전달
const DiaryList = ({ onEdit, onRemove, diaryList }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다</h4>
<div>
{diaryList.map((it) => (
// DiaryItem에게 보내주어야 한다
<DiaryItem key={it.id} {...it} onEdit={onEdit} onRemove={onRemove} />
))}
</div>
</div>
);
};
* DiaryItem
// diaryItem component 생성
import { useRef, useState } from "react";
// onDelete를 prop으로 받아준다
// DiaryList에서 내려준 onEdit함수 prop을 받아서 호출해주면 된다
const DiaryItem = ({
onRemove,
onEdit,
author,
content,
emotion,
id,
create_date,
}) => {
// 수정하기 버튼을 눌렀을 때 나오는 수정 폼 => status로 만들기
// isEdit = boolean 값으로 수정 중인지 아닌지를 판단
const [isEdit, setIsEdit] = useState(false);
// isEdit의 값을 반대로 바꾸는 반전연산 함수
// true 값이라면 수정중으로 간주해서 수정 폼을 띄우도록 한다
const toggleIsEdit = () => setIsEdit(!isEdit);
//
const localContentInput = useRef();
// textarea의 input을 handling할 status 생성
// useState(content) localContent의 기본값을 원래의 content값으로 설정해서
// 수정하기 버튼을 누르면 원래의 내용을 불러올 수 있도록 해준다
const [localContent, setLocalContent] = useState(content);
// 밖으로 뺀 onClick의 함수를 새로 정의해줌
const handleClickRemove = () => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onRemove(id);
}
};
// 수정폼 내용 자체를 원래의 내용으로 초기화 해주는 함수
const handleQuitEdit = () => {
setIsEdit(false);
setLocalContent(content);
};
// 수정완료 버튼을 눌렀을 때의 event를 처리할 함수
const handleEdit = () => {
// 5자 이상 입력하도록
if (localContent.length < 5) {
localContentInput.current.focus();
return;
}
// 확인받기
if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
onEdit(id, localContent);
// 수정이 완료되었기 때문에 토글을 한번더 실행해서 상태를 종료해준다
toggleIsEdit();
}
};
return (
<div className="DiaryItem">
<div className="info">
<span className="author_info">
| 작성자 : {author} | 감정점수 : {emotion} |
</span>
<br />
<span className="date">{new Date(create_date).toLocaleString()}</span>
</div>
<div className="content">
{/* 3항 연산자를 사용하여 isEdit의 상태에 따라 표시를 다르게 해줌 */}
{isEdit ? (
<textarea
ref={localContentInput}
// 값을 localContent에 mapping
value={localContent}
// onChange event handler에는 e event 객체를 받아서
// 적힌 값을 localContent에 mapping 시켜서 사용할 수 있게 만들어준다
onChange={(e) => setLocalContent(e.target.value)}
/>
) : (
content
)}
</div>
{/* 버튼의 onClick 함수가 길어서 밖으로 빼줌 */}
{/* 수정하기가 활성중일 때 버튼도 따라서 달라짐 */}
{isEdit ? (
<>
<button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
) : (
<>
<button onClick={handleClickRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default DiaryItem;
2023. 01. 04 (목)
프로그래머 도전기 97일차 (2) | 2024.01.08 |
---|---|
프로그래머 도전기 96일차 (2) | 2024.01.05 |
프로그래머 도전기 94일차 (2) | 2024.01.03 |
프로그래머 도전기 93일차 (0) | 2024.01.01 |
프로그래머 도전기 92일차 (0) | 2023.12.31 |