1.  목록 데이터를 위한 DTO와 서비스 계층

🚀  TodoMapper에서 TodoVO의 목록과 전체 데이터의 수를 가져온다면 이를 서비스 계층에서 한 번에 담아서 처리하도록 DTO를 구성하는 것이 좋음
🚀  작성하려는 DTO는 PageResponseDTO라는 이름을 생성하고 다음과 같은 데이터와 가능을 가지도록 구성

  •  TodoDTO의 목록
  •  전체 데이터의 수
  •  페이지 번호 처리를 위한 데이터들 (시작 페이지 번호 / 끝 페이지 번호)

🚀  화면상에서 페이지 번호들을 출력하려면 현재 페이지 번호 page 와 페이지당 데이터의 수 size를 이용해서 계산할 필요가 있음
        ➡️  이 때문에 작성하려는 PageResponseDTO는 생성자를 통해서 필요한 page나 size 등을 전달받도록 구성

 

PageResponseDTO가 가져야 하는 데이터를 정리해서 클래스를 구성
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;

    private List<E> dtoList;
}

 

  ✓  제네릭을 이용하는 이유는 나중에 다른 종류의 객체를 이용해서 PageResponseDTO를 구성할 수 있도록 하기 위함
       예를 들어 게시판이나 회원 정보 등도 페이징 처리가 필요할 수 있기 때문에 공통적인 처리를 위해서 제네릭으로 구성
 

   ✓  PageResponseDTO는 여러 정보를 생성자를 이용해서 받아서 처리하는 것이 안전
        예를 들어 PageRequestDTO 에 있는 page, size 값이 필요하고, TodoDTO 목록 데이터와 전체 데이터의 개수도 필요
 

PageResponseDTO의 생성자는 Lombok의 @Builder를 적용
public class PageResponseDTO<E> {
    @Builder(builderMethodName = "withAll")
    public PageResponseDTO(PageRequestDTO pageRequestDTO, List<E> dtoList, int total) {
        log.info(pageRequestDTO);
        this.page = pageRequestDTO.getPage();
        this.size = pageRequestDTO.getSize();

        this.total = total;
        this.dtoList = dtoList;
}

 


1) 페이지 번호 계산

페이지 번호를 계산하려면 우선 현재 페이지의 번호 page 가 필요.
화면에 10개의 페이지 번호를 출력한다고 했을 때 다음과 같은 경우들이 생길수 있음.

  •  현재 page가 1인 경우 : 시작 페이지 start는 1, 마지막 페이지 end는 10
  •  현재 page가 10인 경우 : 시작 페이지 start는 1, 마지막 페이지 end는 10
  •  현재 page가 11인 경우 : 시작 페이지 start는 11, 마지막 페이지 end는 20

 

A.  마지막 페이지 / 시작 페이지 번호의 계산


📍 end는 현재 페이지 번호를 기준으로 계산

this.end = (int)(Math.ceil(this.page/10.0)) * 10;

 

  👾  page를 10으로 나눈 값을 올림처리 한 후 * 10
         1 / 10 => 0.1 => 1 => 10
         11 / 10.0 => 1.1 => 2 => 20
         10 / 10 => 1.0 => 1 => 10

 

📍 시작 페이지 start의 경우 계산한 마지막 페이지에서 9를 빼면 됨

this.start = this.end - 9;

 

시작 페이지의 구성은 끝났지만 마지막 페이지의 경우 다시 전체 개수 total를 고려해야 함.
만일 10개씩 (size) 보여주는 경우 전체 개수 total가 75라면 마지막 페이지는 10이 아니라 8이 되어야 함.

int last = (int)(Math.ceil(total/(double)size));

 

     123 / 10.0 => 12.3 => 13
     100 / 10.0 => 10.0 => 10
     75 / 10.0 => 7.5 => 8

 

📍 마지막 페이지 end는 앞에서 구한 last 값보다 큰 경우에 last 값이 end가 되어야만 함

this.end = end > last ? last : end;

B. 이전 prev / 다음 next 의 계산


이전 prev 페이지의 존재 여부는 시작 페이지 start가 1이 아니라면 무조건 true
다음 next은 마지막 페이지 end 와 페이지당 개수 size를 곱한 값보다 전체 개수 total가 더 많은지 보고 판단

this.prev = this.start > 1;
this.next = total > this.end * this.size;

 

PageResponseDTO는 최종적으로 Lombok의 @Getter를 적용


2)  TodoService / TodoServiceImpl

