Day 7

์ด์ „์— ๋งŒ๋“ค์—ˆ๋˜ mustache ํ…œํ”Œ๋ฆฟ์˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž

ํŽ˜์ด์ง€์— ๊ด€๋ จ๋œ ์ปจํŠธ๋กค๋Ÿฌ๋Š” IndexController์— ์ถ”๊ฐ€

src/main/java/com/kyu/book/springboot/web/dto/IndexController

@GetMapping("/posts/save")
public String postSave(){
    return "posts-save";
}
  • ์—ฌ๊ธฐ์„œ posts-save๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€

๊ธ€์„ ๋“ฑ๋กํ•˜๋Š” ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑ

src/main/resource/template/posts-save.mustache

{{>layout/header}}

<h1>๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก</h1>

<div class="col-md-12">
    <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>
</div>

{{>layout/footer}}

ํ˜„์žฌ๊นŒ์ง€๋Š” localhost:8080์œผ๋กœ ์ ‘์† โ†’ ๊ธ€ ๋“ฑ๋ก(๋ฒ„ํŠผ)์„ ๋ˆ„๋ฆ„ โ†’ posts-save.mustache๋กœ ์ด๋™๋˜์„œ ๊ธ€ ๋“ฑ๋กํ•˜๋Š” ํŽ˜์ด์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Œ

๊ธ€์„ ๋“ฑ๋กํ•˜๋Š” ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด API๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋Š” jsํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์•ผํ•จ

src/main/resources/static/js/app/index.js

var main = {
    init : function(){
        var _this = this;
        $('#btn-save').on('click', function(){
            _this.save();
        });
    },
    save : function(){
        var data = {
            title: $('#title').val(),
            author: $('#author').val(),
            content: $('#content').val()
        };

        $.ajax({
            type: 'POST',
            url: '/api/v1/posts',
            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));
        });
    }
};
main.init();

์›๋ž˜ ๋งŒ๋“ค์–ด๋‘์—ˆ๋˜ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” javascriptํŒŒ์ผ

๋ธŒ๋ผ์šฐ์ €์˜ ์Šค์ฝ”ํ”„๋Š” ๊ณต์šฉ ๊ณต๊ฐ„์œผ๋กœ ์“ฐ์ด๊ธฐ ๋–„๋ฌธ์— ๋‚˜์ค‘์— ๋กœ๋”ฉ๋œ js์˜ init, save๊ฐ€ ๋จผ์ € ๋กœ๋”ฉ๋œ js์˜ function์„ ๋ฎ์–ด์“ฐ๊ฒŒ ๋œ๋‹ค โ‡’ ๊ทธ๋ž˜์„œ index.js๋งŒ์˜ ์œ ํšจ๋ฒ”์œ„(scope)๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ

var index๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•ด๋‹น ๊ฐ์ฒด์—์„œ ํ•„์š”ํ•œ ๋ชจ๋“  function์„ ์„ ์–ธํ–ˆ์Œ โ‡’ index๊ฐ์ฒด ์•ˆ์—์„œ๋งŒ function์ด ์œ ํšจํ•˜๊ธฐ ๋–„๋ฌธ์— ๋‹ค๋ฅธ js์™€๋Š” ๊ฒน์น  ์œ„ํ—˜์ด ์‚ฌ๋ผ์ง€๊ฒŒ ๋จ

ํ˜„์žฌ index์˜ ํ˜ธ์ถœ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด /(์ ˆ๋Œ€๊ฒฝ๋กœ)์—์„œ ์‹œ์ž‘

  • ์Šคํ”„๋ง๋ถ€ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ src/main/resource/static์— ์œ„์น˜ํ•œ js, css ๋“ฑ ์ •์ ์ธ ํŒŒ์ผ๋“ค์„ URL์—์„œ /๋กœ ์„ค์ •

๋“ฑ๋ก ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์•ˆ๋ˆŒ๋ฆฌ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค

  • resources์—์„œ static/js/app ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์•ˆ์— index.js๋ฅผ ์ƒ์„ฑํ–ˆ๋Š”๋ฐ IDE์—์„œ ๋ณผ๋•Œ ๊ตฌ๋ถ„์ด . ์œผ๋กœ ๋˜์–ด์žˆ์–ด์„œ ์•„๋ฌด์ƒ๊ฐ์—†์ด . ์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์—ˆ๋‹ค

    โ‡’๊ฒฐ๊ณผ์ ์œผ๋กœ static/js/app ์ด๋ฆ„์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•ด์คŒ

์ „์ฒด ์กฐํšŒ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ

index.mustachedml UI๋ฅผ ๋ณ€๊ฒฝ

src/main/resources/templates/index.mustache

