[SpringBoot] 게시판 만들기(3) - 게시글 등록,조회 VIEW/API만들기 :: 매운코딩
728x90
300x250

앞선 포스트에서 API Controller를 만들었고 view도 기본적인 틀은 잡아두었다.

이제 게시글을 등록/수정/조회/삭제하는 기능이 화면상에서 버튼과 텍스트박스로 보여지도록 하겠다.

 

[스프링부트와 AWS로 혼자 구현하는 웹서비스] 책과 github을 참고하여 작성하였다.

전체코드는 아래의 깃헙주소를 참고하였다.

github.com/jojoldu/freelec-springboot2-webservice

 

jojoldu/freelec-springboot2-webservice

Contribute to jojoldu/freelec-springboot2-webservice development by creating an account on GitHub.

github.com

1. 메인페이지에 게시글 등록화면 만들기

@GetMapping("/"

기본 경로가 index 페이지를 호출하도록 하였으니, index 페이지에 등록버튼을 추가한다.

코드의 반복성을 줄이기위해 다른 화면에서도 공통으로 사용하는 부분은 레이아웃 방식을 통해 header와 footer로 구분하여 레이아웃을 생성해두고 화면별로 header와 footer를 include하여 재사용한다.

HTML은 위에서부터 코드가 실행되기떄문에 <head>가 다 불러와지지 않으면 실제 화면의 구성 내용이 담긴 <body>도 보이지 않기때문에 사용자는 백지 화면을 보게된다. 이에 페이지 로딩속도를 높이기 위해 css는 header, 용량이 큰 js는 footer에 둔다고한다. .. (우선 화면 그림부터 그려지고 그뒤에 동작 기능을 넣자는 것)

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>

프론트엔드 오픈소스인 부트스트랩은 제이쿼리가 꼭 있어야 동작되기 때문에 부트스트랩보다 제이쿼리를 먼저 호출해야 한다.

 

            <div class="col-md-6">
                <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
            </div>

 

2. 게시글 등록화면 HTML / JS 생성

위의 코드의 <a>태그를 누르면 /posts/save를 호출한다. 그럼 Controller에서 해당 url과 매핑되는 함수를 만들어준다.

    @GetMapping("/posts/save")
    public String postsSave() {
        return "posts-save";
    }

"posts-save"의 이름을 리턴하여 해당 이름과 동일한 view (.mustache) 를 찾아서 띄워준다.

그러기에 posts-save.mustache 파일도 temlplates 하위에 생성한다. (전체 코드는 깃헙 참고)

<div class="col-md-4">
        <form>
            <div class="form-group">
                <label for="title">제목</label>
                <input type="text" class="form-control" id="title" placeholder="제목을 입력하세요">
            </div>
            <div class="form-group">
                <label for="author"> 작성자 </label>
                <input type="text" class="form-control" id="author" placeholder="작성자를 입력하세요">
            </div>
            <div class="form-group">
                <label for="content"> 내용 </label>
                <textarea class="form-control" id="content" placeholder="내용을 입력하세요"></textarea>
            </div>
        </form>
        <a href="/" role="button" class="btn btn-secondary">취소</a>
        <button type="button" class="btn btn-primary" id="btn-save">등록</button>
</div>

프로젝트를 Run()하고 localhost:8080/에 접근하여 글등록을 하면 아래와 같이 나온다.

취소버튼은 누르면 "/"경로인 메인 index로 이동하게 되어있지만 등록버튼은 눌렀을때 반응이 없을 것이다.

클릭 이벤트시 수행할 함수를 작성하기 위해서 js 파일을 생성한다.

 

스프링부트는 기본적으로 절대경로를 src/main/resources/static로 설정한다. 이에 js파일도 static 디렉토리 하위에서 관리하면 HTML에서 <script>를 추가할 때, src = "/index.js"와 같이 static뒤의 절대경로로 js파일과 HTML파일을 연결시킬 수 있다.

 

등록 버튼을 클릭하였을 시, JS 코드의 일부이다.

POST메소드로 url인 /api/v1/ api호출을 한다. /api/v1/의 POST메서드로 매핑되어있는 controller의 함수가 실행된다.

        $.ajax({
            type: 'POST',
            url: '/api/v1/',
            dataType: 'json',
            contentType:'application/json; charset=utf-8',
            data: JSON.stringify(data)
        }).done(function(){
            alert('글이 등록되었습니다.');
            window.location.href = '/';
        }).fail(function (error){
            alert(JSON.stringify(error));
        });

작성 후 등록을 누르면 아래와 같이 alert이 뜨고 H2 DB에 들어간 것을 확인할 수 있다.

 

3. 게시글 조회 화면 생성

글 등록 버튼만 있던 index 메인페이지에 테이블을 생성하여 작성된 게시글을 조회할 수 있도록 한다.

        <!-- 목록 출력 영역 -->
        <table class="table table-horizontal table-bordered">
            <thead class="thead-strong">
            <tr>
                <th>게시글번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>최종수정일</th>
            </tr>
            </thead>
            <tbody id="tbody">
            {{#posts}}
                <tr>
                    <td>{{id}}</td>
                    <td><a href="/posts/update/{{id}}">{{title}}</a></td>
                    <td>{{author}}</td>
                    <td>{{modifiedDate}}</td>
                </tr>
            {{/posts}}
            </tbody>
        </table>

{{#posts}}는 머스테치의 문법으로 for문과 같다. {{id}}는 하나 뽑아낸 posts 객체의 속성 값들이다.

 

Controller/Repository/Service에서 전체 조회API를 요청받고 DB에서 가져오기 위한 작업을 수행한다.

//Repository
public interface PostsRepository extends JpaRepository<Posts, Long> {

    @Query("SELECT p FROM Posts p ORDER BY p.id DESC")
    List<Posts> findAllDesc();
    
}

@Query 어노테이션을 통해 Mybatis처럼 복잡한 쿼리를 JPA에서 수행할 수 있다.

다만... 이렇게 손으로 쿼리문을 직접작성하면 오타 등의 휴먼에러 발생 가능성이 높아서 JPA를 쓰는 경우에는 Querydsl을 많이 사용한다... (책에는 우선 해당 방법으로 나왔으므로 우선 따라하고 나중에 Querydsl을 접목해야겠다.)

//Service
    @Transactional(readOnly = true)
    public List<PostsListResponseDto> findAllDesc() {
        return postsRepository.findAllDesc().stream()
                .map(PostsListResponseDto::new)
                .collect(Collectors.toList());
    }

repo에서 findAllDesc()를 통해 얻은 stream형태의 결과를 map을 통해 PostsListResponseDto로 변환한 후 List로 반환한다. 결론은 PostsListResponseDto형태의 List를 반환하는 것...

 

//Controller
    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("posts",postsService.findAllDesc());
        return "index";
    }

Model은 서버 템플릿 엔진에서 사용하는 객체를 저장할 수 있다.

findAllDesc()로 가져온 결과를 index.mustache에 전달한다.

 

프로젝트를 다시 run()한 후, 게시물 등록한뒤 메인index 페이지로가면 이렇게 목록이 보인다.

728x90

+ Recent posts