오늘은
해야 할 일들이 점점 늘어만 가는데
차근차근히 풀어나가려고 해도 일이 점점 쌓이고 있습니다
언제 쯤 다 해낼 수 있을지 걱정이 듭니다
또 곧 있으면 파이널 프로젝트가 시작이 될텐데
시간이 언제 이렇게 흘러갔는지...ㅠㅠ
엊그제가 새해였던것 같은데 벌써 1월의 3분의 1이 지났네요..
Spring 수업
변환기 - 서비스계층
Service, Controller
DTO - TodoDTO(전달, 유효성 검사)
변환 - builder( )
VO <-> DTO
1. 등록(회원가입, 글쓰기) : 입력폼 수집(DTO) -> DB에 저장(VO)
2. 검색(목록보기, 상세보기) : DB(VO) -> 브라우저 출력(DTO)
<1개 보기(상세 보기)>
* TodoController
@AllArgsConstructor
@Slf4j
@RequestMapping("/todo")
@Controller
public class TodoController {
private TodoService todoService;
// 상세보기
@GetMapping // 경로지정X => /todo?tno=1
public String getTodo(Model model, @RequestParam("tno") Long tno) {
TodoDTO todoDTO = todoService.findById(tno);
model.addAttribute("todo", todoDTO);
return "/todo/detail";
}
}
* TodoService
public interface TodoService {
TodoDTO findById(Long tno);
}
* TodoServiceImpl
@AllArgsConstructor
@Service
public class TodoServiceImpl implements TodoService {
private TodoMapper todoMapper;
private ModelMapper modelMapper;
@Override
public TodoDTO findById(Long tno) {
// vo를 가져오서 dto로 변환
TodoVO todoVO = todoMapper.findById(tno);
/*TodoDTO todoDTO = TodoDTO.builder()
.tno(todoVO.getTno())
.title(todoVO.getTitle())
.writer(todoVO.getWriter())
.createDate(todoVO.getCreateDate())
.build();*/
// modelMapper
TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
return todoDTO;
}
}
* TodoMapper
public interface TodoMapper {
public TodoVO findById(Long tno);
}
* TodoMapper.xml
<mapper namespace="cohttp://m.khit.todoweb.mapper.TodoMapper">
<select id="findById" resultType="cohttp://m.khit.todoweb.vo.TodoVO">
select * from tbl_todo where tno = #{tno}
</select>
</mapper>
* detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Todo 등록</title>
</head>
<body>
<div class="container-fluid">
<jsp:include page="../layout/header.jsp" />
<!-- 본문 영역 -->
<div class="row content">
<div class="col">
<div class="card">
<div class="card-body">
<div class="input-group mb-3">
<label class="input-group-text">Tno</label> <input
class="form-control" type="text" name="tno" value="${todo.tno}"
readonly>
</div>
<div class="input-group mb-3">
<label class="input-group-text">To do</label> <input
class="form-control" type="text" name="title"
value="${todo.title}" readonly>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Writer</label> <input
class="form-control" type="text" name="writer"
value="${todo.writer}" readonly>
</div>
<div class="input-group mb-3">
<label class="input-group-text">createDate</label> <input
type="text" name="createDate" class="form-control"
value="<fmt:formatDate
value="${todo.createDate}" pattern="yyy-MM-dd" />"
readonly>
</div>
<div class="my-4">
<div class="float-end">
<button class="btn btn-primary" type="button">Modify</button>
<button class="btn btn-secondary" type="button">gotoList</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 본문 영역 닫기 -->
<jsp:include page="../layout/footer.jsp" />
</div>
<!-- 자바스크립트 영역 -->
<script>
// 목록버튼
const listBtn = document.querySelector(".btn-secondary");
listBtn.addEventListener("click", function(e) {
location.href = "/todo/list";
});
// 수정버튼
const modifyBtn = document.querySelector(".btn-primary");
const tno = '${todo.tno}';
modifyBtn.addEventListener("click", function(e) {
location.href = "/todo/update?tno=" + tno;
});
</script>
</body>
</html>
<할일 수정/삭제>
* update.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Todo 등록</title>
</head>
<body>
<div class="container-fluid">
<jsp:include page="../layout/header.jsp" />
<!-- 본문 영역 -->
<div class="row content">
<div class="col">
<div class="card">
<div class="card-body">
<form action="/todo/update" method="post">
<div class="input-group mb-3">
<label class="input-group-text">Tno</label> <input
class="form-control" type="text" name="tno" value="${todo.tno}"
readonly>
</div>
<div class="input-group mb-3">
<label class="input-group-text">To do</label> <input
class="form-control" type="text" name="title"
value="${todo.title}" >
</div>
<div class="input-group mb-3">
<label class="input-group-text">Writer</label> <input
class="form-control" type="text" name="writer"
value="${todo.writer}" >
</div>
<div class="input-group mb-3">
<label class="input-group-text">createDate</label> <input
type="text" name="createDate" class="form-control"
value="<fmt:formatDate
value="${todo.createDate}" pattern="yyy-MM-dd" />"
readonly>
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Modify</button>
<button class="btn btn-danger" type="button">Delete</button>
<button class="btn btn-secondary" type="button">gotoList</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- 본문 영역 닫기 -->
<jsp:include page="../layout/footer.jsp" />
</div>
<script>
// 목록버튼
const listBtn = document.querySelector(".btn-secondary");
listBtn.addEventListener("click", function(e) {
location.href = "/todo/list";
});
</script>
</body>
</html>
* TodoController
@AllArgsConstructor
@Slf4j
@RequestMapping("/todo")
@Controller
public class TodoController {
private TodoService todoService;
// 수정 페이지 요청
@GetMapping("/update")
public String updateFrom(Model model, @RequestParam("tno") Long tno) {
// 해당 할일 가져오기
TodoDTO todoDTO = todoService.findById(tno);
model.addAttribute("todo", todoDTO);
return "/todo/update";
}
@PostMapping("/update")
public String update(@ModelAttribute TodoDTO todoDTO) {
todoService.update(todoDTO);
return "redirect:/todo/update?tno=" + todoDTO.getTno();
}
// 삭제하기
@GetMapping("/delete")
public String delete(@RequestParam("tno") Long tno) {
todoService.delete(tno);
return "redirect:/todo/list";
}
}
* TodoService
public interface TodoService {
void update(TodoDTO todoDTO);
void delete(Long tno);
}
* TodoServiceImpl
@AllArgsConstructor
@Service
public class TodoServiceImpl implements TodoService {
// 생성자의 매개변수로 들어감
private TodoMapper todoMapper;
private ModelMapper modelMapper;
@Override
public void update(TodoDTO todoDTO) {
TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
todoMapper.update(todoVO);
}
@Override
public void delete(Long tno) {
todoMapper.delete(tno);
}
}
* TodoMapper
public interface TodoMapper {
public void update(TodoVO todoVO);
public void delete(Long tno);
}
* TodoMapper.xml
<mapper namespace="com.khit.todoweb.mapper.TodoMapper">
<update id="update">
update tbl_todo
set title = #{title}, writer = #{writer}
where tno = #{tno}
</update>
<delete id="delete">
delete from tbl_todo where tno = #{tno}
</delete>
</mapper>
<<검색, 페이징 처리>>
<페이지 처리>
1. 페이지당 게시글 수를 정하고 게시글수에 따른 페이지를 설정
=> /todo/paging?page=2&kw=키워드 : 검색, 페이징처리 같이 해줌
2. 하단 페이지 번호
1. PageRequestDTO
*PageRequestDTO
package com.khit.todoweb.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PageRequestDTO { // 파라미터(page, kw) 수집용 DTO
// 필드
private int page = 1; // 한 페이지
private int size = 10; // 페이지당 게시글 수
public int getSkip() { // = #{skip}
return (page - 1) * 10; // 1p - 0, 2p - 10
}
}
2. PageResponseDTO
* PageResponseDTO
package com.khit.todoweb.dto;
import java.util.List;
import lombok.Builder;
import lombok.Data;
@Data
// <E> : 제네릭으로 만드는 이유 = 회원정보나 게시판정보 객체 등을 페이지 처리할 수 있다
public class PageResponseDTO<E> { // 목록데이터, 페이지 이동을 위한 자료형
// 필드
private int page;
private int size;
private int total;
// 시작페이지 번호
private int startPage;
// 마지막페이지
private int endPage;
// 이전페이지 존재여부
private boolean prev;
// 다음페이지 존재여부
private boolean next;
// 목록 데이터
// E = element(객체) : 제네릭<E>
private List<E> dtoList;
// 생성자
@Builder(builderMethodName = "withAll")
public PageResponseDTO(PageRequestDTO pageRequestDTO,
List<E> dtoList,
int total) {
this.page = pageRequestDTO.getPage();
this.size = pageRequestDTO.getSize();
this.total = total;
this.dtoList = dtoList;
// 하단-1,2,3,4,5,6,7,8,9,10
this.endPage = (int)(Math.ceil((double)this.page / this.size))*10;
this.startPage = this.endPage - 9;
// total 83 8.3 -> 9.0 -> 9
int maxPage = (int)(Math.ceil((double)total / this.size));
if(this.endPage > maxPage) {
this.endPage = maxPage;
}
// 이전
this.prev = this.startPage > 1;
// 다음
this.next = total > this.endPage * this.size;
}
}
* TodoController
public class TodoController {
// 1건 상세보기
@GetMapping // 경로지정X => /todo?tno=1
public String getTodo(Model model,
@RequestParam("tno") Long tno,
PageRequestDTO pageRequestDTO) {
TodoDTO todoDTO = todoService.findById(tno);
model.addAttribute("todo", todoDTO);
return "/todo/detail";
}
@GetMapping("/paging")
public String todoPagingList(Model model,
@ModelAttribute PageRequestDTO pageRequestDTO) {
PageResponseDTO<TodoDTO> pageResponseDTO = todoService.pagingList(pageRequestDTO); // class 를 담아서 보냄
model.addAttribute("responseDTO", pageResponseDTO);
return "/todo/pagelist";
}
}
* TodoService
public interface TodoService {
PageResponseDTO<TodoDTO> pagingList(PageRequestDTO pageRequestDTO);
}
* TodoServiceImpl
public class TodoServiceImpl implements TodoService {
// 생성자의 매개변수로 들어감
private TodoMapper todoMapper;
private ModelMapper modelMapper;
@Override
public PageResponseDTO<TodoDTO> pagingList(PageRequestDTO pageRequestDTO) {
// db에서 voList 가져오기
List<TodoVO> voList = todoMapper.pagingList(pageRequestDTO);
// voList를 dtoList로 변환
List<TodoDTO> dtoList = voList.stream()
.map(vo -> modelMapper.map(vo, TodoDTO.class))
.collect(Collectors.toList());
// 전체 데이터 개수 가져옴
int total = todoMapper.todoCount();
PageResponseDTO<TodoDTO> pageResponseDTO = PageResponseDTO.<TodoDTO>withAll() // builder() 대신 활용
.dtoList(dtoList)
.total(total)
.pageRequestDTO(pageRequestDTO)
.build();
return pageResponseDTO;
}
}
* TodoMapper
public interface TodoMapper {
// 목록보기(페이지, 검색)
List<TodoVO> pagingList(PageRequestDTO pageRequestDTO);
public int todoCount();
}
* TodoMapper.xml
<mapper namespace="cohttp://m.khit.todoweb.mapper.TodoMapper">
<select id="pagingList" resultType="cohttp://m.khit.todoweb.vo.TodoVO">
select * from tbl_todo order by tno desc limit #{skip}, #{size}
</select>
</mapper>
* pagelist.jsp
<!-- 페이지 처리 -->
<div class="d-flex justify-content-center">
<ul class="pagination flex-wrap">
<!-- 이전 페이지 -->
<c:if test="${responseDTO.prev}">
<li class="page-item">
<a class="page-link" href="/todo/paging?page=${responseDTO.startPage - 1}">Previous</a>
</li>
</c:if>
<!-- 페이지 번호 -->
<c:forEach var="num" begin="${responseDTO.startPage}" end="${responseDTO.endPage}">
<li class="page-item ${responseDTO.page eq num ? 'active' : ''}">
<a class="page-link" href="/todo/paging?page=${num}">${num}</a>
</li>
</c:forEach>
<!-- 이전 페이지 -->
<c:if test="${responseDTO.next}">
<li class="page-item">
<a class="page-link" href="/todo/paging?page=${responseDTO.endPage + 1}">Next</a>
</li>
</c:if>
</ul>
</div>
<검색>
체크박스 방식
types[ ] (배열)
keyword
- 제목
- 작성자
log4j2 - 라이브러리 추가 => 자세한 로그 조회 가능
1.log4j2-core 2.17.2
log4j2-api 2.17.2
log4j-slf4j-impl 2.17.2
=> debug, warn, info
* pom.xml
<!-- log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
<!-- log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
* TodoMapper
<mapper namespace="cohttp://m.khit.todoweb.mapper.TodoMapper">
<sql id="search">
<where>
<if test="types != null and types.length > 0">
<foreach collection="types" item="type" open="(" close=")" separator=" or ">
<if test="type=='t'.toString()">
title like concat('%', #{keyword}, '%')
</if>
<if test="type=='w'.toString()">
writer like concat('%', #{keyword}, '%')
</if>
</foreach>
</if>
</where>
</sql>
<select id="pagingList" resultType="cohttp://m.khit.todoweb.vo.TodoVO">
select * from tbl_todo
<include refid="search" />
order by tno desc limit #{skip}, #{size}
</select>
<select id="todoCount" resultType="Integer">
select count(tno) from tbl_todo
<include refid="search" />
</select>
</mapper>
React 공부
<<최적화>>
<컴포넌트 재사용>
state
= count, text
<App/>
count와 text라는 state를 두개 갖고있는 App component
count state는 text state는
CountView라는 자식component에게 TextView라는 자식component에게
prop으로 내려준다 prop으로 내려준다
<CountView/> <TextView/>
prop prop
= count = text
-> setCount(10);
setText("Hi"); 명령 입력
-> App component의 count state의 값이 변경 -> App component => re-rendering O
-> porp count의 값도 변경 -> CountView component => re-rendering O
-> TextView component => re-render 필요X
-> 하지만 TextView component도 re-redering이 됨O
=> 부모 component가 re-render가 되면 자식 component도 강제로 re-render가 된다
-> setText의 명령을 실행할 때도 위와 같이 렌더링이 모두 되면서 연산의 낭비가 발생하게 된다
* 연산의 낭비 해결하기
= 업데이트 조건을 만들어준다
=> CountView : count가 변경될 때만 렌더링
=> TextView : text가 변경될 때만 렌더링
=> React.memo 기능으로 가능
* React.memo
= 고차 컴포넌트
= 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수입니다.
=> const MyComponent = React.memo(function MyComponent(props) {
/* props를 사용하여 렌더링 */
});
= 똑같은 prop을 바뀐것처럼 전달받아도 똑같은 prop이라면 re-rendering하지 않는다
= ex) const EnhancedComponent = higherOrderComponent(WrappedComponent);
= higherOrderComponent의 매개변수로 WrappedComponent 를 전달해서
더 강화된 EnhancedComponent를 돌려준다 는 의미
* 객체를 비교하는 방법
let a = { count: 1 };
let b = { count: 1 };
if(a===b) {
console.log("EQUAL");
} else {
console.log("NOT EQUAL");
}
=> 결과 : NOT EQUAL
= 비원시 타입의 형태를 비교할 때
값과 형태는 같지만 객체의 주소에 의한 비교
즉, 얕은 비교를 하기 때문에 같지 않게 나온다
* 객체들은 생성되자마자 고유한 메모리 주소를 가지게 된다
* 얕은 비교 = 객체의 값을 비교하는 것이 아닌 객체의 주소에 의한 비교를 하는 것
let a = { count: 1 };
let b = a;
if(a===b) {
console.log("EQUAL");
} else {
console.log("NOT EQUAL");
}
=> 결과 : EQUAL
= b 변수가 a 객체와 같은 객체를 가리키는 것
* React.memo의 areEqual() 함수
= React.memo()는 인자로 첫번째 인자 component 말고도 areEqual이라는 함수를 두번째 인자도 받는다
areEqual() 함수는 prev props(이전의 props)와 next props(이후의 props)를 받고
동일한 값을 가지면 true를 반환시키고 그렇지 않으면 false를 반환하게 알아서 코딩을 할 수 있게 해줌
=> areEqual()은 비교함수로써 사용이 되기때문에 기존의 얕은 비교를 하게하지 않고 깊은 비교를 할 수 있다
=> 그래서 값의 비교에 따라 re-rendering을 시키거나 시키지 않게 할 수 있다
* App.js
import OptimizeTest from "./OptimizeTest";
const App = () => {
return (
<div className="App">
{/* 가장 상단에 OptimizeTest rendering */}
<OptimizeTest />
</div>
);
};
* OptimizeTest.js
import React, { useEffect, useState } from "react";
// 자식 component 2개 생성
// const Textview = ({ text }) => {
// useEffect(() => {
// console.log(`update :: Text : ${text}`);
// });
// return <div>{text}</div>;
// };
// const CountView = ({ count }) => {
// useEffect(() => {
// console.log(`update :: Count : ${count}`);
// });
// return <div>{count}</div>;
// };
// 각 component의 값이 바뀔때 마다 모두 re-rendering 하게됨
// => 낭비 상황 발생
// 고차 component인 Reat.memo로 감싸준다
// const CountView = React.memo(({ count }) => {
// useEffect(() => {
// console.log(`update :: Count : ${count}`);
// });
// return <div>{count}</div>;
// });
// => text component의 값이 바뀌어도 re-rendering이 되지 않는다
const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`CountA Update - count : ${count}`);
});
return <div>{count}</div>;
});
// 객체인 obj를 prop으로 받는다
const CounterB = ({ obj }) => {
useEffect(() => {
console.log(`CountB Update - count : ${obj.count}`);
});
return <div>{obj.count}</div>;
};
// areEqual함수를 사용하여 얕은 비교가 아닌
// 값을 비교하도록 해서 연산의 낭비를 하지 않도록 해줄 수 있다
const areEqual = (prevProps, nextProps) => {
// 이전 props와 현재 props가 같은 경우 true를 반환
// = re-rendering을 하지 않는다
if (prevProps.obj.count === nextProps.obj.count) {
return true;
}
// false인 경우 re-rendering을 일으킨다
return false;
// = 식 간단화 하기
// = return prevProps.obj.count === nextProps.obj.count;
// 한줄로 작성해도 같은 기능을 한다
};
// 새로운 component 생성
// areEqual는 React.memo의 비교함수로써 작용을 하게 된다
// CounterB는 areEqual의 판단에 따라 re-render를 할지 말지 결정하게 되는
// 메모화된 component가 된다
// MemoizedCounterB 로 고차원이 된 함수를 받는다
const MemoizedCounterB = React.memo(CounterB, areEqual);
// OptimizeTest component 생성
const OptimizeTest = () => {
// 2개의 state 생성
// const [count, setCount] = useState();
// const [text, setText] = useState("");
// count state 생성
// state 의 값이 계속 1
const [count, setCount] = useState(1);
// object state 생성
const [obj, setObj] = useState({
// object state의 초기값으로 count=1이라는 property를 넣어준다
count: 1,
});
return (
<div style={{ padding: 50 }}>
{/* <div>
count를 handling 하겠다
<h2>count</h2>
count를 prop으로 전달
<CountView count={count} />
count를 + 1 해줄 버튼 생성
<button onClick={() => setCount(count + 1)}>+</button>
</div>
<div>
{text handler
<h2>text</h2>
text를 prop으로 전달
<Textview text={text} />
<input value={text} onChange={(e) => setText(e.target.value)} />
</div> */}
<div>
<h2>Counter A</h2>
<CounterA count={count} />
{/* state 의 값이 계속 1이기 때문에 re-rendering되지 않음 */}
<button onClick={() => setCount(count)}>A Button</button>
</div>
<div>
<h2>Counter B</h2>
<MemoizedCounterB obj={obj} />
<button onClick={() => setObj({ count: 1 })}>B Button</button>
</div>
</div>
);
};
// component 내보내기
export default OptimizeTest;
2023. 01. 09 (화)
프로그래머 도전기 100일차 (2) | 2024.01.11 |
---|---|
프로그래머 도전기 99일차 (2) | 2024.01.10 |
프로그래머 도전기 97일차 (2) | 2024.01.08 |
프로그래머 도전기 96일차 (2) | 2024.01.05 |
프로그래머 도전기 95일차 (1) | 2024.01.04 |