REST API (백엔드)
- REST API는 백엔드에서 데이터를 처리하고 응답을 JSON으로 주는 방식.
- @RestController는 반환값이 html이 아니라 json이라는 걸 의미함.
- 사용자는 서버에 데이터를 요청하면, 서버는 JSON으로 응답을 돌려줌.
→ 예) /api/user/isExistsAccountName?accountName=abc 요청하면
{ "result": true } 같은 JSON 응답이 옴.
AJAX (프론트엔드)
- AJAX는 HTML 페이지를 새로고침하지 않고, 자바스크립트를 통해 서버에 비동기 요청을 보내는 기술.
- 과거엔 XMLHttpRequest를 썼지만, 지금은 fetch()로 많이 씀.
→ 즉, 사용자가 아이디를 입력하면 fetch()로 서버에 REST API 요청 → JSON 응답 받기
→ 페이지 새로고침 없이 바로 결과 보여줌!
📘 추가 개념 정리: 웹브라우저 역할 & @ResponseBody / @RestController
💡 왜 아이디 중복 확인은 백엔드에서 해야 할까?
- 웹브라우저(프론트)는 HTML, CSS, JavaScript만 다룸 → DB 접근 못 함
- 아이디가 이미 있는지 확인하려면 → DB에서 SELECT COUNT(*) 같은 쿼리 필요
- 그래서 **백엔드 서버(Spring)**가 DB를 조회하고 결과를 알려줘야 함
📌 즉, 백엔드에 요청(Request)을 보내서 결과(Response)를 받아오는 구조 필요
@ResponseBody란?
- 이 어노테이션을 쓰면, **함수의 return 값이 HTML이 아니라 데이터(JSON)**로 클라이언트에 응답됨
- 보통 컨트롤러 메서드는 View 이름(html 파일 경로)을 리턴하는데,
- @ResponseBody가 붙으면 → return 값이 JSON, 문자열, 숫자 그대로 전송됨
@RestController란?
- @Controller + @ResponseBody를 합친 것
- 이 어노테이션을 클래스에 붙이면 → 이 안의 모든 메서드는 자동으로 JSON 리턴
- 매번 @ResponseBody 쓰기 귀찮을 때 클래스 단위로 처리 가능
👉 그래서 JSON API 만들 땐 대부분 @RestController를 씀
- 근데 리턴타입을 Map<String, Object>으로 많이 사용함
- 그 이유 : json은 key-value 구조다 → Java의 Map<String, Object>와 매우 유사함
- 이걸 Java에서 간단하게 표현하려면 Map이 가장 편하다:
그럼 이걸로 뭘 해야 돼?
- 사용자 입력값(아이디)을 백엔드 함수의 파라미터로 전달
- 백엔드는 이 값을 기반으로 DB에서 중복 여부를 확인
- 확인한 값을 JSON 형식으로 리턴
- 프론트는 JSON 응답을 받아서 JS로 사용자한테 메시지 보여줌
회원가입 아이디 중복 체크
register.html
<form id="registerForm" action="/user/registerProcess" method="post">
<!-- 한글이 들어갈 수 있음 -->
아이디 : <input onblur="confirmAccountName()" id="inputAccountName" name="accountName" type="text">
<input onclick="confirmAccountName()" type="button" value="아이디 중복확인">
<br>
<div id="accountnameErrorTextBox"></div>
button추가 중복확인!
RestController.java
package com.ca.finalproject.user.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ca.finalproject.user.service.UserServiceImpl;
@RestController //@Controller + @ResponseBody : body는 json이다
@RequestMapping("api/user")
public class RestUserController {
@Autowired
private UserServiceImpl userService;
@RequestMapping("isExistsAccountName")
public Map<String,Object> isExistsAccountName(@RequestParam("accountName") String accountName){
Map<String,Object> map = new HashMap<>();
map.put("result", userService.isExistsAccountName(accountName));
return map;
}
}
- @RestController: 반환값을 JSON 형태로 자동 변환
- @RequestParam("accountName"): 쿼리스트링에서 파라미터 받음
- Map<String,Object>로 { "result": true/false } 형태 반환
UserSqlMapper.java
//회원 존재 여부
public int countuserByAccountName(String accountName);
UserSqlMapper.xml
<select id="countuserByAccountName">
select count(*) from fp_user fu where fu.account_name=#{accountName}
</select>
register.html
function confirmAccountName(){
//아이디를 눌렀을때에 그 아이디가 필요한것?
const inputAccountName = document.getElementById("inputAccountName");
// inputAccountName.value; // 이 값ㅂ이 이미 가입한 녀석이냐?
//restapi를 이제 js로 호출할 것
//AJAX - js로 링크 변경 없이 페이지 내에서 백엔드에 요청(requeset) 하는 기술
fetch(`/api/user/isExistsAccountName?accountName=${inputAccountName.value}`)
.then(response=>response.json())
.then(json =>{ //여기가 reutnr된 값임
//여기서부터는 그냥 js 일반적인 능력 활용 필요!! 나중엔 여기가 죽어나갈 구간
// 백엔드 응답 후 여기부터 실행됨!!(비동기)
const accountnameErrorTextBox = document.getElementById("accountnameErrorTextBox")
if(json.result==true){
accountnameErrorTextBox.style.color="red";
accountnameErrorTextBox.innerText="이미 사용중인 아이디입니다."
isConfirmedAccountname=false;
}else{
accountnameErrorTextBox.style.color="green";
accountnameErrorTextBox.innerText="사용 가능한 아이디입니다."
isConfirmedAccountname=true;
}
})
;
}
- fetch()로 서버에 AJAX 방식의 요청을 보냄 (accountName=입력값)
- .then(response => response.json()): JSON 파싱
- .then(json => {...}): 결과에 따라 텍스트 색과 메시지 변경
좋아요기능 구현
-- 좋아요T
CREATE TABLE fp_like (
id INT PRIMARY KEY AUTO_INCREMENT ,
article_id INT,
user_id INT,
created_at DATETIME DEFAULT NOW()
);

