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

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

4.1 서버 템플릿 엔진

템플릿 엔진이란, 지정된 템플릿 양식과 데이터가 합쳐져 HTML문서를 출력하는 소프트웨어를 이야기한다.

서버 템플릿 엔진을 이용한 화면 생성은 서버에서 Java 코드로 문자열을 만든 뒤 이 문자열을 HTML로 변환하여 브라우저로 전달한다.

반면 클라이언트 템플릿 엔진(Vue, React 등)을 이용한 SPA(Single Page Application)은 브라우저에서 화면을 생성한다. 즉, 서버에서 이미 코드가 벗어난 경우라서

서버에서는 Json 혹은 Xml 형식의 데이터만 전달하고 클라이언트에서 조립한다.

최근엔 리액트나 뷰와 같은 자바스크립트 프레임워크에서 서버사이드렌더링을 지원하는 모습을 볼 수 있지만 그건 나중에 생각하자.


머스테치

머스테치는 많은 언어를 지원하는 심플한 템플릿 엔진이다.

스프링 부트에서 공식 지원하는 템플릿 엔진으로 gradle에 의존성 한줄 추가하면 바로 사용할 수 있다.

파일위치는 기본적으로 src/main/resources/templates이며 이 위치에 머스테치 파일을 두면 스프링 부트에서 자동으로 로딩한다.

해당 위치에 index.mustache를 생성한 후 이 머스테치에 URL을 매핑하는데 이는 Controller에서 진행한다.

  • IndexController
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;


@Controller
public class IndexController {

@GetMapping("/")
public String index() {
return "index";
}
}

머스테치 스타터 의존성을 추가했기 때문에 컨트롤러에서 문자열을 반환할 때 앞의 경로와 뒤의 파일 확장자는 자동으로 지정된다.

즉, 여기선 "index"를 반환하므로 src/main/resources/templates/index.mustache로 전환되어 View Resolver가 처리하게 된다.

(View Resolver는 URL 요청의 결과를 전달할 타입과 값을 지정하는 관리자 격으로 볼 수 있다.)


화면 구성 시 bootstrap을 사용하는데 공통된 부분에 대해선 layout을 따로 둬서 header와 footer 파일을 각각 만들어서 공통된 코드는 해당 위치에 생성한다.

여기서 페이지 로딩속도를 높이기 위해 css는 header에, js는 footer에 두는데 HTML은 위에서부터 코드가 실행되기 때문에 head가 다 실행되고서야 body가 실행된다.

즉, head가 다 불러지지 않으면 사용자 쪽에선 백지 화면만 노출되며 특히 js의 용량이 크면 클수록 body 부분의 실행이 늦어지기 때문에 js는 body 하단에 두어 화면이 다 그려진 뒤에 호출하는 것이 좋다.

header와 footer를 index에 추가하는건 아래와 같다.

1
2
3
4
5
6
7
8
9
10
{{>layout/header}}
<h1>스프링 부트로 시작하는 웹 서비스</h1>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a href="/posts/save" role="button" clas="btn btn-primary">글 등록</a>
</div>
</div>
</div>
{{>layout/footer}}

{{> }} 는 현재 머스테치 파일을 기준으로 다른 파일을 가져온다.

화면의 버튼에 API를 호출하는 js파일을 작성하여 footer.mustache에 추가한다.

1
2
3
4
5
6
7
<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 호출 코드는 절대경로(/)로 바로 시작하는데 스프링 부트는 기본적으로 src/main/resources/static에 위치한 자바스크립트, CSS, 이미지 등 정적 파일들은 URL에서 / 로 설정된다.


  • PostsRepository 인터페이스
1
2
3
4
5
6
public interface PostsRepository extends JpaRepository<Posts, Long> {

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

@Query 어노테이션을 사용하면 SpringDataJpa에서 제공하지 않는 메소드를 쿼리로 직접 작성할 수 있다.

보통 규모가 있는 프로젝트에선 데이터 조회는 FK의 조인, 복잡한 조건 등으로 인해 Entity 클래스만으로 처리가 어려워 조회용 프레임워크를 추가로 사용한다.

대표적 예로 querydsl, jooq, MyBatis 등이 있는데 해당 프레임워크 중 하나로 조회를 하고 그 외 등록/수정/삭제 등은 SpringDataJpa를 통해 진행한다.

  • PostsService
1
2
3
4
5
6
...
@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc() {
return postsRepository.findAllDesc().stream().map(PostsListResponseDto::new).collect(Collectors.toList());
}
...

@Transactional 어노테이션에 추가된 readOnly 옵션을 true로 주면 트랜잭션 범위는 유지하되, 조회기능만 남겨두어 조회 속도가 개선되기 때문에 등록, 수정, 삭제 기능이 전혀 없는 서비스 메소드에서 사용하는 것을 추천한다.


  • IndexController
1
2
3
4
5
6
7
8
9
10
11
12
@RequiredArgsConstructor
@Controller
public class IndexController {

private final PostsService postsService;

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

Model

  • 서버 템플릿 엔진에서 사용할 수 있는 객체를 저장할 수 있다.
  • 여기서는 postsService.findAllDesc()로 가져온 결과를 posts로 index.mustache에 전달한다.

REST에서 CURD는 다음과 같이 HTTP Method에 매핑된다.

  • 생성(Create) : POST
  • 읽기(Read) : GET
  • 수정(Update) : PUT
  • 삭제(Delete) : DELETE
Author

Jaeyong Yoo

Posted on

2021-04-22

Updated on

2023-06-10

Licensed under

댓글