Discover us

About us

Projects

Blog

Events

Members

Development Blog

GDGoC CAU 개발자와 디자이너의 작업 과정과
결과물을 공유하는 공간입니다.

어떻게 프로젝트를 시작하게 되었고,
진행하면서 느낀 개발자와 디자이너의
생생한 스토리를 직접 확인해보세요!

Development

4주차 Spring Study

  • #Back-End
  • Yunji Lim
  • 2024. 9. 23.

4주차 Spring Study

지난주 스터디..

  • JPA 의 개념
  • 프로젝트에 Spring Data Jpa 적용
  • 등록 / 수정 / 조회 API 생성
  • JPA Auditing을 이용한 생성시간 / 수정시간 자동화
 

제4장 머스테치로 화면 구성하기

  1. 서버 템플릿 엔진과 머스테치 소개
  1. 기본 페이지 만들기
  1. 게시글 등록 화면 만들기
  1. 전체 조회 화면 만들기
  1. 게시글 수정, 삭제 화면 만들기

1. 서버 템플릿 엔진과 머스테치 소개

템플릿 엔진
: 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
 

머스테치

: 수많은 언어를 지원하는 가장 심플한 템플릿 엔진
 
notion image
 
지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
 
일반적인 템플릿 엔진들의 단점
  • JSP, Velocity
    • : 스프링 부트에서는 권장하지 않는 템플릿 엔진 → 유료 버전에서만 공식 지원
  • Freemarker
    • : 너무 과하게 많은 기능 지원
      : 높은 자유도로 인해 숙련도에 따라 잘못된 방향으로 사용하게 될 가능성 높음
  • Thymleaf
    • : 어려운 문법
      : HTML 태그에 속성으로 템플릿 기능을 사용하는 방식 → 높은 진입장벽
       
머스테치의 장점
  • 다른 템플릿 엔진보다 심플한 문법
  • 로직 코드 사용 x → View의 역할과 서버의 역할의 명확한 분리
  • Mustache.js와 Mustache.java 2개 모두 존재
    • → 하나의 문법으로 클라이언트 / 서버 템플릿 모두 사용 가능
 

머스테치 플러그인 설치

: 해당 플러그인을 이용하면 문법 체크, HTML 문법 지원, 자동완성 등이 지원됨
notion image
💡
설치 완료 후 인텔리제이 재시작
 

2. 기본 페이지 만들기

머스테치 스타터 의존성을 build.gradle에 등록
buildscript { ext { springBootVersion = '2.1.18.RELEASE' } repositories { mavenCentral() jcenter() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group 'com.jojoldu.book' version '1.0.4-SNAPSHOT-'+new Date().format("yyyyMMddHHmmss") sourceCompatibility = 1.8 repositories { mavenCentral() jcenter() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.projectlombok:lombok') compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('com.h2database:h2') compile('org.springframework.boot:spring-boot-starter-mustache') testCompile('org.springframework.boot:spring-boot-starter-test') }
 

index.mustache

머스테치의 기본 파일 위치인 src/main/resources/templates 디렉토리에 생성
notion image
<!DOCTYPE HTML> <html> <head> <title>스프링 부트 웹서비스</title> <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8" /> </head> <body> <h1>스프링 부트로 시작하는 웹 서비스</h1> </body> </html>
  • h1 크기로 “스프링 부트로 시작하는 웹 서비스” 출력
 

IndexController.java

src/main/java/com/jojoldu/book/springboot/web 에 생성
package com.jojoldu.book.springboot.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ return "index"; } }
  • 해당 머스테치에 URL 매핑 (in Controller)
  • 머스테치 스타터로 인해 컨트롤러에서 문자열 반환시 앞의 경로와 뒤의 파일 확장자는 자동으로 지정
    • 앞의 경로 : src/main/resources/templates 뒤의 파일 확장자 : .mustache
 

IndexControllerTest.java

src/test/java/com/jojoldu/book/springboot/web 에 생성
package com.jojoldu.book.springboot.web; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) public class IndexControllerTest { @Autowired private TestRestTemplate restTemplate; @Test public void 메인페이지_로딩() { //when String body = this.restTemplate.getForObject("/", String.class); //then assertThat(body).contains("스프링 부트로 시작하는 웹 서비스"); } }
  • 실제로 URL 호출 시 페이지의 내용이 제대로 호출되는지에 대한 테스트 코드
 
💡
Non-ASCII characters 경고 발생
원인 : 한글 변수명 사용으로 인해 발생
해결책 : Settings > Editor > Inspections > Non-ASCII characters 체크 해제
notion image
notion image
 

실제 실행 화면

Application.java의 main 메소드 실행
notion image
 

3. 게시글 등록 화면 만들기

프론트엔드 라이브러리를 사용할 수 있는 방법
  • 외부 CDN 사용
  • 직접 라이브러리를 받아 사용
 
2개의 라이브러리 부트스트랩과 제이쿼리를 레이아웃 방식으로 추가
→ 레이아웃 방식 : 공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 쓰는 방식
 

header.mustache

src/main/resources/templates/layout 디렉토리에 생성
notion image
<!DOCTYPE HTML> <html> <head> <title>스프링부트 웹서비스</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> </head> <body>
 

footer.mustache

src/main/resources/templates/layout 디렉토리에 생성
<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> </body> </html>
💡
HTML은 head가 다 실행된 후에 body가 실행 → 페이지 로딩 속도를 높이기 위해 css는 header에, js는 footer에 둠
 

index.mustache

src/main/resources/templates/index.mustache 에서 수정
{{>layout/header}} <h1>스프링부트로 시작하는 웹 서비스 Ver.2</h1> <div class="col-md-12"> <div class="row"> <div class="col-md-6"> <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a> </div> </div> </div> {{>layout/footer}}
  • {{> }} : 현재 머스테치 파일을 기준으로 다른 파일을 가져옴.
  • <a> : 태그를 이용해 글 등록 버튼 생성
  • 이동할 페이지 주소 : /posts/save
 

IndexController.java

src/main/java/com/jojoldu/book/springboot/web/IndexController.java 에 추가
package com.jojoldu.book.springboot.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ return "index"; } // ********** @GetMapping("/posts/save") public String postsSave() { return "posts-save"; } // ********** }
  • /posts/save를 호출하면 posts-save.mustache를 호출하는 메소드
 

posts-save.mustache

src/main/resources/templates 디렉토리에 생성
{{>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}}
 
실제 실행 화면
Application.java의 main 메소드 실행
notion image
  • 등록 버튼 기능 없음
 

index.js

src/main/resources 에 static/js/app 디렉토리 생성 후 여기에 index.js 생성
notion image
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();
  • window.location.href = '/' : 글 등록이 성공하면 메인페이지(/)로 이동
 

footer.mustache

src/main/resources/templates/layout/footer.mustache 에 추가
<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> // ********** <!--index.js 추가--> <script src="/js/app/index.js"></script> // ********** </body> </html>
  • 생성된 index.js를 머스테치 파일이 쓸 수 있게 footer.mustach에 추가
  • index.js 호출 코드에 따라 절대 경로(/)로 바로 시작
 
실제 실행 화면
Application.java의 main 메소드 실행
notion image
 

실제 DB 화면

notion image
notion image