{{>layout/header}}
    <h1>์Šคํ”„๋ง ๋ถ€ํŠธ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์›น ์„œ๋น„์Šค</h1>
    <div class="col-md-12">
        <div class="col-md-6">
            <a href="posts/save" role="button" class="btn btn-primary">๊ธ€ ๋“ฑ๋ก</a>
        </div>
        <br>
        <table class="table table-horizontal table-bordered">
            <thread class="thead-strong">
                <tr>
                    <th>๊ฒŒ์‹œ๊ธ€๋ฒˆํ˜ธ</th>
                    <th>์ œ๋ชฉ</th>
                    <th>์ž‘์„ฑ์ž</th>
                    <th>์ตœ์ข…์ˆ˜์ •์ผ</th>
                </tr>
            </thread>
            <tbody id="tbody">
            {{#posts}}
                <tr>
                    <td>{{id}}</td>
                    <td>{{title}}</td>
                    <td>{{author}}</td>
                    <td>{{modifiedDate}}</td>
                </tr>
            {{/posts}}
            </tbody>
        </table>
    </div>

{{>layout/footer}}
  • ๋จธ์Šคํ…Œ์น˜์˜ ๋ฌธ๋ฒ•์ด ์‚ฌ์šฉ๋จ

    • {{#posts}} : posts๋ผ๋Š” List๋ฅผ ์ˆœํšŒํ•œ๋‹ค

      • Java์˜ for๋ฌธ์ด๋ผ๊ณ  ์ƒ๊ฐ

    • {{id}}๋“ฑ์˜ ๋ณ€์ˆ˜๋ช… : List์—์„œ ๋ฝ‘์•„๋‚ธ ๊ฐ์ฒด์˜ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉ

Controller, Service, Repository ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ

๊ธฐ์กด์— ์žˆ๋˜ Repository์ธํ„ฐํŽ˜์ด์Šค์— ์ฟผ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋จ

src/main/java/com/kyu/book/springboot/domain/posts/PostsRepository

package com.kyu.book.springboot.domain.posts;

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

import java.util.List;

public interface PostsRepository extends JpaRepository<Posts, Long> {
    @Query("SELECT p FROM Posts p ORDER BY p.id DESC ")
    List<Posts> findAllDesc();
}
  • ์ฒ˜์Œ์œผ๋กœ ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๊ณ  ์ฟผ๋ฆฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ @Query ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉ

  • ๋ฐ์ดํ„ฐ์˜ ์กฐํšŒ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด

    • read๋Š” MyBatis ๋ฅผ ์‚ฌ์šฉ

    • create, update, delete๋Š” SpringDataJPA๋ฅผ ์‚ฌ์šฉ

๋‹ค์Œ์œผ๋กœ๋Š” PostsService์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€

src/main/java/com/kyu/book/springboot/service/posts/PostsService

@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc(){
    return postsRepository.findAllDesc().stream().map(PostsListResponseDto::new).collect(Collectors.toList());
}
  • @Transactional(readOnly = true)์ด๋ ‡๊ฒŒ ์˜ต์…˜์„ ์ถ”๊ฐ€

    • readOnly = true ๋Š” ํŠธ๋žœ์ ์…˜์˜ ๋ฒ”์œ„๋Š” ์œ ์ง€ํ•˜๋˜, ์กฐํšŒ ๊ธฐ๋Šฅ๋งŒ ๋‚จ๊ฒจ๋‘๋Š” ๊ฒƒ

    • ์กฐํšŒ ์†๋„๊ฐ€ ๊ฐœ์„ 

    • create, update, delete๊ธฐ๋Šฅ์ด ์ „ํ˜€ ์—†๋Š” ์„œ๋น„์Šค ๋ฉ”์†Œ๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœ

  • ์—ฌ๊ธฐ์„œ PostsListResponseDto๊ฐ€ ์—†์Œ

๋ฐ”๋กœ ์—ฌ๊ธฐ์„œ ์ƒ์„ฑํ•ด์คŒ

src/main/java/com/kyu/book/springboot/web/dto/PostsListResponseDto

package com.kyu.book.springboot.web.dto;

import com.kyu.book.springboot.domain.posts.Posts;
import lombok.Getter;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

@Getter
public class PostsListResponseDto {
    private Long id;
    private String title;
    private String author;
    private LocalDateTime modifiedDate;

    public PostsListResponseDto(Posts entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
        this.modifiedDate = entity.getModifiedDate();
    }
}

์ฝ์—ˆ์œผ๋‹ˆ๊นŒ ์ฝ์€ ๊ฒƒ์„ ์ถœ๋ ฅํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ IndexController ์ˆ˜์ •

src/java/main/kyu/book/springboot/web/dto/IndexController

package com.kyu.book.springboot.web.dto;

import com.kyu.book.springboot.service.posts.PostsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@RequiredArgsConstructor
@Controller
public class IndexController {
    private final PostsService postsService;

		//๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ณณ
    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("posts", postsService.findAllDesc());
        return "index";
    }
		//////////////////
    @GetMapping("/posts/save")
    public String postSave(){
        return "posts-save";
    }
}
  • Model

    • ์„œ๋ฒ„ ํ…œํ”Œ๋ฆฟ ์—”์ง„์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Œ

    • ์—ฌ๊ธฐ์—์„œ๋Š” postsService.findAllDesc()๋กœ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋ฅผ posts๋กœ index.mustache์— ์ „๋‹ฌํ•œ๋‹ค

Last updated

Was this helpful?