💫 TodoMapper ===> TodoService ===> TodoController==>JSP
// 목록 1개씩 상세보기
TodoVO selectOne(Long tno);
<select id="selectOne" resultType="com.multicampus.springex.domain.TodoVO">
select * from tb1_todo where tno =#{tno}
</select>
@Test
public void testSelectOne(){
TodoVO todoVO = todoMapper.selectOne(3L);
log.info(todoVO);
}
TodoDTO getOne(Long tno);
@Override
public TodoDTO getOne(Long tno) {
// TodoService의 실제 동작
//todoVO ==> todoDTO 변환
TodoVO todoVO = todoMapper.selectOne(tno);
TodoDTO todoDTO = modelMapper.map(todoVO, TodoDTO.class);
return todoDTO;
}
tno
)에 해당하는 할 일 데이터를 조회하고, 그 데이터를 뷰로 전달하는 역할// HTTP GET 요청을 처리하며, "/read" 경로로 요청이 들어올 때 호출
@GetMapping("/read")
// model은 뷰와 컨트롤러 간의 데이터 교환을 위한 객체
public void read(Long tno, Model model){
// todoService라는 서비스 객체를 통해 tno에 해당하는 할 일 데이터를 조회하는 역할
TodoDTO todoDTO = todoService.getOne(tno);
log.info(todoDTO);
// 조회한 할 일 데이터를 model에 "dto"라는 이름으로 추가합니다. 이렇게 모델에 데이터를 추가하면 해당 데이터는 뷰로 전달되어 뷰에서 사용가능
model.addAttribute("dto", todoDTO);
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<div class="card-body">
<div class="input-group mb-3">
<span class="input-group-text">TNO</span>
<input type="text" name="tno" class="form-control"
value=<c:out value="${dto.tno}"></c:out> readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-control"
value='<c:out value="${dto.title}"></c:out>' readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">DueDate</span>
<input type="date" name="dueDate" class="form-control"
value=<c:out value="${dto.dueDate}"></c:out> readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control"
value=<c:out value="${dto.writer}"></c:out> readonly>
</div>
<div class="form-check">
<label class="form-check-label" >
Finished
</label>
<input class="form-check-input" type="checkbox" name="finished" ${dto.finished?"checked":""} disabled >
</div>
<div class="my-4">
<div class="float-end">
<button type="button" class="btn btn-primary">Modify</button>
<button type="button" class="btn btn-secondary">List</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
<script>
/*수정에 관련된 기능인데 tno을 담아서 보냄*/
/*위에 버튼 클래스 이름에다가 listener을 달아서 이벤트 처리*/
document.querySelector(".btn-primary").addEventListener("click",function (e){
self.location = "/todo/modify?tno="+${dto.tno} // /todo/modify?tno=3이랑 같은 의미
},false)
document.querySelector(".btn-secondary").addEventListener("click", function(e){
self.location = "/todo/list";
},false)
</script>
<a href="/todo/read?tno=${dto.tno}" class="text-decoration-none" data-tno="${dto.tno}" >
<c:out value="${dto.title}"/></a>
GET 방식
으로 조회한 후 POST 방식
으로 처리@GetMapping({"/read", "/modify"})
// model은 뷰와 컨트롤러 간의 데이터 교환을 위한 객체
public void read(Long tno, Model model){
// todoService라는 서비스 객체를 통해 tno에 해당하는 할 일 데이터를 조회하는 역할
TodoDTO todoDTO = todoService.getOne(tno);
log.info(todoDTO);
// 조회한 할 일 데이터를 model에 "dto"라는 이름으로 추가합니다. 이렇게 모델에 데이터를 추가하면 해당 데이터는 뷰로 전달되어 뷰에서 사용가능
model.addAttribute("dto", todoDTO);
}
getMapping
안에 이름과 꼭 맞춰 줄 것<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<%--read 부분에서 카피해서 card-body부분만 수정--%>
<div class="card-body">
<%--get방식 아니라 post 방식으로 처리하기 위해 form태그 넣어주기--%>
<form action="/todo/modify" method="post">
<div class="input-group mb-3">
<span class="input-group-text">TNO</span>
<input type="text" name="tno" class="form-control"
value=<c:out value="${dto.tno}"></c:out> readonly>
</div>
<%--수정할 수 있는 것은 readonly 뺄 것 --%>
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-control"
value='<c:out value="${dto.title}"></c:out>'>
</div>
<div class="input-group mb-3">
<span class="input-group-text">DueDate</span>
<input type="date" name="dueDate" class="form-control"
value=<c:out value="${dto.dueDate}"></c:out>>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control"
value=<c:out value="${dto.writer}"></c:out> readonly>
</div>
<div class="form-check">
<label class="form-check-label" >
Finished
</label>
<input class="form-check-input" type="checkbox" name="finished" ${dto.finished?"checked":""} >
</div>
<div class="my-4">
<div class="float-end">
<button type="button" class="btn btn-danger">Remove</button>
<button type="button" class="btn btn-primary">Modify</button>
<button type="button" class="btn btn-secondary">List</button>
</div>
</div>
</form>
<script>
/*수정에 관련된 기능인데 tno을 담아서 보냄*/
/*위에 버튼 클래스 이름에다가 listener을 달아서 이벤트 처리*/
document.querySelector(".btn-primary").addEventListener("click",function (e){
self.location = "/todo/modify?tno="+${dto.tno} // /todo/modify?tno=3이랑 같은 의미
},false)
document.querySelector(".btn-secondary").addEventListener("click", function(e){
self.location = "/todo/list";
},false)
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
<script>
/*Remove 버튼 처리 : form 태그 안에 action을 조정하는 방식으로 처리*/
/*객체 선택*/
const formObj = document.querySelector(".btn-danger").addEventListener("click", function (e) {
e.preventDefault() /*기본적으로 정의된 이벤트를 작동하지 못하도록 막는 메서드*/
e.stopPropagation() /*DOM 특징으로 부모와 자식간에 이벤트가 전파현상 버블링과 캡처링이라는 현상을 방지하기 위함*/
formObj.action ="/todo/remove"
formObj.method="post"
formObj.submit(), false);
</script>
@PostMapping("/remove")
public String remove(Long tno, RedirectAttributes redirectAttributes){
log.info("---remove-process-------");
log.info("tno"+tno);
todoService.remove(tno);
return "redirect:/todo/list";
}
**//delete
void delete(Long tno);**
<delete id="delete">
DELETE FROM tb1_todo WHERE tno = #{tno}
</delete>
void remove(Long tno);
@Override
public void remove(Long tno) {
todoMapper.delete(tno);
}
void update(TodoVO todoVO);
<update id="update">
update tb1_todo set title=#{title}, dueDate=#{dueDate}, finished=#{finished} where tno=${tno}
</update>
void modify(TodoDTO todoDTO);
@Override
public void modify(TodoDTO todoDTO) {
TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
todoMapper.update(todoVO);
}
package com.multicampus.springex.formatter;
import org.springframework.format.Formatter;
import java.text.ParseException;
import java.util.Locale;
public class CheckboxFormatter implements Formatter<Boolean> {
@Override
public Boolean parse(String text, Locale locale) throws ParseException {
if(text==null){
return false;
}
return text.equals("on");
}
@Override
public String print(Boolean object, Locale locale) {
return object.toString();
}
}
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<!--빈을 넣어줌-->
<set>
<bean class="com.multicampus.springex.formatter.LocalDateFormatter"/>
<bean class="com.multicampus.springex.formatter.CheckboxFormatter"/>
</set>
</property>
</bean>
@PostMapping("/modify")
public String modify(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes){
if(bindingResult.hasErrors()){
log.info("has..error");
redirectAttributes.addFlashAttribute("error", bindingResult.getAllErrors());
redirectAttributes.addAttribute("tno", todoDTO.getTno());
return "redirect:/todo/modify";
}
log.info(todoDTO);
todoService.modify(todoDTO);
return "redirect:/todo/list";
}
<script>
const serverValidResult = {}
<c:forEach items="${errors}" var="error">
serverValidResult['${error.getField()}'] = '${error.defaultMessage}'
</c:forEach>
console.log(serverValidResult)
</script>
<script>
/*Remove 버튼 처리 : form 태그 안에 action을 조정하는 방식으로 처리*/
/*객체 선택*/
const formObj = document.querySelector("form")
document.querySelector(".btn-danger").addEventListener("click", function (e) {
e.preventDefault() /*기본적으로 정의된 이벤트를 작동하지 못하도록 막는 메서드*/
e.stopPropagation() /*DOM 특징으로 부모와 자식간에 이벤트가 전파현상 버블링과 캡처링이라는 현상을 방지하기 위함*/
formObj.action ="/todo/remove"
formObj.method="post"
formObj.submit()
}, false);
document.querySelector(".btn-primary").addEventListener("click", function (e) {
e.preventDefault()
e.stopPropagation()
formObj.action="/todo/modify"
formObj.method ="post"
formObj.submit()
}, false);
document.querySelector(".btn-secondary").addEventListener("click", function(e){
self.location = "/todo/list";
},false)
</script>
document.querySelector(".btn-secondary").addEventListener("click", function(e){
self.location = "/todo/list";
},false)
limit
처리 이용💫 페이지를 보여주는 작업은 다음과 같은 과정을 통해서 진행
- 브라우저 주소창에서 페이지 번호를 전달해서 결과를 확인하는 단계
- jsp에서 페이지 번호를 출력하는 단계
- 각 페이지 번호에
클릭 이벤트
처리- 전체 데이터 개수를 반영해서
페이지 번호
조절
order by
를 이용하는 작업을 가능하면 하지 말아야 한다./* + INDEX_DESC(tb1_board pk_board) */
)는 옵티마이저에게 인덱스 힌트를 제공하여 특정 인덱스를 사용하도록 유도 pk인 pk_board
를 이용해서 접근 한 점식별자
의 의미와 인덱스
의 의미를 가진다.ROWID
라는 존재힌트(hint)
사용💫 페이지가 크게 필요로 하는 것
- 현재 페이지 번호(page)
- 이전과 다음으로 이동 가능한 링크의 표시 여부(prev, next)
- 화면에 보여지는 페이지의 시작 번호와 끝 번호(startPage, endPage)
insert into tb1_todo(title, dueDate, writer) (select title, dueDate, writer from tb1_todo);
select * from tb1_todo;
select count(*) from tb1_todo;
select * from tb1_todo order by tno desc limit 10; /*최근글목록에서 최근글부터 10개를 가지고 옴*/
select * from tb1_todo order by tno desc limit 10, 10; /*10개를 스킵해서 10개를 가지고 옴*/
select * from tb1_todo order by tno desc; /*500씩 나눠짐*/
package com.multicampus.springex.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Positive;
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
@Builder.Default
@Min(value=1)
@Positive
private int page = 1; //page number 페이지 번호
@Builder.Default// 글을 10개씩 가지고 옴
@Min(value = 10)
@Max(value=100)
@Positive
private int size = 10; //1 page per data number 한 페이지당 개수
public int getSkip(){
return (page-1)*10;
}
}
// 한 페이지씩 처리하는 작업 필요
List<TodoVO> selectList(PageRequestDTO pageRequestDTO);
<!--MyBATIS 경우 기본적으로 getXXX, setXXX을 통해 동작하므로, #{skip}, #{size}-->
<select id="selectList" resultType="com.multicampus.springex.domain.TodoVO">
select * from tb1_todo order by tno desc limit #{skip}, #{size}
</select>
@Test
public void testSelectList(){
PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
.page(1).size(10).build();
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
voList.forEach(vo->log.info(vo));
}
int getCount(PageRequestDTO pageRequestDTO);
<!--getCount 연결해주기, 전체 개수 반환-->
<select id="getCount" resultType="int">
select count(tno) from tb1_todo
</select>
package com.multicampus.springex.dto;
import lombok.Builder;
import java.util.List;
// 어떤 형태를 담을지 지정한해둠 E => 아무거나 담을 수 있는 확장성
public class PageResponseDTO<E> {
private int page;
private int size;
private int total;
// 시작 페이지 번호
private int start;
//끝 페이지 번호
private int end;
// 이전 페이지 존재의 여부
private boolean prev;
// 다음 페이지 존재의 여부
private boolean next;
// 전체 list 값을 담음
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;
this.end = (int)(Math.ceil(this.page / 10.0 )) * 10;
this.start = this.end - 9;
int last = (int)(Math.ceil((total/(double)size)));
this.end = end > last ? last: end;
this.prev = this.start > 1;
this.next = total > this.end * this.size;
}
// 페이지 번호의 계산
// 현재 페이지 번호 (page)
// 화면에 10개씩 페이지 번호를 출력 page 1 => 시작 1, 마지막 10
// if. page 10인 경우 : 시작 1, 마지막 10
// if page 11인 경우 : 시작 11, 마지막 20
// 올림하기 1/10 => ceil(0.1) => 올림 1 * 10 => 10
// 1. 마지막 페이지 구하기
/*this.end = (int)(Math.ceil(this.page / 10.0)) * 10;
2. 시작 페이지
this.start = this.end - 9;
3. 전체 개수 123/10.0 => 12.3 => 올림 페이지 개수 13개 만들면 됨
this.last = int(Math.ceil(total/(double)size))
*/
}
package com.multicampus.springex.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Positive;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.Arrays;
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
@Builder.Default
@Min(value=1)
@Positive
private int page = 1; //page number 페이지 번호
@Builder.Default// 글을 10개씩 가지고 옴
@Min(value = 10)
@Max(value=100)
@Positive
private int size = 10; //1 page per data number 한 페이지당 개수
private String link;
private String[] types;
private String keyword;
private boolean finished;
private LocalDate from;
private LocalDate to;
public int getSkip(){
return (page-1)*10;
}
public String getLink() {
StringBuilder builder = new StringBuilder();
builder.append("page=" + this.page);
builder.append("&size=" + this.size);
if(finished){
builder.append("&finished=on");
}
if(types != null && types.length > 0){
for (int i = 0; i < types.length ; i++) {
builder.append("&types=" + types[i]);
}
}
if(keyword != null){
try {
builder.append("&keyword=" + URLEncoder.encode(keyword,"UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
if(from != null){
builder.append("&from=" + from.toString());
}
if(to != null){
builder.append("&to=" + to.toString());
}
return builder.toString();
}
public boolean checkType(String type){
if(types == null || types.length == 0){
return false;
}
return Arrays.stream(types).anyMatch(type::equals);
}
}
//List<TodoDTO> getAll();
PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO);
@Override
public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
List<TodoDTO> dtoList = voList.stream()
.map(vo -> modelMapper.map(vo, TodoDTO.class))
.collect(Collectors.toList());
int total = todoMapper.getCount(pageRequestDTO);
PageResponseDTO<TodoDTO> pageResponseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.total(total)
.pageRequestDTO(pageRequestDTO)
.build();
return pageResponseDTO;
}
@Test
public void testPaging(){
PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
.page(3).size(10).build();
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
voList.forEach(vo-> log.info(vo));
}
예습부분
@RequestMapping("/list") //localhost:8090/todo/list
public void list(@Valid PageRequestDTO pageRequestDTO, BindingResult bindingResult, Model model){
log.info("todo_list");
// TodoService에서 리턴한 List<TodoDTO> getAll();을 model에다가 담기
//model.addAttribute("dtoList", todoService.getList());
//model 'dtoList' 이름으로 목록 데이터가 담겨있다. => list.jsp가 처리해줘야 함
if(bindingResult.hasErrors()){
pageRequestDTO = PageRequestDTO.builder().build();
}
model.addAttribute("responseDTO", todoService.getList(pageRequestDTO));
}
<tbody>
<%--responseDTO의 dtoList를 가져올 것이니--%>
<c:forEach items="${responseDTO.dtoList}" var="dto">
<tr>
<th scope="row"><c:out value="${dto.tno}"/></th>
<td>
<a href="/todo/read?tno=${dto.tno}" class="text-decoration-none" data-tno="${dto.tno}" >
<c:out value="${dto.title}"/></a>
</td>
<td><c:out value="${dto.writer}"/></td>
<td><c:out value="${dto.dueDate}"/></td>
<td><c:out value="${dto.finished}"/></td>
</tr>
</c:forEach>
</tbody>
</table>
<div class="float-end">
<ul class="pagination flex-wrap">
<c:if test ="${responseDTO.prev}">
<li class="page-item">
<a class="page-link" data-num="${responseDTO.start-1}">Previous</a>
</li>
</c:if>
<c:forEach begin="${responseDTO.start}" end="${responseDTO.end}" var="num">
<li class="page-item${responseDTO.page == num? "active":""}">
<a class="page-link" data-num="${num}">${num}</a></li>
</li>
</c:forEach>
<c:if test="${responseDTO.next}">
<li class="page-item">
<a class="page-link" data-num="${responseDTO.end +1}">Next</a>
</li>
</c:if>
</ul>
</div>
</div>
<script>
document.querySelector(".pagination").addEventListener("click", function (e) {
e.preventDefault()
e.stopPropagation()
const target = e.target
if(target.tagName !== 'A') {
return
}
const num = target.getAttribute("data-num")
self.location =`/todo/list?page=\${num}`
},false)
</script>
오늘은 복습을 많이 못했다. 다음주 휴강 때 이부분은 보충하자. 그리고 부트 스트랩 이용방법도 알아겠다 다음주에 쉴 때 이용해볼까 고민중
그리고 뭔가 역시 실력은 부족하나 나는 정보를 나누고 나눔받는 게 좋아서 개발이 좋은 것도 있는 것 같다. 물론 이런 협업은 개발자가 아니라도 다른 직업도 있지만 내가 구현해내고 조금 더 나은 방법이 있으면 그것을 다른 사람과 공유하여 학습하고 내가 조금 더 나은 방법이 있으면 제안하고 조율해 나가는 게 좋다.
오늘 강사님이 임시로 조를 짜줬는데 나쁘지 않은 느낌이다. 소통도 잘된다. 그리고 스터디 팀원 분들과도 서로 안되는 코드 내가 공유해주기도 공유받기도 하는 데 뿌듯하면서 짜릿하다. 짜릿해