이 글은 책 「스프링 부트와 AWS로 혼자 구현하는 웹 서비스」를 공부하고 정리한 글입니다.
지난 시간에 이어 오늘은 전체 조회 화면과 게시글 수정/삭제 기능을 추가해보겠다.
{{>layout/header}}
<h1>스프링 부트로 시작하는 웹 서비스 Ver.2</h1>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a href="/post/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">
{{#post}}
<tr>
<td>{{id}}</td>
<td><a href="/post/update/{{id}}">{{title}}</a></td>
<td>{{author}}</td>
<td>{{modifiedDate}}</td>
</tr>
{{/post}}
</tbody>
</table>
</div>
{{>layout/footer}}
머스테치의 문법을 살펴보자.
{{#post}}
post
라는 List를 순회한다. Java의 for문과 같은 역할이다.{{id}}
등의 {{변수명}}
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p ORDER BY p.id DESC")
List<Post> findAllDesc();
}
SpringDataJpa에서 제공하지 않는 메소드는 이렇게 @Query
를 사용한 뒤 쿼리로 작성하면 된다.
public class PostService {
...
@Transactional(readOnly = true)
public List<PostListResponseDto> findAllDesc() {
return postRepository.findAllDesc().stream()
.map(PostListResponseDto::new)
.collect(Collectors.toList());
}
}
findAllDesc
메소드의 @Transactional
어노테이션에 옵션을 하나 추가했다. (readOnly = true)
를 주면 트랜잭션 범위는 유지하되, 조회 기능만 남겨두어 조회 속도가 개선된다. 그러므로 등록, 수정, 삭제, 기능이 전혀 없는 서비스 메소드에서 사용하는 것을 추천한다.
@Getter
public class PostListResponseDto {
private Long id;
private String title;
private String author;
private LocalDateTime modifiedDate;
public PostListResponseDto(Post entity) {
this.id = entity.getId();
this.title = entity.getTitle();
this.author = entity.getAuthor();
this.modifiedDate = entity.getModifiedDate();
}
}
public class IndexController {
private final PostService postService;
@GetMapping("/")
public String index() {
public String index(Model model) {
model.addAttribute("post", postService.findAllDesc());
return "index";
}
}
Model
postService.findAllDesc()
로 가져온 결과를 post
로 index.mustache
에 전달한다.localhost:8080
접속 화면{{>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="id" value="{{post.id}}" readonly>
</div>
<div class="form-group">
<label for="title">제목</label>
<input type="text" class="form-control" id="title" value="{{post.title}}">
</div>
<div class="form-group">
<label for="author"> 작성자 </label>
<input type="text" class="form-control" id="author" value="{{post.author}}" readonly>
</div>
<div class="form-group">
<label for="content"> 내용 </label>
<textarea class="form-control" id="content">{{post.content}}</textarea>
</div>
</form>
<a href="/" role="button" class="btn btn-secondary">취소</a>
<button type="button" class="btn btn-primary" id="btn-update">수정 완료</button>
</div>
</div>
{{>layout/footer}}
{{post.id}}
.
)으로 구분한다.post.id
로 할 수 있다.readOnly
btn-update
를 클릭하면 update 기능을 호출할 수 있도록 update function을 추가하자.
var main = {
init : function () {
var _this = this;
...
$('#btn-update').on('click', function () { //추가
_this.update();
});
},
save : function () {
...
},
update : function () { //추기
var data = {
title: $('#title').val(),
content: $('#content').val()
};
var id = $('#id').val();
$.ajax({
type: 'PUT',
url: '/api/v1/post/'+id,
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();
$('#btn-update').on('click')
btn-update
라는 id를 가진 HTML 요소에 click
이벤트가 발생할 때 update function을 실행하도록 이벤트를 등록한다.update : function()
type: 'PUT'
@PutMapping
으로 선언했기 때문에 PUT을 사용해야 한다.url:'/api/v1/post/'+id
public class IndexController {
...
@GetMapping("/post/update/{id}")
public String postUpdate(@PathVariable Long id, Model model) {
PostResponseDto dto = postService.findById(id);
model.addAttribute("post", dto);
return "post-update";
}
}
수정 전
내용 수정
수정 완료 Alert
수정 후 목록 화면
...
<div class="col-md-12">
<div class="col-md-4">
...
<button type="button" class="btn btn-danger" id="btn-delete">삭제</button>
</div>
</div>
{{>layout/footer}}
btn-delete
var main = {
init : function () {
...
$('#btn-delete').on('click', function () {
_this.delete();
});
},
...
delete : function () {
var id = $('#id').val();
$.ajax({
type: 'DELETE',
url: '/api/v1/post/'+id,
dataType: 'json',
contentType:'application/json; charset=utf-8'
}).done(function() {
alert('글이 삭제되었습니다.');
window.location.href = '/';
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
};
main.init();
type: 'DELETE'
을 제외하면 update function과 크게 다르지 않다.
public class PostService {
...
@Transactional
public void delete(Long id) {
Post post = postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));
postRepository.delete(post);
}
}
postRepository.delete(post)
deleteById
메소드를 이용하면 id로 삭제할 수도 있다.public class PostApiController {
...
@DeleteMapping("/api/v1/post/{id}")
public Long delete(@PathVariable Long id) {
postService.delete(id);
return id;
}
}
삭제 완료 Alert
삭제 후 목록 화면
이렇게 기본적인 게시판 기능을 완성하였다! 다음 시간에는 로그인 기능을 만들어보도록 하겠다.