ㅁ ajax는 포워드도 redirect도 아니다.
ㅁ src/main/webapp/WEB-INF/views에 member 폴더 생성
- member 폴더에 manage1.jsp, manage2.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>회원관리 1번 페이지</h3>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>회원관리 2번 페이지</h3>
</body>
</html>
ㅁ main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>메인페이지 입니다</h3>
<a href="${contextPath}/member/manage1.do">회원관리1</a> <br>
<a href="${contextPath}/member/manage2.do">회원관리2</a> <br>
</body>
</html>
- url mapping값이 ~다. controller 클래스가 필요하다.
- ".do"를 빼면 ~와 비슷하다.
ㅁ MvcController
package com.br.ajax.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MvcController {
private Logger logger = LoggerFactory.getLogger(MvcController.class);
@RequestMapping(value= {"/", "/main.do"})
public String mainPage() {
// 출력문은 성능저하를 야기시킴 => 로그 출력 권장
// System.out.println("MvcController의 mainPage 메소드 작동됨");
/*
// 다음과 같은 레벨로 다음과 같은 메세지가 출력되도록 작성. 각각의 레벨별로 출력.
logger.trace("trace msg");
logger.debug("debug msg");
logger.info("info msg");
logger.warn("warn msg");
logger.error("error msg");
// slf4j는 fatal은 없다.
*/
return "main";
}
@GetMapping("/member/manage1.do")
public void memberManagePage1() {}
@GetMapping("/member/manage2.do")
public void memberManagePage2() {}
}
- 메인 페이지 요청할때마다 매번 출력되니까 로그는 주석처리한다.
- @RequestMapping보다 @GetMapping, @PostMapping을 쓰는 습관 들이기.
- 리턴한다면 member/manage1, member/manage2를 리턴한다. 맞나?
ㅁ manage1.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>회원관리 1번 페이지</h3>
<form id="mem-form">
번호 : <input type="text" name="userNo"> <br>
아이디 : <input type="text" name="userId"> <br>
비밀번호 : <input type="text" name="userPwd"> <br>
<button type="button">조회1(아이디, 비번으로 이름 조회)</button>
<button type="button">조회2(아이디, 비번으로 이름 조회)</button>
<button type="button">조회3(번호로 회원전체정보 조회)</button>
<button type="button">전체조회</button>
<br>
<button type="button" onclick="">번외1</button>
<button type="button" onclick="">번외2</button>
</form>
<hr>
<div id="result">
결과가 보여지는 영역
</div>
</body>
</html>
- form 안에 버튼을 두지만 submit시키지 않고 함수를 실행시킬 예정이다.
ㅁ com.br.ajax.dto 패키지에 MemberDto 클래스 생성
package com.br.ajax.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class MemberDto {
private int userNo;
private String userId;
private String userPwd;
private String userName;
}
- db를 쓰진 않고 조회됐다는 가정하에 진행해본다.
- lombok 사용. 필드 가지고 생성자, 게터세터, toString 메소드가 만들어진다.
ㅁ com.br.ajax.service 패키지에 MemberService 인터페이스 생성
package com.br.ajax.service;
public interface MemberService {
// 아이디와 비번 가지고 특정 회원 이름 조회하는 서비스
String selectMemberByIdPwd(String userId, String userPwd);
// 번호 가지고 회원전체정보 조회하는 서비스
MemberDto selectMemberByNo(int userNo);
// 회원 전체 목록을 조회하는 서비스
List<MemberDto> selectMemberList();
}
ㅁ MemberServiceImpl 구현클래스 생성
package com.br.ajax.service;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
import com.br.ajax.dto.MemberDto;
@Service
public class MemberServiceImpl implements MemberService {
// 이게 db에 저장된 정보라고 가정
private List<MemberDto> list = Arrays.asList(
new MemberDto(1, "user01", "pass01", "홍길동")
, new MemberDto(2, "user02", "pass02", "김말똥")
, new MemberDto(3, "user03", "pass03", "강개순")
);
@Override
public String selectMemberByIdPwd(String userId, String userPwd) {
for(MemberDto m : list) {
if(m.getUserId().equals(userId) && m.getUserPwd().equals(userPwd)) {
return m.getUserName();
}
}
return null;
}
@Override
public MemberDto selectMemberByNo(int userNo) {
for(MemberDto m : list) {
if(m.getUserNo() == userNo) {
return m;
}
}
return null;
}
@Override
public List<MemberDto> selectMemberList() {
return list;
}
}
- @Service 어노테이션으로 빈 등록.
ㅁ com.br.ajax.controller 패키지에 MemberController1 클래스 생성
package com.br.ajax.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.ajax.service.MemberService;
import lombok.RequiredArgsConstructor;
@RequestMapping("/member1")
@RequiredArgsConstructor
@Controller
public class MemberController1 {
private final MemberService memService;
private Logger logger = LoggerFactory.getLogger(MemberController1.class);
@GetMapping("/detail1.do")
public void memberDetail(String id, String pwd) {
logger.debug("request id: {}, pwd: {}", id, pwd);
}
}
- @Controller 어노테이션으로 빈 등록.
- 롬복의 @RequiredArgsConstructor을 써서 final이 붙은 필드의 매개변수 생성자만 만든다.
- memberDetail메소드의 매개변수에 넘어오는 값의 키값과 동일하게 작성하면 @RequestParam을 안써도 됨.
- 로거에 printf문처럼 패턴을 작성할 수 있다.
%d, %f, %s 이런거 필요 없이 어떤 타입이든 가능하다. 문자든 실수든 객체든.
- 로그를 찍을 때 그냥 { }를 두는것 만으로 출력 형식을 설정할 수 있다.
ㅁ manage1.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="${contextPath}/resources/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<h3>회원관리 1번 페이지</h3>
<form id="mem-form">
번호 : <input type="text" name="userNo"> <br>
아이디 : <input type="text" name="userId"> <br>
비밀번호 : <input type="text" name="userPwd"> <br>
<button type="button" onclick="fn_ajax1()">조회1(아이디, 비번으로 이름 조회)</button>
<button type="button" onclick="">조회2(아이디, 비번으로 이름 조회)</button>
<button type="button" onclick="">조회3(번호로 회원전체정보 조회)</button>
<button type="button" onclick="">전체조회</button>
<br>
<button type="button" onclick="">번외1</button>
<button type="button" onclick="">번외2</button>
</form>
<hr>
<div id="result">
결과가 보여지는 영역
</div>
<script>
// 조회1 버튼 : 아이디랑 비번으로 이름을 조회
function fn_ajax1() {
let id = $("input[name=userId]").val();
let pwd = $("input[name=userPwd]").val();
$.ajax({
// 요청
url: "${contextPath}/member1/detail1.do",
data: "id=" + id + "&pwd=" + pwd, // 쿼리스트링으로 전달값 작성
type: "get",
// 응답
success: function(){
},
error: function(){
}
})
}
</script>
</body>
</html>
- jQuery라이브러리가 없다. 2번째 프로젝트의 src/main/webapp/assets/js의 jquery-3.7.1.min.js를 가져온다.
- ajax data 속성을 쿼리스트링, 객체 방식으로 할 수 있었다.
- type에 get이나 post를 쓸 수 있었다. 생략시 get이었다.
- success에는 통신에 성공했을 때 실행할 함수를, error에는 통신에 실패했을 때 실행할 함수를, complete에는 성공 여부와 상관없이 항상 실행할 함수를 작성한다.
- AJAX 요청 시 서버에서 돌아오는 응답 데이터는 success 콜백 함수의 첫 번째 인자로 전달되며, 이 함수 내에서 응답 데이터를 처리하거나 화면에 반영할 수 있습니다. 이를 통해 서버와 클라이언트 간의 비동기 통신이 가능합니다.
- 아이디와 비밀번호를 입력하고 '조회1(아이디, 비번으로 이름 조회)' 클릭시 콘솔에 로그가 찍힌다.
C:\logs의 ajax.log와 ajax-2024-10-18.0.log에도 저게 찍혀있다.
(ConsoleAppender, FileAppender, RollingFileAppender에 로그가 기록된다.)
ㅁ MemberController1 수정
package com.br.ajax.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.br.ajax.service.MemberService;
import lombok.RequiredArgsConstructor;
@RequestMapping("/member1")
@RequiredArgsConstructor
@Controller
public class MemberController1 {
private final MemberService memService;
private Logger logger = LoggerFactory.getLogger(MemberController1.class);
/* 기존의 HttpServletResponse 객체 이용하는 방식
@GetMapping("/detail1.do")
public void memberDetail(String id, String pwd, HttpServletResponse response) throws IOException {
logger.debug("request id: {}, pwd: {}", id, pwd);
String result = memService.selectMemberByIdPwd(id, pwd);
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.print(result);
}
*/
// Spring 방식
@GetMapping("/detail1.do")
public String memberDetail(String id, String pwd) {
logger.debug("request id: {}, pwd: {}", id, pwd);
String result = memService.selectMemberByIdPwd(id, pwd);
return result;
}
}
- ajax 요청은 forward도, redirect도 하면 안된다.
(1) 기존 방식
(2) Spring 방식
- 기본적으로 return result;로 문자열 리턴하면 포워딩하려 한다. 그래서 404 에러가 발생할 수 있다.
- return 하는 문자열이 응답 뷰가 아니라 응답 데이터라는 것을 알려줘야 한다.
- 메소드 상단에 @ResponseBody 어노테이션을 붙이면 반환값이 응답 뷰가 아니라 응답 데이터다.
- 응답 뷰가 아닌 응답 데이터를 반환하는 경우, 돌려보내는 데이터에 대한 타입을 써줘야 한다.
단순한 숫자나 영문이면 타입을 쓰지 않아도 상관없지만 한글 데이터는 깨질 수 있어서 무조건 써야 한다.
- @GetMapping, @PostMapping의 produces 속성에 응답 데이터에 대한 MIME 타입을 쓸 수 있다.
@GetMapping(value="/detail1.do", produces="text/html; charset=utf-8")
※ @ResponseBody
- 비동기식으로 데이터 응답시 필요한 어노테이션.
- 해당 어노테이션이 붙은 메소드에서의 반환 값은 응답 뷰가 아닌 어떤 data(text, json, xml 등)라는 걸 의미한다.
- 개발자 도구의 Network탭에서 각 요청을 클릭한다.
payload 탭에서 요청시 전달값을, response 탭에서 응답 데이터를 확인할 수 있다.
===========================================================================
ㅁ 2번째 버튼
ㅁ manage1.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="${contextPath}/resources/js/jquery-3.7.1.min.js"></script>
</head>
<body>
<h3>회원관리 1번 페이지</h3>
<form id="mem-form">
번호 : <input type="text" name="userNo"> <br>
아이디 : <input type="text" name="userId"> <br>
비밀번호 : <input type="text" name="userPwd"> <br>
<button type="button" onclick="fn_ajax1();">조회1(아이디, 비번으로 이름 조회)</button>
<button type="button" onclick="fn_ajax2();">조회2(아이디, 비번으로 이름 조회)</button>
<button type="button" onclick="">조회3(번호로 회원전체정보 조회)</button>
<button type="button" onclick="">전체조회</button>
<br>
<button type="button" onclick="">번외1</button>
<button type="button" onclick="">번외2</button>
</form>
<hr>
<div id="result">
결과가 보여지는 영역
</div>
<script>
// 조회1 버튼 : 아이디랑 비번으로 이름을 조회
function fn_ajax1() {
let id = $("input[name=userId]").val();
let pwd = $("input[name=userPwd]").val();
$.ajax({
// 요청
url: "${contextPath}/member1/detail1.do",
data: "id=" + id + "&pwd=" + pwd, // 쿼리스트링으로 전달값 작성
type: "get",
// 응답
success: function(resData){
$("#result").text(resData);
},
error: function(){
}
})
}
function fn_ajax2() {
$.ajax({
url: "${contextPath}/member1/detail2.do",
type: "get",
data: $("#mem-form").serialize(),
success: function(resData){
$("#result").text(resData);
},
error: function(){
console.log("조회2 버튼에 대한 ajax 통신 실패");
}
})
}
</script>
</body>
</html>
- form의 모든 입력요소들을 직렬화 (조회1 버튼과 같은 쿼리스트링으로 만들어짐)
- 해당 form요소.serialize() => "userNo=10&userId=user01&userPwd=pass01"
- form 요소에 serialize() 메서드를 실행하면 해당 폼 데이터가 쿼리스트링 형태로 변환되며, 이때 쿼리스트링에 포함되는 각 key값은 폼 필드의 name 속성값이 된다.
ㅁ MemberController1 수정
package com.br.ajax.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.br.ajax.service.MemberService;
import lombok.RequiredArgsConstructor;
@RequestMapping("/member1")
@RequiredArgsConstructor
@Controller
public class MemberController1 {
private final MemberService memService;
private Logger logger = LoggerFactory.getLogger(MemberController1.class);
/* 기존의 HttpServletResponse 객체 이용하는 방식
@GetMapping("/detail1.do")
public void memberDetail(String id, String pwd, HttpServletResponse response) throws IOException {
logger.debug("request id: {}, pwd: {}", id, pwd);
String result = memService.selectMemberByIdPwd(id, pwd);
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.print(result);
}
*/
// Spring 방식
@ResponseBody
@GetMapping(value="/detail1.do", produces="text/html; charset=utf-8")
public String memberDetail(String id, String pwd) {
logger.debug("request id: {}, pwd: {}", id, pwd);
String result = memService.selectMemberByIdPwd(id, pwd);
return result;
}
@ResponseBody
@GetMapping(value="/detail2.do", produces="text/html; charset=utf-8")
public String memberDetail2(String userId, String userPwd) {
return memService.selectMemberByIdPwd(userId, userPwd); // String 변수 안두고 1줄로 줄이기.
}
}
- form요소를 직렬화했다. 직접 쿼리스트링으로 쪼개도 되고 직렬화해도 된다.
ㅁ 지금 응답 데이터로 문자열을 응답하고 있는데 DTO, List를 응답할 예정이다.
- 스프링에서는 JSON이나 GSON 라이브러리를 직접 사용하지 않고,
반환형만 바꾸는 것으로 가능하며, @ResponseBody 어노테이션을 추가해주면 된다.
- 대신 자바 객체를 응답 데이터로 반환하려면 내부적으로 json 데이터로 변환해서 전달해야 한다.
내부적으로 JSON으로 변환되는 과정을 자동으로 처리해주는 라이브러리가 있다.
(담주에 배우는 Jackson 라이브러리)