ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코드로 배우는 스프링부트 웹 프로젝트 Day 10
    Spring/코드로 배우는 스프링부트 웹 프로젝트 2022. 8. 25. 21:28

    /* Annotation들은 스프링부트 프로젝트의 Annotation 정리 페이지에 따로 정리해두었습니다. */

     

    [방명록의 수정/삭제 처리]

    계획

    • Guestbook의 수정은 Post 방식으로 처리하고 다시 수정된 결과를 확인할 수 있는 조회 화면으로 이동
    • 삭제는 Post방식으로 처리하고 목록 화면으로 이동
    • 목록을 이동하는 작업은 Get 방식으로 처리. 기존에 사용하던 페이지 번호 등을 유지해서 이동!

     

    수정과 삭제는 모두 '수정 및 삭제가 가능한 페이지'로 이동한 상태에서 수정과 삭제 중 선택을 해서 작업이 이루어짐

     

    이를 구현하기 위해 GuestbookController에서는 조회와 비슷하게 Get방식으로 진입하는 'gusetbook/modify'를 기존의 read()에 어노테이션의 값을 변경해서 처리

    @GetMapping({"/read", "/modify"})
    public void read(long gno, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, Model model) {
    
        log.info("gno: " + gno);
    
        GuestbookDTO dto = service.read(gno);
    
        model.addAttribute("dto" ,dto);
    }

     

    read.html을 modify.html을 만들어서 복사.붙여넣기 한다.

     

    <h1 class="mt-4">GuestBook Modify Page</h1>
    
    <form action="/guestbook/modify" method="post">

    <h1>태그의 내용을 modify로 바꾸고, form 태그로 수정된 내용을 감싸 Post 방식으로 처리하도록 함

     

    <div class="form-group">
        <label>Gno</label>
        <input type="text" class="form-control" name="gno" th:value="${dto.gno}" readonly>
    </div>
    
    <div class="form-group">
        <label>Title</label>
        <input type="text" class="form-control" name="title" th:value="${dto.title}">
    </div>
    
    <div class="form-group">
        <label>Content</label>
        <textarea class="form-control" rows="5" name="content">[[${dto.content}]]</textarea>
    </div>
    
    <div class="form-group">
        <label>Writer</label>
        <input type="text" class="form-control" name="writer" th:value="${dto.writer}" readonly>
    </div>
    
    <div class="form-group">
        <label>RegDate</label>
        <input type="text" class="form-control" th:value="${#temporals.format(dto.regDate, 'yyy/MM/dd HH:mm:ss')}" readonly>
    </div>
    
    <div class="form-group">
        <label>ModDate</label>
        <input type="text" class="form-control" th:value="${#temporals.format(dto.modDate, 'yyyy/MM/dd HH:mm:ss')}" readonly>
    </div>
    
    </form>

    Title과 내용만 수정 가능하도록 한다. (readonly삭제)

    regDate와 modDate는 수정이 불가능할 뿐더러, JPA에서 자동 처리되므로, name속성을 없앰

     

    </form>
    
    <button type="button" class="btn btn-primary">Modify</button>
    <button type="button" class="btn btn-info">List</button>
    <button type="button" class="btn btn-danger">Remove</button>

    화면에 표시할 수정, List, 삭제용 버튼 추가

     

    위와 같이 Title과 Content만 수정 가능하도록 처리된다.

     

    수정, 삭제, 목록의 각 버튼을 클릭할 때 마다 다른 이벤트를 처리해야 한다.

    이는 <form>태그의 action속성을 통해 처리 가능하다. 해당 처리는 조금 뒤쪽에 한다.

     

    [서비스 계층에서의 수정과 삭제]

    수정과 삭제 관련 기능 추가

     

    GuestbookService 인터페이스 추가

    void remove(Long gno);
    
    void modify(GuestbookDTO dto);

     

    GuestbookServiceImpl 클래스 추가

    @Override
    public void remove(Long gno) {
        
        repository.deleteById(gno);
    }
    
    @Override
    public void modify(GuestbookDTO dto) {
        
        //업데이트 하는 항목은 '제목', '내용'
        Optional<Guestbook> result = repository.findById(dto.getGno());
        
        if(result.isPresent()) {
            
            Guestbook entity = result.get();
            
            entity.changeTitle(dto.getTitle());
            entity.changeContent(dto.getContent());
            
            repository.save(entity);
        }
    }

    remove()와 modify()를 구현

     

    [컨트롤러의 게시물 삭제]

    삭제 기능은 Post 방식으로 gno값을 전달하고, 삭제 후에는 다시 목록의 첫 페이지로 이동하는 방식이 가장 보편적임

     

    GuestbookController 클래스

    @PostMapping("/remove")
    public String remove(long gno, RedirectAttributes redirectAttributes) {
        
        log.info("gno: " + gno);
        
        service.remove(gno);
        
        redirectAttributes.addFlashAttribute("msg", gno);
        
        return "redirect:/guestbook/list";
    }

     

    [modify.html 삭제 처리]

    삭제 작업은 Get 방식으로 수정 페이지에 들어가서 '삭제' 버튼을 클릭하는 방식으로 제작

     

    modify.html 수정

    <button type="button" class="btn btn-primary modifyBtn">Modify</button>
    <button type="button" class="btn btn-info listBtn">List</button>
    <button type="button" class="btn btn-danger removeBtn">Remove</button>

    각 버튼 구별을 위해 수정

     

    <script th:inline="javascript">
        
        var actionForm = $("form"); //form 태그 객체
        
        $(".removeBtn").click(function() {
        
            actionForm
                .attr("action", "/guestbook/remove")
                .attr("method", "post");
            
            actionForm.submit();
        });
        
    </script>

     

    자바스크립트 인라인 기능을 사용해 구현

    <form> 태그의 action 속성에 값으로 /guestbook/remove 를 지정

    <form> 태그의 method 속성에 값으로 post를 지정

     

    <form> 태그는 사용자가 입력한 정보를 서버로 전달하는 역할 ↴

    <form> 태그 내에는 <input> 태그로 gno가 있기 때문에 컨트롤러에서는 여러 파라미터 중에서 gno를 추출해서 삭제 시에 이용함

     

     

    [POST 방식의 수정 처리]

     

    수정 처리는 POST 방식으로 이루어져야 함

     

    고려해야 할 점

    • 수정 시에 수정해야하는 내용(제목, 내용, 글 번호)이 전달되어야 함
    • 수정 후에는 목록 페이지로 이동하거나 조회 페이지로 이동해야 함 (이때, 가능하면 기존의 페이지 번호를 유지하는 것이 좋음)

    modify.html에는 /guestbook/read 로 이동할 때 페이지 번호가 파라미터로 전달되고 있고, 수정 페이지로 이동할 경우에도 같이 전달됨

     

    이를 이용하여 수정이 완료된 후에도 동일한 목록 및 조회 페이지를 유지할 수 있도록 page값도 <form> 태그에 추가해서 전달하도록 함

     

    modify.html

    <form action="/guestbook/modify" method="post">
        
        <!-- 페이지 번호 -->
        <input type="hidden" name="page" th:value="${requestDTO.page}">

     

    [컨트롤러의 수정 처리]

    GuestbookController에서는 Guestbook 자체의 수정과 페이징 관련 데이터 처리를 같이 진행해주어야 함

     

    GuestbookController 클래스

    @PostMapping("/modify")
    public String modify(GuestbookDTO dto, @ModelAttribute("requestDTO") PageRequestDTO requestDTO, RedirectAttributes redirectAttributes) {
    
        log.info("post modify..................................................");
        log.info("dto: " + dto);
        
        service.modify(dto);
        
        service.modify(dto);
        
        redirectAttributes.addAttribute("page", requestDTO.getPage());
        redirectAttributes.addAttribute("gno" ,dto.getGno());
        
        return "redirect:/guestbook/read";
    }

    수정해야하는 글의 정보를 가지는 GuestbookDTO, 기존의 페이지 정보를 유지하기 위한 PageRequestDTO, 리다이렉트로 이동하기 위한 RedirectAttributes 세 가지가 파라미터로 필요함

    수정을 완료하면, read 페이지로 이동 (기존 페이지 정보도 같이 유지해서 조회 페이지에서 다시 목록 페이지로 이동할 수 있도록 함)

     

     

    [수정 화면에서의 이벤트 처리]

     

    modify.html

    $(".modifyBtn").click(function() {
    
        if(!confirm("수정하시겠습니까?")) {
            return;
        }
    
        actionForm
            .attr("action", "/guestbook/modify")
            .attr("method", "post")
            .submit();
    });

    confirm을 통해 정말 수정할 것인지 재확인 후, Post 방식으로 서버에 전송

    이후, 다시 조회 페이지(read)로 이동

     

     

    [수정 화면에서 다시 목록 페이지로]

     

    목록 페이지로 이동하는 버튼 처리

    목록 페이지로 이동할 때에는 page 파라미터 외에는 별도로 필요하지 않음

        => page를 제외한 파라미터들을 지운 상태로 처리

    $(".listBtn").click(function () {
    
        var pageInfo = $("input[name='page']");
    
        actionForm.empty(); //form 태그의 모든 내용을 지움
        actionForm.append(pageInfo); //목록 페이지 이동에 필요한 내용을 다시 추가
        actionForm
            .attr("action", "/guestbook/list")
            .attr("method", "get");
    
        console.log(actionForm.html());
        actionForm.submit();
    });

     

     

    [검색 처리]

    검색 처리는 크게 서버 사이드 처리와 화면 쪽의 처리로 나눌 수 있음

     

    서버 사이드 처리

    • PageRequestDTO에 검색 타입(type)과 키워드(keyword)를 추가
    • 이하 서비스 계층에서 Querydsl을 이용해서 검색 처리

    검색 항목 분류

    • 제목, 내용, 작성자
    • 제목 or 내용
    • 제목 or 내용 or 작성자

     

    [서버측 검색 처리]

    PageRequestDTO에 검색 타입과 검색 키워드 추가

     

    PageRequestDTO 클래스

    private String type;
    private String keyword;

    type과 keyword 추가

     

     

    [서비스 계층의 검색 구현과 테스트]

     

    동적으로 검색 조건이 처리되는 경우의 실제 코딩은 Querydsl을 통해서 BooleanBuilder를 작성

    GuestbookRepository는 Querydsl로 작성된 BooleanBuilder를 findAll() 처리하는 용도로 사용

     

    BooleanBuilder는 별도의 클래스 등을 작성하여 처리할 수 있지만, 간단히 처리하기 위해 GuestbookServiceImpl에 메서드를 작성해서 처리

     

    GuestbookServiceImpl 클래스에 추가

    private BooleanBuilder getSearch(PageRequestDTO requestDTO) { //Querydsl 처리
    
        String type = requestDTO.getType();
    
        BooleanBuilder booleanBuilder = new BooleanBuilder();
    
        QGuestbook qGuestbook = QGuestbook.guestbook;
    
        String keyword = requestDTO.getKeyword();
    
        BooleanExpression expression = qGuestbook.gno.gt(0L); //gno > 0 조건만 생성
    
        booleanBuilder.and(expression);
    
        if(type == null || type.trim().length() == 0) { //검색 조건이 없는 경우
            return booleanBuilder;
        }
        
        //검색 조건 작성
        BooleanBuilder conditionBuilder = new BooleanBuilder();
        
        if (type.contains("t")) {
            conditionBuilder.or(qGuestbook.title.contains(keyword));
        }
        
        if (type.contains("c")) {
            conditionBuilder.or(qGuestbook.content.contains(keyword));
        }
        
        if (type.contains("w")) {
            conditionBuilder.or(qGuestbook.writer.contains(keyword));
        }
        
        //모든 조건 통합
        booleanBuilder.and(conditionBuilder);
        
        return booleanBuilder;
    }

    검색 조건이 있는 경우, conditionBuilder 변수를 통해 검색 조건들을 or로 연결하여 return

    검색 조건이 없는 경우, gno > 0 으로만 return

     

     

     

     

     

Designed by Tistory.