TodoService와 TodoServiceImpl에서는 작성된 PageResponseDTO를 반환타입으로 지정해서 getList()를 구성

public interface TodoService {
    void register(TodoDTO todoDTO);

    //List<TodoDTO> getAll();

    PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO);

    TodoDTO getOne(Long tno);

    void remove(Long tno);

    void modify(TodoDTO todoDTO);
}
 @Override
    public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
        List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
        List<TodoDTO> dtoList = new ArrayList<>();
        for(TodoVO todoVO : voList) {
            dtoList.add(modelMapper.map(todoVO, TodoDTO.class));
        }

        int total = todoMapper.getCount(pageRequestDTO);

        PageResponseDTO<TodoDTO> pageResponseDTO = PageResponseDTO.<TodoDTO>withAll()
                .dtoList(dtoList)
                .total(total)
                .pageRequestDTO(pageRequestDTO)
                .build();
        return pageResponseDTO;
    }

 

TodoService의 getList()는 테스트를 통해서 결과를 확인
  @Test
    public void pageingTest() {
        PageRequestDTO pageRequestDTO = PageRequestDTO.builder().page(1).size(10).build();
        PageResponseDTO<TodoDTO> pageResponseDTO = todoService.getList(pageRequestDTO);
        log.info(pageResponseDTO);
        for (TodoDTO todoDTO : pageResponseDTO.getDtoList()) {
            log.info(todoDTO);
        }
    }

 


3)  TodoController와 JSP처리

TodoController의 list()에서는 PageRequestDTO를 파라미터로 처리하고,

Model에 PageResponseDTO의 데이터를 담을 수 있도록 변경

@RequestMapping("/list")
    public void list(@Valid PageRequestDTO pageRequestDTO, BindingResult bindingResult, Model model) {
        log.info(pageRequestDTO);

        if(bindingResult.hasErrors()) {
            pageRequestDTO = pageRequestDTO.builder().build();
        }
        model.addAttribute("responseDTO", todoService.getList(pageRequestDTO));
    }

 

  ⚡️  list()는 @Valid를 이용해서 잘못된 파라미터 값들이 들어오는 경우 page는 1, size는 10으로 고정된 값을 처리하도록 구성
  ⚡️  기존과 달리 Model에 responseDTO라는 이름으로 PageResponseDTO를 담아 주었기 때문에 list.jsp를 수정

<c:forEach var="dto" items="${responseDTO.dtoList}">
    <tr>
        <th scope="row">${dto.tno}</th>
            <td><a href="/todo/read?tno=${dto.tno}" class="text-decoration-none">
                <c:out value="${dto.title}"/>
            </a></td>
            <td>${dto.writer}</td>
            <td>${dto.dueDate}</td>
            <td>${dto.finished}</td>
    </tr>
</c:forEach>

 

  ✓  프로젝트를 실행하고 브라우저에서 "/todo/list"를 호출했을 때 1페이지에 해당하는 데이터들이 출력되는 것을 확인


4)  페이지 이동 확인

화면을 추가로 개발하기 전에 "/todo/list?page=xx&size=xx"를 호출해서 결과가 정상적으로 처리되는지 확인.
page의 경우는 음수가 될 수 없고, size는 100을 넘을 수 없음.


A.  화면에 페이지 이동을 위한 번호 출력


브라우저를 통해서 페이지의 이동에 문제가 없다는 것을 확인했다면 화면 아래쪽에 페이지 번호들을 출력하도록 구성.
페이지 번호는 부트스트랩의 pagination이라는 컴포넌트를 적용.

 

list.jsp에 <table>태그가 끝난 후에 <div>를 구성해서 화면에 적용
<div class="float-end">
    <ul class="pagination flex-wrap">
        <c:forEach var="num" begin="${responseDTO.start}" end="${responseDTO.end}">
            <li class="page-item"><a class="page-link" href="#">${num}</a></li>
        </c:forEach>
    </ul>    
</div>

 

  ✓  브라우저에서 page를 변경시켜 페이지 번호들이 변경되는지 확인

 


B.  화면에서 prev / next / 현재 페이지


페이지 번호들이 정상적으로 출력된다면 "이전 / 다음"을 처리

<div class="float-end">
    <ul class="pagination flex-wrap">
        <c:if test="${responseDTO.prev}">
            <li class="page-item"><a class="page-link">Previous</a></li>
        </c:if>
        
        <c:forEach var="num" begin="${responseDTO.start}" end="${responseDTO.end}">
            <li class="page-item"><a class="page-link" href="#">${num}</a></li>
        </c:forEach>
                                
        <c:if test="${responseDTO.next}">
            <li class="page-item"><a class="page-link">Next</a></li>
        </c:if>
    </ul>
