여기서 정리하는 것은 블로그만드는 프로젝트를 하면서 개인적으로 블로그 분해 공부를 하려고 정리하려고 합니다.
타임리프도 추가로 공부했기 때문에 view단 소스도 적을 예정이지만 css, js는 git 주소 링크로 올릴 생각입니다.
use study01;
create table user(
userId varchar(300) primary key ,
userPw varchar(300) not null ,
userName varchar(300) not null
);
create table board(
boardNum bigint primary key auto_increment,
boardTitle varchar(300) not null ,
boardContents varchar(6000) ,
userId varchar(300),
regDate datetime default now(),
updateDate datetime default now(),
constraint board_id_fk foreign key (userId) references user(userId)
);
select * from board;
select * from user;
drop table board;
insert into board (boardTitle, boardContents, userId)
values ('테스트 제목1', 'apple이 작성한 테스트 내용1', 'apple'),
('테스트 제목2', 'banana이 작성한 테스트 내용2', 'banana'),
('테스트 제목3', 'cheery이 작성한 테스트 내용3', 'cheery'),
('테스트 제목4', 'durian이 작성한 테스트 내용4', 'durian');
insert into user
values ("cheery", "zxzz12", "김체리"),
("durian","zxzz12", "듀리안");
insert into board (boardTitle, boardContents, userId) (select boardTitle, boardContents,userId from board);
home
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="/css/main.css" rel="stylesheet" />
</head>
<body class="is-preload">
<!-- Header -->
<header id="header" class="alt" >
<a class="logo" href="/">Boot<span>Board</span></a>
<nav id="nav">
<ul>
<th:block th:if="${loginUser == null}">
<li><a th:href="@{/}">Home</a> </li>
<li><a th:href="@{/user/signUp}">Join</a> </li>
<li><a th:href="@{/user/login}">Login</a> </li>
</th:block>
<th:block th:unless="${loginUser == null}">
<li><span th:text="${loginUser}"></span>님 어서오세요</li>
<li class="current"><a href="/">Home</a> </li>
<li><a href="/board/list">Board</a> </li>
<li><a href="/board/logout">Logout</a> </li>
<li></li>
</th:block>
</ul>
</nav>
</header>
<!-- Banner -->
<div id="banner">
<div class="wrapper style1 special">
<div class="inner">
<h1 class="heading alt">스프링 게시판</h1>
<p>스프링 최종 예제</p>
<div class="image fit special">
<img src="https://images.unsplash.com/photo-1657497850516-de7b59ac243b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1160&q=80" alt="" />
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="/js/jquery.min.js"></script>
<script src="/js/jquery.dropotron.min.js"></script>
<script src="/js/browser.min.js"></script>
<script src="/js/breakpoints.min.js"></script>
<script src="/js/util.js"></script>
<script src="/js/main.js"></script>
</body>
</html>
UserController
@GetMapping("/user/signUp")
public String signUp() {
return "/user/signUp";
}
이 GetMapping
으로 인해 get방식으로 SignUp 페이지, 즉 회원가입 페이지를 보여줍니다.
signUp
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>SignUp</title>
<link href="/css/signUp.css" rel="stylesheet" />
<link href="/css/common.css" rel="stylesheet" />
</head>
<body>
<div class="wrapper">
<h1>SignUp</h1>
<form action="/user/signUp" id="signUp" class="signUp" name="signUp" method="post">
<div class="signInfo">
<label for="userId">
<input type="text" id="userId" name="userId" placeholder="아이디">
</label>
</div>
<div class="signInfo">
<label for="userPw">
<input type="password" id="userPw" name="userPw" placeholder="비밀번호">
</label>
</div>
<div class="signInfo">
<label for="userName">
<input type="text" id="userName" name="userName" placeholder="이름">
</label>
</div>
<div>
<button type="submit" value="회원가입" class="btnSignUp">회원가입 </button>
</div>
</form>
</div>
</body>
</html>
여기까지는 잘되다가 회원가입을 누르니 갑자기 500번
에러가 발생!
MyBatis 오류: Invalid bound statement (not found)
이 에러가 발생한 이유가 뭘까?
그건 mapper 인터페이스나 xml 오타나 mapper-locations 경로를 안써줘서 에러가 발생한것이다.
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.board.mapper
이거를 작성해주니 제대로 실행이 된다.
UserController
@PostMapping("/user/signUp")
public String signUp(UserDTO user, HttpServletResponse resp) {
if (service.singUp(user)) {
Cookie cookie = new Cookie("userId", user.getUserId());
cookie.setMaxAge(300);
resp.addCookie(cookie);
}
return "redirect:/";
}
이제는 회원가입을 하기 위해 post로 보내줬습니다.
그러면 controller에서 post방식으로 메소드 안에 있는 service의 signUp메소드를 실행하고 true면 if문을 실행해서 cookie 안에 아이디를 담아줍니다. cookie에 담아주기 위해 HttpRServletResponse resp
메소드를 사용했습니다. return "redirect:/";
로 메인 페이지로 보냈습니다.
UserController
@GetMapping("/user/login")
public String login() {
return "/user/login";
}
login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" >
<title>Login</title>
<link href="/css/common.css" rel="stylesheet">
<link href="/css/login.css
" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<h1>login</h1>
<form id="login" action="/user/login" name="login" method="post" >
<input id="userId" type="text" name="userId" placeholder="아이디를 입력하세요">
<input id="userPw" type="password" name="userPw" placeholder="비밀번호를 입력하세요">
<button class="btnLogin" type="submit" value="로그인" >로그인 </button>
</form>
<div>
<a href="#" class="lost_pw">비밀번호를 잊으셨나요?</a>
</div>
</div>
<script src="/js/login.js"></script>
</body>
</html>
UserController
@PostMapping("/user/login")
public String login(@RequestParam("userId") String userId, @RequestParam("userPw") String userPw, HttpServletRequest req, Model model) {
HttpSession session = req.getSession();
UserDTO user = service.login(userId, userPw);
if(user != null) {
session.setAttribute("userId", user.getUserId());
model.addAttribute("loginUser", session.getAttribute("userId"));
}
return "home";
}
로그인을 하면 세션을 사용하기 위해서 HttpServletRequest req
사용 하고 화면에 보여주기 위해서 Model model
메소드를 사용했습니다.
그러면 이제 로그인이 성공하면 다음과 같은 화면이 나옵니다.
원래 화면은
이거 였는데 왜 바뀌었을까?
<nav id="nav">
<ul>
<th:block th:if="${loginUser == null}">
<li><a th:href="@{/}">Home</a> </li>
<li><a th:href="@{/user/signUp}">Join</a> </li>
<li><a th:href="@{/user/login}">Login</a> </li>
</th:block>
<th:block th:unless="${loginUser == null}">
<li><span th:text="${loginUser}"></span>님 어서오세요</li>
<li class="current"><a href="/">Home</a> </li>
<li><a href="/board/list">Board</a> </li>
<li><a href="/board/logout">Logout</a> </li>
<li></li>
</th:block>
</ul>
</nav>
이 코드 때문인데, 로그인에 담긴 정보가 없으면
이렇게 보여주고
담긴 정보가 있으면
model에 담아 줬던 "lognUser"가 th:text를 통해 화면에 나오는 겁니다.
UserController
@GetMapping("/user/logout")
public String logout(HttpServletRequest req) {
req.getSession().invalidate();
return "home";
}
지금 흐름을 보면 회원가입할때는 쿠키에 로그인할 때는 세션에 담아주었습니다. 그리고 logout은 session을 전부 지워주고 home으로 돌아가게 하는 방식으로 진행하고 있습니다.
JPA 방식으로 진행하겠습니다.
home
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light " style="background-color: #F2D7D5;">
<div class="container-fluid">
<a class="navbar-brand" href="#">🌺</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</div>
</div>
</div>
</nav>
<div class="container">
<div class="row mt-3">
<div class="card text-center">
<h5 class="card-header">Study make user & board</h5>
<div class="card-body">
<h5 class="card-title">hello! 😀 </h5>
<a th:href="@{/user/save}" >회원가입</a><br/>
<a th:href="@{/user/login}">로그인</a><br/>
<a th:href="@{/user/}">회원목록 </a><br/>
<a th:href="@{/user/update}">내 정보 수정하기</a><br/>
session 값 확인: <p th:text="${session.loginEmail}"></p>
<a th:href="@{/user/logout}">로그아웃</a>
</div>
</div>
</div>
</div>
</body>
</html>
HomeController
@GetMapping({"/", "/home"})
public String home() {
return "home";
}
이제는 user에 관한 것을 컨트롤해줄 컨트롤러를 만들려고 합니다.
UserController
@Controller
@Log4j2
@RequiredArgsConstructor
public class UserController {
@GetMapping("/user/login")
public String loginForm() {
return "/user/login";
}
// 회원가입 페이지 출력 요청
@GetMapping("/user/save")
public String saveForm(Model model) {
return "/user/save";
}
login
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="/user/login" name="login" method="post">
<input id="userEmail" type="text" name="userEmail" placeholder="이메일을 입력하세요"> <br/>
<input id="userPw" type="password" name="userPw" placeholder="비밀번호를 입력하세요"> <br/>
<input class="btnLogin" type="submit" value="로그인" />
</form>
</body>
</html>
save
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>save</title>
</head>
<body>
<h1>Save</h1>
<form action="/user/save" name="save" method="post">
<span id="check-result"></span><br/>
<label>
이메일 :
<input type="text" name="userEmail" id="userEmail" onblur="emailCheck()"/> <br/>
</label>
<label>
비밀번호 :
<input type="password" name="userPw"/> <br/>
</label>
<label>
이름 :
<input type="text" name="userName"/> <br/>
</label>
<input type="submit" value="회원가입">
</form>
</body>
<script th:inline="javascript">
const emailCheck = () => {
const email = document.getElementById("userEmail").value;
const checkResult = document.getElementById("check-result");
console.log("입력값 : ", email);
$.ajax({
// 요청 방식 : post, url: "email-check", 데이터: 이메일
type: "post",
url: "/user/email-check",
data: {
"userEmail" : email
},
success: function (res) {
console.log("요청성공 : ", res);
if(res === "ok") {
console.log("사용가능한 이메일");
checkResult.style.color = "blue";
checkResult.innerHTML = "사용가능한 이메일";
} else {
console.log("이미 사용중인 이메일");
checkResult.style.color = "red";
checkResult.innerHTML = "이미 사용중인 이메일";
}
},
error: function (err) {
console.log("에러발생 : ", err);
}
})
}
</script>
</html>
여기서 이메일 중복체크를 하기위해서 Ajax를 사용했습니다. ajax를 사용한 이유는 보통 중복체크의 경우 해당 페이지에서 이미 사용중인 아이디입니다
또는 사용 가능한 아이디입니다
이렇게 뜨기 때문에 해당 화면에서 즉각적으로 보여주는 ajax를 사용했습니다.
<label>
이메일 :
<input type="text" name="userEmail" id="userEmail" onblur="emailCheck()"/> <br/>
</label>
이걸 통해 이벤트가 일어나면 자바스크립트를 실행합니다. onblur
은 이메일 창에서 벗어나서 다른 창으로 넘어가면 그 값이 이벤트입니다.
<span id="check-result"></span><br/>
여기가 이미 사용중인 아이디입니다
또는 사용 가능한 아이디입니다
보여주는 곳입니다.
<script th:inline="javascript">
const emailCheck = () => {
const email = document.getElementById("userEmail").value;
const checkResult = document.getElementById("check-result");
console.log("입력값 : ", email);
$.ajax({
// 요청 방식 : post, url: "email-check", 데이터: 이메일
type: "post",
url: "/user/email-check",
data: {
"userEmail" : email
},
success: function (res) {
console.log("요청성공 : ", res);
if(res === "ok") {
console.log("사용가능한 이메일");
checkResult.style.color = "blue";
checkResult.innerHTML = "사용가능한 이메일";
} else {
console.log("이미 사용중인 이메일");
checkResult.style.color = "red";
checkResult.innerHTML = "이미 사용중인 이메일";
}
},
error: function (err) {
console.log("에러발생 : ", err);
}
})
}
</script>
여기서 th:inline="javascript"
타임리프라서 이렇게 작성해준 것입니다.
id가 userEmail인 값을 가지고 옵니다.
id가 check-result인 것을 불러옵니다.
UserDTO
package com.example.web_sty.dto;
import com.example.web_sty.entity.UserEntity;
import lombok.*;
@Getter
@Setter
@ToString
// 기본 생성자를 만들어줌
@NoArgsConstructor
// 필드를 모두 매개변수로 하는 생성자를 만들어준다.
@AllArgsConstructor
public class UserDTO {
private Long id;
private String userEmail;
private String userPw;
private String userName;
public static UserDTO toUserDTO(UserEntity userEntity) {
UserDTO userDTO = new UserDTO();
userDTO.setId(userEntity.getId());
userDTO.setUserEmail(userEntity.getUserEmail());
userDTO.setUserPw(userEntity.getUserPw());
userDTO.setUserName(userEntity.getUserName());
return userDTO;
}
}
UserService
@Service
@RequiredArgsConstructor
public class userService {
private final UserRepository userRepository;
public void save(UserDTO userDTO) {
// 1. dto → entity 변환
// 2. repository의 save 메서드 호출
UserEntity userEntity = UserEntity.toUserEntity(userDTO);
userRepository.save(userEntity);
// repository의 save 매서드 호출 (조건 : entity객체를 넘겨줘야 함)
}
public UserDTO login(UserDTO userDTO) {
/*
* 1. 회원이 입력한 이메일로 DB에서 조회를 함
* 2. DB에서 조회한 비밀번호와 사용자가 입력한 비밀번호가 일치하는지 판단
* */
// Optional로 감싸준다.
Optional<UserEntity> byUserEmail = userRepository.findByUserEmail(userDTO.getUserEmail());
if(byUserEmail.isPresent()) {
// 조회 결과가 있다.(해당 이메일을 가진 회원 정보가 있다)
// Optional로 감싸준것을 까준다.
UserEntity userEntity = byUserEmail.get();
if(userEntity.getUserPw().equals(userDTO.getUserPw())) {
// 비밀번호 일치
// entity → dto 변환후 리턴
UserDTO dto = UserDTO.toUserDTO(userEntity);
return dto;
} else {
// 비밀번호 불일치(로그인 실패)
return null;
}
} else {
// 조회 결과가 없다(해당 이메일을 가진 회원이 없다)
return null;
}
}
...
UserRepository
package com.example.web_sty.repository;
import com.example.web_sty.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<UserEntity, Long> {
// 이메일로 회원 정보 조회 (select * from user where user_email=?)
// Optional : null 방지, null이여도 에러가 발생하지 않는다.
Optional<UserEntity> findByUserEmail(String userEmail);
}
@PostMapping("/user/save")
public String save(@ModelAttribute UserDTO userDTO) {
log.info("userDTO = " + userDTO);
userService.save(userDTO);
return "/user/login";
}
@PostMapping("/user/login")
// session 사용 : HttpSession
public String login(@ModelAttribute UserDTO userDTO, HttpSession session) {
UserDTO loginResult = userService.login(userDTO);
if(loginResult != null) {
// login 성공
session.setAttribute("loginEmail", loginResult.getUserEmail());
log.info("userEmail : " + loginResult.getUserEmail());
log.info("userPw : " + loginResult.getUserPw());
return "/home";
} else {
// login 실패
return "/user/login";
}
}
login한 이메일을 session을 사용하기 위해서 HttpSession session 메소드를 만들어서 거기에 넣어줍니다. 그리고 성공하면 /home
으로 실패하면 /user/login
으로 보내줍니다.
// 생성자 주입
private final userService userService;
...생략...
@GetMapping("/user/")
public String findAll(Model model) {
List<UserDTO> userDTOList = userService.findAll();
// 어떠한 html로 가져갈 데이터가 있다면 model사용
model.addAttribute("userList", userDTOList);
return "user/list";
}
/user/로 url이 날라오면 list를 보여줍니다.
UserService
public List<UserDTO> findAll() {
List<UserEntity> userEntityList = userRepository.findAll();
List<UserDTO> userDTOList = new ArrayList<>();
for (UserEntity userEntity: userEntityList
) {
userDTOList.add(UserDTO.toUserDTO(userEntity));
}
return userDTOList;
}
@GetMapping("/user/{id}")
// 경로상의 값(/user/{id})를 가져올 때는 @PathVariable을 쓴다.
public String findById(@PathVariable Long id, Model model) {
UserDTO userDTO = userService.findById(id);
model.addAttribute("user", userDTO);
return "/user/detail";
}
이거는 상세정보를 보여주기 위한거이기 때문에 list.html에서
이렇게 보내주는데 이거는 rest api 형식으로 보내주는 것이다. 이걸 받기 위해서 @GetMapping("/user/{id}")
으로 받는데 이렇게 받아온 것은 @PathVariable
로 받아야 합니다.
UserService
public UserDTO findById(Long id) {
// 한개를 조회할때는 Optional로 감싸준다.
Optional<UserEntity> byId = userRepository.findById(id);
if(byId.isPresent()){
// UserEntity userEntity = byId.get();
// UserDTO userDTO = UserDTO.toUserDTO(userEntity);
// return userDTO;
return UserDTO.toUserDTO(byId.get());
} else {
return null;
}
}
detail
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>detail</title>
</head>
<body>
<table>
<tr>
<th>id</th>
<th>email</th>
<th>password</th>
<th>name</th>
</tr>
<tr>
<td th:text="${user.id}"></td>
<td th:text="${user.userEmail}"></td>
<td th:text="${user.userPw}"></td>
<td th:text="${user.userName}"></td>
</tr>
<p>
<a th:href="@{/}">Home으로 가기</a>
</p>
</table>
</body>
</html>
@GetMapping("/user/update")
public String updateForm(HttpSession session, Model model) {
String myEmail = (String) session.getAttribute("loginEmail");
if(myEmail != null) {
UserDTO userDTO = userService.updateForm(myEmail);
model.addAttribute("updateUser", userDTO);
return "/user/update";
} else {
return "redirect:/";
}
}
@PostMapping("/user/update")
public String update(@ModelAttribute UserDTO userDTO) {
userService.update(userDTO);
return "redirect:/user/" + userDTO.getId();
}
UserService
public UserDTO updateForm(String myEmail) {
Optional<UserEntity> byUserEmail = userRepository.findByUserEmail(myEmail);
if(byUserEmail.isPresent()) {
return UserDTO.toUserDTO(byUserEmail.get());
} else {
return null;
}
}
update
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>update</title>
</head>
<body>
<form action="/user/update" method="post">
<input type="hidden" th:value="${updateUser.id}" name="id"><br>
이메일: <input type="text" th:value="${updateUser.userEmail}" name="userEmail" readonly> <br>
비밀번호: <input type="text" th:value="${updateUser.userPw}" name="userPw"> <br>
이름: <input type="text" th:value="${updateUser.userName}" name="userName"> <br>
<input type="submit" value="정보수정">
</form>
</body>
</html>
@GetMapping("/user/delete/{id}")
public String deleteById(@PathVariable Long id) {
userService.deleteById(id);
return "redirect:/user/";
}
@GetMapping("/user/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/";
}
@PostMapping("/user/email-check")
// ajax를 쓸 때는 반드시 @ResponseBody를 써야한다.
public @ResponseBody String emailCheck(@RequestParam("userEmail") String userEmail) {
log.info("userEmail : " + userEmail);
String checkResult = userService.emailCheck(userEmail);
return checkResult;
// if(checkResult != null) {
// return "ok!";
// } else {
// return "no!";
// }
}
}
UserService
package com.example.web_sty.service;
import com.example.web_sty.dto.UserDTO;
import com.example.web_sty.entity.UserEntity;
import com.example.web_sty.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class userService {
private final UserRepository userRepository;
public void save(UserDTO userDTO) {
// 1. dto → entity 변환
// 2. repository의 save 메서드 호출
UserEntity userEntity = UserEntity.toUserEntity(userDTO);
userRepository.save(userEntity);
// repository의 save 매서드 호출 (조건 : entity객체를 넘겨줘야 함)
}
public UserDTO login(UserDTO userDTO) {
/*
* 1. 회원이 입력한 이메일로 DB에서 조회를 함
* 2. DB에서 조회한 비밀번호와 사용자가 입력한 비밀번호가 일치하는지 판단
* */
// Optional로 감싸준다.
Optional<UserEntity> byUserEmail = userRepository.findByUserEmail(userDTO.getUserEmail());
if(byUserEmail.isPresent()) {
// 조회 결과가 있다.(해당 이메일을 가진 회원 정보가 있다)
// Optional로 감싸준것을 까준다.
UserEntity userEntity = byUserEmail.get();
if(userEntity.getUserPw().equals(userDTO.getUserPw())) {
// 비밀번호 일치
// entity → dto 변환후 리턴
UserDTO dto = UserDTO.toUserDTO(userEntity);
return dto;
} else {
// 비밀번호 불일치(로그인 실패)
return null;
}
} else {
// 조회 결과가 없다(해당 이메일을 가진 회원이 없다)
return null;
}
}
public List<UserDTO> findAll() {
List<UserEntity> userEntityList = userRepository.findAll();
List<UserDTO> userDTOList = new ArrayList<>();
for (UserEntity userEntity: userEntityList
) {
userDTOList.add(UserDTO.toUserDTO(userEntity));
}
return userDTOList;
}
public UserDTO findById(Long id) {
// 한개를 조회할때는 Optional로 감싸준다.
Optional<UserEntity> byId = userRepository.findById(id);
if(byId.isPresent()){
// UserEntity userEntity = byId.get();
// UserDTO userDTO = UserDTO.toUserDTO(userEntity);
// return userDTO;
return UserDTO.toUserDTO(byId.get());
} else {
return null;
}
}
public UserDTO updateForm(String myEmail) {
Optional<UserEntity> byUserEmail = userRepository.findByUserEmail(myEmail);
if(byUserEmail.isPresent()) {
return UserDTO.toUserDTO(byUserEmail.get());
} else {
return null;
}
}
public void update(UserDTO userDTO) {
userRepository.save(UserEntity.toUserEntity(userDTO));
}
public void deleteById(Long id) {
userRepository.deleteById(id);
}
public String emailCheck(String userEmail) {
Optional<UserEntity> byUserEmail = userRepository.findByUserEmail(userEmail);
if(byUserEmail.isPresent()) {
// 조회결과가 있다 → 사용x
return null;
} else {
// 조회결과가 없다 → 사용o
return "ok";
}
}
}