일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 아이패드다이어리
- 네트워크
- 티스토리챌린지
- sql
- 42seoul
- CI
- 데이터베이스
- jenkins
- 리눅스
- libasm
- JPA
- 오라클
- swift
- 스프링
- 스프링부트 웹 소켓
- springboot
- IOS
- 스프링부트
- CD
- 소켓
- Xcode
- 프로그래밍언어론
- Spring
- AI
- 오블완
- 다이어리
- DBMS
- 인공지능
- javascript
- MySQL
- Today
- Total
Hi yoahn 개발블로그
#4 [SpringBoot] 머스테치로 화면 구성하기 본문
4.1 템플릿 엔진
JSP와 같이 HTML을 만들어주는 템플릿 엔진
command+shift+a -> plugins => mustache 검색 -> 플러그인 설치
4.2 기본 페이지 만들기
build.gradle
compile('org.springframework.boot:spring-boot-starter-mustache')
의존성 추가
머스테치의 파일 위치
src/main/resources/templates
index.html
<!DOCTYPE HTML>
<html>
<head>
<title>스프링 부트 웹서비스</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<h1>스프링 부트로 시작하는 웹서비스</h1>
</body>
</html>
이 머스테치에 URL 매핑하기
-> Controller에서 진행
web/IndexController.java 생성
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
return "index";
}
}
컨트롤러에서 문자열을 반환할 때 앞의 경로와 뒤의 파일 확장자는 자동으로 지정된다
앞의 경로: src/main/resources/templates/
뒤의 파일 확장자: .mustache
=> src/main/resources/templates/index.mustache 로 전환되어 View Resolver가 처리하게 된다
(View Resolver: URL 요청의 결과를 전달할 타입과 값을 지정하는 관리자 격으로 볼 수 있다)
4.3 게시글 등록 화면 만들기
프론트엔드 라이브러리 사용하기
외부 CDN, 직접 라이브러리 받아서 사용
레이아웃 방식으로 라이브러리 추가하기
src/main/resources/templates/layout/
footer.mustache, header.mustache 파일 생성
github.com/jojoldu/freelec-springboot2-webservice/blob/master/src/main/resources/templates/layout
jojoldu/freelec-springboot2-webservice
Contribute to jojoldu/freelec-springboot2-webservice development by creating an account on GitHub.
github.com
css와 js의 위치가 서로 다르다.
페이지 로딩 속도를 높이기 위해 css 는 header에, js 는 footer에 있다
HTML은 위에서부터 코드가 실행되기 때문에 head가 다 실행되고서야 body가 실행된다.
head가 다 불러지지 않으면 사용자 쪽에선 백지 화면만 노출된다. js의 용량이 크면 클수록 body 부분 실행이 늦어지기 때문에 body 하단에 두어 화면이 다 그려진 뒤에 호출하는 것이 좋다
css는 화면을 그리는 역할이므로 head에서 불러오는 것이 좋다. 그렇지 않으면 css가 적용되지 않은 화면을 사용자가 볼 수 있게 됨
bootstrap.js의 경우 제이쿼리가 꼭 있어야만 하기 때문에 부트스트랩보다 먼저 호출되도록 코드를 작성
-> bootstrap.js가 제이쿼리에 의존한다
라이브러리를 비롯해 기타 HTML 태그들이 모두 레이아웃에 추가되어 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}}
{{> }}
현재 머스테치 파일을 기준으로 index.mustache) 다른 파일을 가져온다
글 등록 버튼 추가하기
이동할 페이지의 주소 /posts/save
이 주소에 해당하는 컨트롤러를 생성 -> IndexController.java (페이지에 관련된 컨트롤러)
게시글 등록 화면에 등록 버튼 기능 추가하기 -> API를 호출하는 JS가 전혀 없기 때문에 작동 안함
src/main/resources/static/js/app
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='/'
- 글 등록이 성공하면 메인페이지로 이동한다
var main = { }
main 변수의 속성으로 function을 추가
index.mustache에서 다른 js 파일이 추가되어 그 파일에 init, save function 이 있다면 문제 생김
-> 브라우저의 scope 은 공용 공간으로 쓰이기 때문에 나중에 로딩된 js의 init, save function이 먼저 로딩된 js의 function을 덮어쓰게 된다
---> 중복된 함수 이름이 발생하여 문제가 생기는 것을 막기 위해서 index.js만의 유효범위를 만들어서 사용하는 것
var main 객체를 만들어서 해당 객체에서 필요한 모든 function을 선언하면 main 객체 안에서만 function이 유효하기 때문에 다른 js와 겹칠 위험이 사라진다
index.js를 머스테치 파일이 쓸 수 있게 footer.mustache에 추가
<script src="/js/app/index.js"></script>
index.js 경로 -> 절대 경로로 시작
스프링 부트는 기본적으로 src/main/resources/static에 위치한 자바스크립트, CSS, 이미지 등 정적 파일들은 URL에서 /로 설정된다
static/js/<파일> -> http://도메인/js/<파일>
localhost:8080 에서 글 등록 버튼 누르고 내용을 입력한 뒤 등록 버튼을 누르면 Alert 창이 뜨고
localhost:8080/h2-console 에 접속해서 실제로 DB에 데이터가 등록되었는지도 확인
-> select * from posts;
게시글 등록된 것 출력됨
4.4 전체 조회 화면 만들기
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>
<br>
<!-- 목록 출력 영역 -->
<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>{{title}}</td>
<td>{{author}}</td>
<td>{{modifiedDate}}</td>
</tr>
{{/posts}}
</tbody>
</table>
</div>
{{>layout/footer}}
mustache 문법 사용
{{ #posts }}
- posts 라는 List 를 순회
{{ 변수명 }}
- List 에서 뽑아낸 객체 필드를 사용
PostsRepository.java 쿼리 추가
public interface PostsRepository extends JpaRepository<Posts, Long> {
@Query("SELECT p FROM Posts p ORDER BY p.id DESC")
List<Posts> findAllDesc();
}
SpringDataJpa 에서 제공하지 않는 메소드는 위처럼 쿼리로 작성해도 됨
PostsService.java 에 코드 추가
@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc(){
return postsRepository.findAllDesc().stream()
.map(PostsListResponseDto::new)
.collect(Collectors.toList());
}
findAllDesc 메소드의 트랜잭션 어노테이션에 옵션 하나 추가됨
readOnly = true 를 옵션으로 주면 트랜잭션 범위는 유지하되 조회 기능만 남겨두게 됨 -> 조회 속도 개선
.map(PostsListResponseDto::new)
= .map(post -> new PostsListResponseDto(posts))
같은 코드
postsRepository 결과로 넘어온 Posts의 Stream을 map을 통해 PostsListResponseDto 변환 -> List로 반환하는 메소드
web/dto/PostsListResponseDto.java 클래스 생성
import com.youngs.springboot.domain.posts.Posts;
import lombok.Getter;
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.java 코드 추가
public class IndexController {
private final PostsService postsService;
@GetMapping("/")
public String index(Model model) {
model.addAttribute("posts", postsService.findAllDesc());
return "index";
}
}
# Model
- 서버 템플릿 엔진(mustache)에서 사용할 수 있는 객체를 저장할 수 있다
- 여기서는 postService.findAllDesc()로 가져온 결과를 posts로 index.mustache에 전달
4.5 게시글 수정, 삭제 화면 만들기
게시글 수정 API
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostsService postsService;
@PutMapping("/api/v1/posts/{id}")
public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto) {
return postsService.update(id, requestDto);
}
}
1) 게시글 수정 화면 머스테치 파일 생성
jojoldu/freelec-springboot2-webservice
Contribute to jojoldu/freelec-springboot2-webservice development by creating an account on GitHub.
github.com
# {{post.id}}
포스트 클래스의 id에 대한 접근 방법
# readonly
- Input 태그에 읽기 가능만 허용하는 속성
- id와 author는 수정할 수 없도록 읽기만 허용하도록 추가
2) btn-update 버튼에 기능 호출하도록 js 추가
p.153-154
$('#btn-update').on('click', function(){_this.update();});
- id가 btn-update인 HTML 엘리먼트에 click 이벤트가 발생하면 update 함수를 실행하도록 이벤트를 등록
type: 'PUT'
- 여러 HTTP 메소드 중, PUT 메소드를 선택
- PostsApiController에 있는 API에서 이미 @PutMapping 으로 선언했기 때문에 PUT을 사용해야 한다
# REST 에서 CRUD는 각각 HTTP 메소드에 매핑된다
- Create : POST
- Read : GET
- Update : PUT
- Delete : DELETE
url: '/api/v1/posts/' + id
- 어느 게시글을 수정할지 URL Path로 구분하기 위해 Path에 id를 추가한다
3) 전체 목록에서 수정 페이지로 이동할 수 있는 기능 추가
index.mustache
<td><a href="/posts/update/{{id}}">{{title}}</a></td>
- a 태그 추가
4) Controller
IndexController 에 메소드 추가
public class IndexController {
private final PostsService postsService;
@GetMapping("/posts/update/{id}")
public String postsUpdate(@PathVariable Long id, Model model){
PostsResponseDto dto = postsService.findById(id);
model.addAttribute("post", dto);
return "posts-update";
}
}
p.158 ~
삭제 이벤트도 추가
index.js
PostsService.java
# postsRepository.delete(posts)
- JpaRepository에서 이미 delete 메소드를 지원
- 엔티티를 파라미터로 삭제할 수도 있고, deleteById 메소드를 이용하면 id로 삭제할 수도 있다
- 존재하는 Posts 인지 확인을 위해 엔티티 조회 후 그대로 삭제
'Framework & Library > springboot' 카테고리의 다른 글
[Spring] #2 스프링 웹 개발 기초 (0) | 2021.01.20 |
---|---|
[Spring] #1 spring 시작하기 (0) | 2021.01.20 |
#3 [Spring Boot] JPA로 데이터베이스 다루기 (0) | 2020.09.08 |
#2 스프링부트 테스트코드 (0) | 2020.08.31 |
#1 spring boot 프로젝트 시작하기 (0) | 2020.08.30 |