</div>

 

  ⚡️  11페이지 이상되었을 때 Previous 버튼이 보임

  ⚡️  1 ~ 10 페이지의 경우 Previous 버튼이 보이지 않음

📍 현재 페이지의 번호는 class 속성에 "active"라는 속성값이 추가되어야 함. 삼항 연산자를 이용

<c:forEach var="num" begin="${responseDTO.start}" end="${responseDTO.end}">
    <li class="page-item" ${responseDTO.page == num ? "active" : ""}>
    <a class="page-link" href="#">${num}</a></li>
</c:forEach>

 

 


C.  페이지의 이벤트 처리


👾  화면에서 페이지 번호를 누르면 이동하는 처리는 자바스크립트를 이용해서 처리
👾  화면의 페이지 번호를 의미하는 <a> 태그에 직접 "onclick"을 적용할 수도 있지만, 한 번에 <ul> 태그에 이벤트를 이용해서 처리
👾  우선은 각 페이지 번호에 적절한 페이지 번호를 가지도록 구성. 이때는 "data-"속성을 이용해서 필요한 속성을 추가해주는 방식을 사용

 

 "data-num"이라는 속성을 추가해서 페이지 번호를 보관하도록 구성
<c:if test="${responseDTO.prev}">
    <li class="page-item">
        <a class="page-link" data-num="${reponseDTO.start-1}">Previous</a>
    </li>
</c:if>

<c:forEach var="num" begin="${responseDTO.start}" end="${responseDTO.end}">
    <li class="page-item" ${responseDTO.page == num ? "active" : ""}>
    <a class="page-link" data-num="${num}">${num}</a></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> 태그가 끝난 부분에 이벤트 처리 추가
 <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}`; 
    });
</script>

 

  ⚡️  자바 스크립트의 이벤트 처리는 <ul> 태그에 이벤트를 등록하고 <a> 태그를 클릭했을 때만 data-num 속성값을 읽어와서 현재 주소(self.location)를 변경하는 방식으로 작성
  ⚡️  자바 스크립트에서 백틱(`)을 이용하면 문자열 결합에 '+'를 이용해야 하는 불편함을 줄일 수 있음. 대신에 JSP의 EL이 아니라는 것을 표시하기 위하여 \${}로 처리
  ⚡️  자바 스크립트 처리가 완료되면 화면상의 페이지 번호를 클릭해서 페이지 이동이 가능한지 확인

 


D.  조회 페이지로 이동


목록 페이지는 특정한 Todo의 제목 title을 눌러서 조회 페이지로 이동하는 기능이 존재
기존에는 단순히 tno만을 전달해서 '/todo/read?tno=33'과 같은 방식으로 이동했지만, 페이지 번호가 붙을 때는 page와 size 등을 같이 전달해 주어야만 조회 페이지에서 다시 목록으로 이동할 때 기존 페이지을 볼 수 있게 됨
이를 위해 list.jsp에는 각 Todo의 링크 처리 부분을 수정할 필요가 있음

 

페이지 이동 정보는 PageRequestDTO 안에 있으므로 PageRequestDTO 내부에 간단한 메서드를 작성해서 필요한 링크를 생성해서 사용. (파라미터로 전달되는 PageRequestDTO는 Model로 자동 전달되기 때문에 별도의 처리가 필요하지 않음)

 

PageRequestDTO에 link라는 속성을 추가하고 getLink()를 추가해서 GET 방식으로 페이지 이동에 필요한 링크들을 생성
private String link;

public String getLink() {
        if (link == null) {
            StringBuilder builder = new StringBuilder();
            builder.append("page=" + this.page);
            builder.append("&size=" + this.size);
            link = builder.toString();
        }
        return link;
}

 

list.jsp 수정
<c:forEach var="dto" items="${responseDTO.dtoList}">
    <tr>
        <th scope="row">${dto.tno}</th>
        <td><a href="/todo/read?tno=${dto.tno}&${pageRequestDTO.link}" 
            class="text-decoration-none">
            <c:out value="${dto.title}"/>
        </a></td>
        <td>${dto.writer}</td>
        <td>${dto.dueDate}</td>
        <td>${dto.finished}</td>
    </tr>
