게시판 구현(2)

김지민·2023년 6월 28일

spring Boot

목록 보기
5/9

삭제,수정 버튼 존재하는 modify.html

 <form id="postModifyForm">
                    <div>
                        <label class="form-label" for="id">번호</label>
                        <input class="form-control" id="id" name="id" th:value="${post.id}" readonly/>
                    </div>
                    <div>
                        <label class="form-label" for="title">제목</label>
                        <input class="form-control" id="title" name="title" th:value="${post.title}"  />
                    </div>
                    <div>
                        <label class="form-label" for="content">내용</label>
                        <textarea class="form-control"  id="content" name="content" th:text="${post.content}" ></textarea> 
                    </div>
                     <div>
                        <label class="form-label" for="author">작성자</label>
                        <input class="form-control" id="author" th:value="${post.author}" readonly/>
                    </div>
                </form>
                <div>
                    <!-- 
                    버튼또는 input type=submit이 폼 안에 있는 경우: 무조건 submit -> get 방식(요청 주소가 ?id = {}&title ={}&content={}으로 변화)
                    ==> form에서 요청 방식을 지정하지 않았기에 get방식으로 넘어감. + action주소도 지정안함.
                    -->
                    <button class="my-2 btn btn-outline-danger" id="btnDelete" >삭제</button>
                    <button class="my-2 btn btn-outline-success" id="btnUpdate" >업데이트</button>
                </div>

삭제, 수정 기능

삭제

* button 작동 java Script 코드

// 삭제 버튼을 찾음.
    const btnDelete = document.querySelector('button#btnDelete');
    btnDelete.addEventListener('click', (e) => {

        const result = confirm(`정말 ${id}를 삭제할까요?`);

        console.log(`결과 = ${result}`)
        console.log(`id 값 = ${id}`)

        if (!result) {
            return;
        }

        // 절대 경로: '/post/delete'
        // 맨 처음 '/' 의미:
        // -> 클라이언트 포트번호 다음 주소
        // -> 서버에서는 context-root를 의미.
        form.action = '/post/delete'; // submit 요청 주소, 그전 주소: 'delete'도 가능함.
        form.method = 'post' // submit 요청 방식.
        form.submit(); // 폼 제출(submit), 요청 보내기.

    });

* controller

 // 삭제
    @PostMapping("/delete")
    public String delete(Long id) {
        log.info("delete(id = {})", id);
        
        // PostService를 이용해서 DB 테이블에서 포스트를 삭제하는 서비스 호출:
        postService.delete(id);
        
        return "redirect:/post";
    }

* service

 // id별 삭제
    public void delete(Long id) {
        log.info("delete(id = {})", id);

        postRepository.deleteById(id);
    }

수정

* button 작동 java Script 코드


// 수정 버튼을 찾음.
    const btnUpdate = document.querySelector('button#btnUpdate');
    btnUpdate.addEventListener('click', (e) => {

        // 만약 이벤트핸들러(콜백 함수) 외부에 있을 경우에는 html 문서가 완성된 상태에서 값이 들어감. 즉, 원래 있던 값, 미리 읽어둔 값이 존재.
        //-> 변경 내용을 처리할 수 없음.
        // 내부에서 변수 선언을 한 경우에는 버튼이 클릭된 당시에 값을 검사 및 읽음.
        
        // 제목을 찾음.
        const title = document.querySelector('#title').value;

        // 내용을 찾음.
        const content = document.querySelector('#content').value;
        
        if (title === '' || content === '') {
            alert('제목, 내용을 입력하세요');
            return; // 함수 종료.
        }

        const result = confirm(`${id}를 수정할까요?`)

        if (!result) {
            return;
        }
        
        form.action = 'update';
        form.method = 'post';
        form.submit();

    });