LikeDto.java
package com.ca.finalproject.dto;
import java.time.LocalDateTime;
import lombok.Data;
@Data
public class LikeDto {
private int id;
private int articleId;
private int userId;
private LocalDateTime createdAt;
}
BoardSqlMapper.java
//좋이요
public void createLike(@Param("articleId") int articleId,@Param("userId") int userId);
public void deleteLike(@Param("articleId") int articleId,@Param("userId") int userId);
public int countByArticleIdAndUserId(@Param("articleId") int articleId,@Param("userId") int userId);
public int countByArticleId(@Param("articleId") int articleId);
BoardSqlMapper.xml
<!-- 좋아요 -->
<insert id="createLike">
Insert into fp_like(article_id,user_id) VALUES(#{articleId},#{userId});
</insert>
<delete id="deleteLike">
delete from fp_like where article_id=#{articleId}, and user_id=#{userId};
</delete>
<select id="countByArticleIdAndUserId">
select count(*) from fp_like fl where fl.article_id=#{articleId} and fl.user_id=#{userId};
</select>
<select id="countByArticleId">
select count(*) from fp_like fl where fl.article_id=#{articleId}
</select>
BoardServiceImpl.java
//좋아요
public void like(int articleId, int userId){
boardSqlMapper.createLike(articleId, userId);
}
public void unlike(int articleId, int userId){
boardSqlMapper.deleteLike(articleId, userId);
}
public boolean isMyLike(int articleId,int userId){
return boardSqlMapper.countByArticleIdAndUserId(articleId, userId)>0;
}
public int getTotalLikeCount(int articleId){
return boardSqlMapper.countByArticleId(articleId);
}
RestBoardController.java
package com.ca.finalproject.board.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ca.finalproject.board.service.BoardServiceImpl;
import com.ca.finalproject.dto.UserDto;
import jakarta.servlet.http.HttpSession;
@RestController
@RequestMapping("api/board")
public class RestBoardController {
@Autowired
private BoardServiceImpl boardService;
@RequestMapping("like")
public Map<String,Object> like(HttpSession session, @RequestParam("articleId") int articleId){
UserDto sessionUserInfo = (UserDto)session.getAttribute("sessionUserInfo");
boardService.like(articleId, sessionUserInfo.getId());
Map<String,Object> map = new HashMap<>();
map.put("result", "success");
return map;
}
@RequestMapping("unlike")
public Map<String,Object> unlike(HttpSession session, @RequestParam("articleId") int articleId){
UserDto sessionUserInfo = (UserDto)session.getAttribute("sessionUserInfo");
boardService.unlike(articleId, sessionUserInfo.getId());
Map<String,Object> map = new HashMap<>();
map.put("result", "success");
return map;
}
@RequestMapping("isLiked")
public Map<String, Object> isLiked(HttpSession session, @RequestParam("articleId") int articleId){
Map<String, Object> map = new HashMap<>();
UserDto sessionUserInfo = (UserDto)session.getAttribute("sessionUserInfo");
map.put("result", boardService.isMyLike(articleId, sessionUserInfo.getId()));
return map;
}
@RequestMapping("totalLikeCount")
public Map<String,Object> totalLikeCount(@RequestParam("articleId") int articleId){ //이경우는 세션 필요없음 로그인 하지 않아도 카운트가 보여야하니까..
Map<String, Object> map = new HashMap<>();
map.put("result", boardService.getTotalLikeCount(articleId));
return map;
}
}
readArticle.html
- <script>안에다가 넣기
function like(){
//로그인 안 했을때
if(mySessionId==null){ //null인 경우는 로그인 하지않았따
//alert과 비슷한데 버튼이 두개 나옴
if(confirm("로그인 후 이용 가능합니다. 로그인 페이지로 넘어갈래?")){
location.href="/user/loginPage";
}
return;
}
//로그인 했을 때 : like insert하면됨
fetch(`/api/board/like?articleId=${articleId}`)
.then(response=> response.json())
.then(json=>{
renderHeart();
renderTotalCount();
});
}
function unlike(){
//로그인 했을 때 : like insert하면됨
fetch(`/api/board/unlike?articleId=${articleId}`)
.then(response=> response.json())
.then(json=>{
renderHeart();
renderTotalCount();
});
}
function renderTotalCount(){
fetch(`/api/board/totalLikeCount?articleId=${articleId}`)
.then(response=>response.json())
.then(json=>{
const countBox= document.getElementById("countBox");
countBox.innerText=json.result;
});
}
function renderHeart(){
fetch(`/api/board/isLiked?articleId=${articleId}`)
.then(response=>response.json())
.then(json=>{
const heartBox = document.getElementById("heartBox");
if(json.result == true){
heartBox.classList.add("bi-heart-fill");
heartBox.classList.remove("bi-heart");
heartBox.setAttribute("onclick","unlike()");
}else{
heartBox.classList.remove("bi-heart-fill") ;
heartBox.classList.add("bi-heart");
heartBox.setAttribute("onclick","like()");
}
});
}
window.addEventListener("DOMContentLoaded",()=>{
//여기가 사실상 시작점
setMySessionId();
renderTotalCount();
});
- body에다가 넣기
<!-- 추천 -->
<div>
<i id="heartBox" onclick="like()" class="bi bi-heart text-danger"></i> <span id="countBox"></span>
</div>'중앙정보기술인재개발원 > Spring' 카테고리의 다른 글
| [Spring] | final project 댓글 구현 (1) | 2025.07.10 |
|---|---|
| [Spring] 카카오페이 단건결제 API (2) | 2025.06.21 |
| [Spring] final project_2차 (0) | 2025.05.12 |
| [Spring] | 세션,쿠키,Session,Cookie (0) | 2025.05.02 |
| [Spring] final project_4 (0) | 2025.05.02 |