</c:forEach>

 

  ⚡️ 기존의 tno=xx 뒤에 &를 추가하고 PageRequestDTO의 getLink()의 결과인 문자열을 생성하게 되면 다음과 같이 기존 링크에
page와 size가 추가된 형태가 됨

<a href="/todo/read?tno-3579&page=1&size=10" class="text-decoration-none" data-tno=357>

 


E. 조회에서 목록으로

 

조회 화면에서는 기존과 달리 PageRequestDTO를 추가로 이용하도록 TodoController를 수정

 

TodoController의 read 메소드는 PageRequestDTO 파라미터를 추가해서 수정
 @GetMapping({"/read", "/modify"})
    public void read(Long tno, PageRequestDTO pageRequestDTO, Model model) {
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        model.addAttribute("dto", todoDTO);
    }

 

read.jsp에서는 List 버튼의 링크를 다시 처리
document.querySelector('.btn-secondary').addEventListener('click', function (e) {
    self.location = "/todo/list?${pageRequestDTO.link}";
}, false);

 

  👾  브라우저는 특정한 페이지에서 조회 페이지로 이동해서 List 버튼을 눌렀을 때 정상적으로 이동하는지 확인

 


F.  조회에서 수정으로


조회 화면에서 수정 화면으로 이동할 때도 현재 페이지 정보를 유지해야 하기 때문에 read.jsp에서는 링크 처리 부분을 수정

document.querySelector('.btn-primary').addEventListener('click', function (e) {
    self.location = `/todo/modify?tno=${dto.tno}&${pageRequestDTO.link}`;
}, false);

 

  👾  화면에서 Modify 버튼을 누르면 아래와 같이 동작

 


G.  수정화면에서의 링크 처리


수정 화면에서도 다시 목록으로 가는 링크가 필요. TodoController의 read() 메서드는 GET 방식으로 동작하는 '/todo/modify'에도 동일하게 처리되므로 JSP에서 PageRequestDTO를 사용할 수 있음.

 

modify.jsp 의 List 버튼을 누르는 자바 스크립트의 이벤트 부분을 변경
document.querySelector('.btn-secondary').addEventListener('click', function (e) {
    self.location = `/todo/list?${pageRequestDTO.link}`;
});

 


H.  수정 / 삭제 처리 후 페이지 이동


실제 수정 / 삭제 작업은 POST 방식으로 처리되고 삭제 처리가 된 후에는 다시 목록으로 이동할 필요가 있음.
그렇기 때문에 수정 화면에서 <form> 태그로 데이터를 전송할 때 페이지와 관련된 정보를 같이 추가해서 전달해야 함.

 

modify.jsp에 <input type="hidden">을 이용해서 추가
<form action="/todo/modify" method="post">
    <input type="hidden" name="page" value="${pageRequestDTO.page}">
    <input type="hidden" name="size" value="${pageRequestDTO.size}">

 

 

TodoController에서 POST 방식으로 이루어지는 삭제 처리에도 PageRequestDTO를 이용해서 <form> 태그로 전송되는 태그들을 수집하고 수정 후에 목록 페이지로 이동할 때 page는 무조건 1페이지로 이동해서 size 정보를 활용

@PostMapping("/remove")
    public String remove(Long tno, PageRequestDTO pageRequestDTO , RedirectAttributes redirectAttributes) {
        log.info("-----remove----");
        log.info("tno: " + tno);

        todoService.remove(tno);
        redirectAttributes.addAttribute("page", 1);
        redirectAttributes.addAttribute("size", pageRequestDTO.getSize());
        return "redirect:/todo/list";
    }

 

  👾  브라우저는 목록에서 특정한 Todo를 조회 -> 수정 / 삭제 화면 -> 삭제 후 이동이 정상적으로 이루어지는지 확인

 


I.  수정 처리 후 이동


Todo를 수정한 후에 목록으로 이동할 때는 페이지 정보를 이용해야 하므로 TodoController의 modify() 에서는 PageRequestDTO를 받아서 처리하도록 변경.

    @PostMapping("/modify")
    public String midify(PageRequestDTO pageRequestDTO,
                         @Valid TodoDTO todoDTO,
                         BindingResult bindingResult,
                         RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
            log.info("has error");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            redirectAttributes.addAttribute("tno", todoDTO.getTno());
            return "redirect:/todo/mofidy";
        }
        log.info(todoDTO);
        todoService.modify(todoDTO);
        
        redirectAttributes.addAttribute("page", pageRequestDTO.getPage());
        redirectAttributes.addAttribute("size", pageRequestDTO.getSize());
        
        return "redirect:/todo/list";
    }

 

  👾  정상적으로 수정된 후에는 '/todo/list'로 이동할 때 필요한 page나 size를 유지할 수 있도록 구성

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  Todo의 삭제 기능 개발

