상세 컨텐츠

본문 제목

프로그래머 도전기 98일차

프로그래머가 될거야!

by Choyee 2024. 1. 10. 10:33

본문

오늘은

 

해야 할 일들이 점점 늘어만 가는데

차근차근히 풀어나가려고 해도 일이 점점 쌓이고 있습니다

언제 쯤 다 해낼 수 있을지 걱정이 듭니다

또 곧 있으면 파이널 프로젝트가 시작이 될텐데

시간이 언제 이렇게 흘러갔는지...ㅠㅠ

엊그제가 새해였던것 같은데 벌써 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 (화)

관련글 더보기