* controller

 // 수정 
    // form의 name 속성과 DTO 필드 값이 같을 경우 찾을 수 있음.
    @PostMapping("/update")
    public String update(PostUpdateDto dto) {
        log.info("update(dto={})", dto);
        
        // 포스트 업데이트 서비스 호출:
        /*
         * Post entity = postRepository.findById(dto.getId()).orElseThrow();
         * entity.update(dto);
         */
        
        postService.update(dto);
        
        // 쿼리 스트링에서는 중간에 공백이 있으면 안됨.
        return "redirect:/post/details?id=" + dto.getId();
    }

* service

// id별 수정
    // Junit Test와는 다름.
    // readOnly = false(기본값): select 과정이 늦을 수도 있음. 변경되고 있는지를 추적하고 관리하며, 변경시 DB와 연동이
    // 됨.
    // readOnly = true: 읽기만 하고, DB에 수정 변경 안됨.
    @Transactional // (1)
    public void update(PostUpdateDto dto) {
        log.info("update(dto ={})", dto);

        // (1) 메서드에 @Transactional 애너테이션을 설정하고,
        // (2) DB에서 entity를 검색하고,
        // (3) 검색한 엔터티를 수정하면,
        // 틀랙잭션이 끝나는 시점에 DB update가 자동으로 수행됨!

        // 실제로 존재하는 entity의 경우에는 오류를 날리지 않고 있다고 알림.
        Post entity = postRepository.findById(dto.getId()).orElseThrow(); // (2)
        entity.update(dto); // (3)
    }

+ Junit Test

   // @Test
    // @Transactional
    public void update() {
        // 업데이트하기 전의 엔터티 검색:
        // 검색은 리턴 결과가 존재 할수도 없을 수도 있기에 Post 타입이 아닌 Optional로 지정함. 
        // Post라는 타입을 꺼내주는 메서드: orElseThrow -값이 있으면 리턴 없으면 예외(오류)를 던져 버림.
        Post entity = postRepository.findById(61L)
                .orElseThrow();
        log.info("update 전: {}", entity);
        log.info(" update 전 수정 시간: {}", entity.getModifiedTime());
        
        // entity를 변경할 내용을 가지고 있는 객체 생성:
        PostUpdateDto dto = new PostUpdateDto();
        dto.setTitle("JPA update 테스트");
        dto.setContent("JPA Hibernate를 사용한 DB 테이블 업데이트");
        
        // entity를 수정:
        entity.update(dto);
        
        // DB 테이블 업데이트:
        // JPA에서는 insert와 update 메서드가 구분되어 있지 않음.
        // save() 메서드의 argument가 DB에 없는 entity이면 insert, DB에 있는 entity이면 update를 실행.
        // save: 커밋되는 순간이 시간 차이가 존재
        // saveAndFlush: 변경 사항 즉각 반응됨. 바로 커밋.
        postRepository.saveAndFlush(entity);
    }

검색 기능

postSearchDto class

package com.itwill.spring4.dto;

import lombok.Data;

@Data
public class PostSearchDto {

    // reqeustParameter와 동일한 이름 사용하기.
    private String type;
    private String keyword;
}

목록 read.html코드

<div class="card-footer">
                <!-- 검색은 보통 get 방식 -->
                <form method="get" th:action="@{/post/search}">
                    <div class="row">
                        <div class="col-3">
                            <select class="form-select" name="type">
                                <option value="t">제목</option>
                                <option value="c">내용</option>
                                <option value="tc">제목+내용</option>
                                <option value="a">작성자</option>
                            </select>
                        </div>
                        <div class="col-8">
                            <input class="form-control" name="keyword" type="text" placeholder="검색어 입력" required/>
                        </div>
                        <div class="col-1">
                            <input class="form-control btn btn-outline-dark" type="submit" value="검색" />
                        </div>
                    </div>
                </form>
            </div>

controller

@GetMapping("/search")
    public String search(PostSearchDto dto, Model model) {
        log.info("search(dto = {})",dto);
        
        // postService의 검색 기능 호출:
        List<Post> list = postService.search(dto);
        
        // 검색 결과를 Model에 저장해서 뷰에 전달:
        model.addAttribute("posts", list);
        
        return "/post/read";
    }