수정과 삭제는 GET 방식으로 조회한 후에 POST 방식으로 처리
사실상 GET 방식의 내용은 조회 화면과 같지만 스프링 MVC에는 여러 개의 경로를 배열과 같은 표기법을 이용해서 하나의 @GetMapping으로 처리할 수 있기 때문에 read() 기능을 수정해서 수정과 삭제에도 같은 메서드를 이용하도록 작업

 

TodoController의 read()를 다음과 같이 수정해서 "/todo/modify?tno=xxx"의 경로를 처리하도록 수정
 @GetMapping({"/read", "/modify"})
    public void read(Long tno, Model model) {
        // 1) request로 전달 받은 tno를 서비스에 전달해서 2)TodoDTO를 반환받아서 3)View 에 전달
        TodoDTO todoDTO = todoService.getOne(tno);
        log.info(todoDTO);
        model.addAttribute("dto", todoDTO);
    }

 

 

/WEB-INF/view/todo 폴더에 있는 read.jsp를 그대로 복사해서 modify.jsp를 구성


  👾  modify.jsp에서는 수정과 삭제 작업이 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="${dto.tno}" readonly>
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Title</span>
        <input type="text" name="title" class="form-control" value="${dto.title}">
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">DueDate</span>
        <input type="date" name="dueDate" class="form-control" value="${dto.dueDate}">
    </div>
    <div class="input-group mb-3">
        <span class="input-group-text">Writer</span>
        <input type="text" name="writer" class="form-control" value="${dto.writer}" readonly>
    </div>
    <div class="form-check">
        <label class="form-check-label">
            Finished &nbsp;
        </label>
        <input type="checkbox" name="finished"
            class="form-check-input" ${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>

 

 

 

 ✓  제목 / 만료일 / 완료는 수정이 가능하도록 수정
  ✓  화면 아래 버튼은 삭제, 수정, 목록 버튼이 추가
  ✓  브라우저를 통해 "/todo/modify?tno=xx"와 같은 경로로 화면이 나오는 것 확인

 

 

 

 

 

 

 


 

1)  Remove 버튼의 처리


Remove 버튼의 클릭은 자바 스크립트를 이용해서 <form> 태그의 action을 조정하는 방식으로 동작하게 구성

<script>
    const frmModify = document.querySelector('form');
    
    document.querySelector('.btn-danger').addEventListener('click', function () {
        frmModify.action = '/todo/remove';
        frmModify.method = 'post';
        frmModify.submit();
    });
</script>

 

  ✓  Remove 버튼은 class 속성이 "btn-danger"이므로 이를 이용해서 클릭 이벤트를 처리

 

TodoController에는 POST 방식으로 동작하는 remove() 메서드를 설계
    @PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes) {
        log.info("-----remove----");
        log.info("tno: " + tno);

        todoService.remove(tno);
        return "redirect:/todo/list";
    }

 

  ✓  우선 tno 파라미터가 정상적으로 전달되는지 확인하고 목록으로 이동하도록 구성

  ✓  Remove 버튼을 누르면 다음과 같은 로그 출력 확인


 

2) TodoMapper와 TodoService 처리

TodoMapper에는 delete() 메서드를 추가하고 TodoMapper.xml에는 sql을 추가
public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);
}
    <delete id="delete">
        DELETE FROM tbl_todo WHERE tno = #{tno}
    </delete>

 

test 코드 작성
  @Test
    public void testDelete() {
        // 1) tno로 데이터를 반환해서 정상 출력 확인 2) 삭제 3) 다시 tno로 데이터를 반환해서 삭제 확인.
        Long tno = 2L;
        TodoVO todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);

        todoMapper.delete(tno);

        todoVO = todoMapper.selectOne(tno);
        log.info(todoVO);

    }

 

TodoServce / TodoServiceImpl에는 remove() 메서드를 작성
public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);
}
    @Override
    public void remove(Long tno) {
        todoMapper.delete(tno);
    }

 

 

