AJAX(Asynchronous JavaScript and XML)는 웹 페이지에서 비동기적으로 서버와 데이터를 주고받는 기술입니다. 페이지를 새로고침하지 않고도 서버와 통신해 데이터를 주고받을 수 있어, 웹 애플리케이션의 사용자 경험을 개선하는 데 사용됩니다.
AJAX의 핵심 개념:
- 비동기 통신: 서버로부터 데이터를 받아오거나 전송하는 작업을 백그라운드에서 수행하므로, 웹 페이지를 다시 로드하지 않고도 서버와 상호작용할 수 있습니다.
- XMLHttpRequest 객체: AJAX 요청을 보내고 받는 데 사용하는 JavaScript 객체입니다. 이를 통해 서버와의 통신을 제어할 수 있습니다. 최근에는
fetchAPI가 많이 사용되면서 XML보다는 JSON을 주로 사용하게 되었습니다.
** AJAX 통신을 이용한 댓글 삭제 처리 **
1. 요청 준비: 클라이언트(브라우저)에서 XMLHttpRequest 또는 fetch API를 사용해 서버로 요청을 보냅니다.
detail.mustache
{{>layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/api/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{{model.content}}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
{{#model.replies}}
<!-- 댓글아이템 -->
<div id="reply-{{id}}" class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">{{username}}</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<button onclick="deleteReply('{{id}}')" type="button" class="btn">🗑</button>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
async function deleteReply(id){
let response = await fetch(`/api/reply/${id}`, {
method: "delete"
});
let responseBody = await response.json(); // 파싱
if(response.ok){
$(`#reply-${id}`).remove();
}else{
alert(responseBody.msg);
}
}
</script>
{{>layout/footer}}댓글 아이템 마다 각각의 id를 붙여주고 이를 버튼을 클릭할 때마다 요청을 보내도록 구현한다.
fetch(
url ,{method, 타입 등})을 스크립트로 전송한다.2. 서버 응답: 서버는 클라이언트로부터 받은 요청을 처리하고, 그에 따른 데이터를 응답합니다. 이 데이터는 XML, JSON, HTML 등의 형태일 수 있습니다.
Controller
@RequiredArgsConstructor
@Controller
public class ReplyController {
private final HttpSession session;
private final ReplyService replyService;
@DeleteMapping("/api/reply/{id}")
public ResponseEntity<?> delete(@PathVariable("id") Integer id){
// 1. 인증 체크 -> 주소 체크
// 2. 서비스 호출 -> 댓글 삭제
User sessionUser = (User) session.getAttribute("sessionUser");
replyService.댓글삭제(id, sessionUser);
// 3. 응답
return ResponseEntity.ok(Resp.ok(null));
// throw new ExceptionApi403("권한이 없습니다");
}
}AJAX 는 데이터 타입으로 return을 받아야 하기 때문에 ResoponseBody나 ResponseEntity로 받는다.
Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service // IOC 등록
public class ReplyService {
private final ReplyRepository replyRepository;
@Transactional
public void 댓글삭제(int id, User sessionUser){
Reply replyPS = replyRepository.findById(id)
.orElseThrow(()-> new ExceptionApi404("해당 댓글을 찾을 수 없습니다")
);
if(replyPS.getUser().getId() != sessionUser.getId()){
throw new ExceptionApi403("댓글 삭제 권한이 없습니다");
}
replyRepository.deleteById(id);
}
}Repository
package org.example.springv3.reply;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReplyRepository extends JpaRepository<Reply, Integer> {
}
3. 데이터 처리: 클라이언트는 받은 데이터를 처리하여 웹 페이지에 동적으로 반영합니다.
이후 Controller에서 ResponseEntity.ok(Resp.ok(null))를 클라이언트에서 데이터를 받고 스크립트를 실행해
if(response.ok){
$(`#reply-${id}`).remove();
}else{
alert(responseBody.msg);
}응답이 ok 가 온다면 댓글이 삭제되고, 아니면 Exception 처리한 메시지가 뜰 것이다.
Exception 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
// 유효성 검사 실패 (잘못된 클라이언트의 요청)
@ExceptionHandler(Exception400.class)
public String ex400(Exception e) {
return Script.back(e.getMessage());
}
// 인증 실패 (클라이언트가 인증없이 요청했거나, 인증을 하거나 실패했거나)
@ExceptionHandler(Exception401.class)
public String ex401(Exception e) {
return Script.href("인증되지 않았습니다", "/login-form");
}기존의 Exception 처리는 위와 같이 인증 메시지나 주소로 return을 받았다.
하지만 AJAX에서는 데이터 타입으로 return을 받아야 하기 때문에 이를 다른 방식으로 처리하여야 한다.
다음은 예외처리를 위한 클래스를 생성한 것이다
Resp.java
package org.example.springv3.core.util;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class Resp<T> {
private Integer status;
private String msg;
private T body;
public static <B> Resp<?> ok(B body){
return new Resp<>(200, "성공", body);
}
public static Resp<?> fail(Integer status, String msg){
return new Resp<>(status, msg, null);
}
}status와 msg를 만들어 상태 코드와 그에 따른 오류 메시지 등을 묶어서 응답 처리에 용이하도록 만든 클래스다.
@RestControllerAdvice
public class GlobalApiExceptionHandler {
// 유효성 검사 실패 (잘못된 클라이언트의 요청)
@ExceptionHandler(ExceptionApi400.class)
public ResponseEntity<?> ex400(Exception e) {
return new ResponseEntity<>(Resp.fail(400, e.getMessage()), HttpStatus.BAD_REQUEST);
}
// 인증 실패 (클라이언트가 인증없이 요청했거나, 인증을 하거나 실패했거나)
@ExceptionHandler(ExceptionApi401.class)
public ResponseEntity<?> ex401(Exception e) {
return new ResponseEntity<>(Resp.fail(401, e.getMessage()), HttpStatus.UNAUTHORIZED);
}작성한 클래스를 새로 만든 ExceptionHandler에 return으로 new해준다.
이렇게 작성하면 AJAX통신으로 예외처리가 기존의 자바스크립트로 alert하는 형식이 아닌 데이터를 전송하는 방식으로 가능해진다.
결론) AJAX 통신을 이용하면 서버의 전체적인 부하가 줄어들고, 화면의 부분을 리로딩하며 비동기 처리를 하기 떄문에 사용자 입장에서도 사용하기 편하다.
Share article