Service

 @Transactional(readOnly = true)
  // 읽기만 하고 수정을 안 하기에 즉, 읽기 전용 용도이기에 Select 속도를 빠르게 하기 위해서 애너테이션 설정.
    public List<Post> search(PostSearchDto dto) {
        log.info("search(dto ={})", dto);

        List<Post> list = null;
        switch (dto.getType()) {
        case "t":
            list = postRepository.findByTitleContainsIgnoreCaseOrderByIdDesc(dto.getKeyword());
            break;
        case "c":
            list = postRepository.findByContentContainsIgnoreCaseOrderByIdDesc(dto.getKeyword());
            break;
        case "tc":
            list = postRepository.searchByKeyword(dto.getKeyword());
            break;
        case "a":
            list = postRepository.findByAuthorContainsIgnoreCaseOrderByIdDesc(dto.getKeyword());
            break;
        }

        return list;
    }

Repository

package com.itwill.spring4.repository.post;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

// crud 작업을 할 entity class이름 작성, 그 class의 id 컬럼의 타입.
public interface PostRepository extends JpaRepository<Post, Long> {

   
    
    // 제목으로 검색:
    /*
     * ?: prepared Statement
     * 
     * SQL 문장
     * select * from posts P
     * where lower(p.title) like lower('%' || ? || '%') 
     * order by p.id desc;
     * 
     * ||: 붙여주는 역할
     * lower, upper 등 상관없이 사용 가능함.
     */
    // JPA에서 
    // Contains: 키워드를 포함하고 있으면 -> Like 검색을 만들어주는 키워드
    // IgnoreCase: 대소문자 구분없이
    // where == By~: 뒤에 있는 글은 Post의 필드 값(컬럼 이름)이어야 하며, 만약 값이 Title일 경우 파람도 같은 걸로 해야 함.
    List<Post> findByTitleContainsIgnoreCaseOrderByIdDesc(String title);
    
    // 내용으로 검색:
    /*
     * ?: prepared Statement
     * 
     * SQL 문장
     * select * from posts P
     * where lower(p.content) like lower('%' || ? || '%') 
     * order by p.id desc;
     */
    List<Post> findByContentContainsIgnoreCaseOrderByIdDesc(String Content);
    
    // 작성자로 검색:
    /*
     * ?: prepared Statement
     * 
     * SQL 문장
     * select * from posts P
     * where lower(p.author) like lower('%' || ? || '%') 
     * order by p.id desc;
     */
    List<Post> findByAuthorContainsIgnoreCaseOrderByIdDesc(String author);
    
    // 제목 또는 내용으로 검색:
    /*
     * select * from posts p
     * where lower(p.title) like lower('%' || ? || '%')
     *    or lower(p.content) like lower('%' || ? || '%') 
     * order by p.id desc;   
     */
    List<Post> findByTitleContainsIgnoreCaseOrContentContainsIgnoreCaseOrderByIdDesc(String title, String Content);
    
    // JPQL(JPA Query Language) 문법으로 쿼리를 작성하고, 그 쿼리를 실행하는 메서드 이름을 설정
    // JPQL은 Entity 클래스의 이름과 필드 이름들을 사용해서 작성.
    // (주의) DB 테이블 이름과 컬럼 이름을 사용하지 않음!
    
    /*
     * :keyword -> 변수 이름
     * 변수이름이 서로 다를 경우 다르게 사용해도 가능함.
     * 변수 이름을 동일하게 사용시 param이 한 개로 채워지게 됨.
     * 
     * 모든 컬럼: entity 클래스에 별명 준 것사용, P/ *은 안됨.
     * 일부 컬럼: p.id, p.title,...
     */
    // @Param("keyword") 값에 argument keyword를 넘긴다.
    @Query(
         "select p from Post p " +
         "where lower(p.title) like lower('%' || :keyword || '%') " +
         "or lower(p.content) like lower('%' || :keyword || '%') " +
         "order by p.id desc"
    )
    List<Post> searchByKeyword(@Param("keyword") String keyword);
    
}
profile
한 단계씩 차근차근

0개의 댓글