TodoController 에서 TodoService의 remove()를 호출하는 코드를 추가
    @PostMapping("/remove")
    public String remove(Long tno, RedirectAttributes redirectAttributes) {
        log.info("-----remove----");
        log.info("tno: " + tno);

        todoService.remove(tno);
        return "redirect:/todo/list";
    }

 

  ✓  브라우저를 통해서 특정한 번호를 가진 게시물이 삭제 되는지 확인

 


2.  Todo의 수정 기능 개발

Todo의 수정 기능은 수정이 가능한 항목들만 변경되어야 하므로 SQL이 복잡해짐

 

TodoMapper에 메서드 추가
public interface TodoMapper {
    String getTime();

    void insert(TodoVO todoVO);

    List<TodoVO> selectAll();

    TodoVO selectOne(Long tno);

    void delete(Long tno);

    void update(TodoVO todoVO);
}

 

TodoMapper.xml 코드 추가
<update id="update">
    UPDATE tbl_todo SET title = #{title}, dueDate = #{dueDate}, 
        finished = #{finished} WHERE tno = #{tno}
</update>

 

test 코드 추가
@Test
    public void testUpdate() {
        Long tno = 12L;
        TodoVO todoVO = TodoVO.builder()
                .tno(tno)
                .title("수정")
                .dueDate(LocalDate.parse("2024-4-26"))
                .finished(true)
                .build();

        todoMapper.update(todoVO);
        log.info(todoMapper.selectOne(tno));
    }

 

 

TodoService / TodoServiceImpl 에서는 TodoDTO를 TodoVO로 변환해서 처리
public interface TodoService {
    void register(TodoDTO todoDTO);

    List<TodoDTO> getAll();

    TodoDTO getOne(Long tno);

    void remove(Long tno);

    void modify(TodoDTO todoDTO);
}
 @Override
    public void modify(TodoDTO todoDTO) {
        log.info("...modify()...");

        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.update(todoVO);
    }

 


1)  checkbox를 위한 Formatter


👾  수정 작업에서는 화면에서 체크박스를 이용해서 완료여부 finished (boolean) 를 처리하게 됨
👾  문제는 브라우저가 체크박스가 클릭된 상태일때 전송되는 값은 "on"이라는 값을 전달
👾  TodoDTO로 데이터를 수집할 때에는 문자열 "on"을 boolean 타입으로 처리할 수 있어야 하므로 컨트롤러에서 데이터를 수집할 때 타입을 변경해 주기 위한 CheckboxFormatter 를 formatter 패키지에 추가해서 개발

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();
    }
}

 

servlet-context.xml에 format 라이브러리 추가
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="com.example.spring_ex_01_2404.controller.formatter.LocalDateFormatter"/>
            <bean class="com.example.spring_ex_01_2404.controller.formatter.CheckBoxFormatter"/>
        </set>
    </property>
</bean>

 


2)  TodoController의 modify()

@PostMapping("/modify")
    public String midify(@Valid TodoDTO todoDTO,
                         BindingResult bindingResult,
                         RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
        // 유효성 검사 결과 에러가 있으면 수정 페이지로 돌아감
            log.info("has error");
            
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            redirectAttributes.addAttribute("tno", todoDTO.getTno());
            return "redirect:/todo/mofidy";
        }
        log.info(todoDTO);
        todoService.modify(todoDTO);
        return "redirect:/todo/list";
    }

 

  ✓  @Valid를 이용해서 필요한 내용들을 검증하고 문제가 있는 경우에는 다시 "/todo/modify"로 이동시키는 방식을 사용
  ✓  "/todo/modify"로 이동할 때는 tno 파라미터가 필요하기 때문에 RedirectAttributes의 addAttribute을 이용

 

/WEB-INF/view/todo/modify.jsp 에 검증된 정보를 처리하는 코드를 추가


  👾  <form> 태그가 끝난 후에는 <script> 태그를 이용

<script>
    const serverValidResult = {};
    <c:forEach items="${errors}" var="error">
    serverValidResult['${error.getField()}'] = '${error.defaultMessage}';
    </c:forEach>
    console.log(serverValidResult);
</script>
<script>
    const frmModify = document.querySelector('form');
                             
    document.querySelector('.btn-danger').addEventListener('click', function () {
        frmModify.action = '/todo/remove';
        frmModify.method = 'post';
        frmModify.submit();
    });
                            
    document.querySelector('.btn-primary').addEventListener('click', function () {
        frmModify.action = '/todo/modify';
        frmModify.method = 'post';
        frmModify.submit();
    });
                           
    document.querySelector('.btn-secondary').addEventListener('click', function () {
        self.location = '/todo/list';
    });
</script>

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts