본문 바로가기

Programming/국비학원

221025 - Ajax 비동기 통신 ( jsp, 서블릿 / 스프링 )

학원에서는 예제를 통해 jsp, 서블릿을 이용한 Ajax 비동기 통신을 배웠는데, 스프링을 이용한 통신도 궁금해 서치하며 공부해봤다.

 

 

 

 

동기식 / 비동기식 통신

동기식 / 비동기식

동기식 통신 : 요청 전송 후 응답을 받은 후에야 다음 동작이 이뤄짐

비동기식 통신 : 요청 보내면 응답과 상관없이 동작함 => http 전송 중에도 클라이언트가 웹 애플리케이션과 상호작용 가능

 

 

 

 

  • 동기식 통신

Synchronous : 동시에 일어난다

=> 요청을 전송하면 얼마나 시간이 걸리든 그 자리에서 응답을 받음
=> 두 서버 사이의 트랜잭션을 맞춤

요청 보낸 스레드는 응답 도착 전까지 아무것도 못하는 Block 상태가 됨

=> 요청값, 응답값 순서 보장 + 보낸 요청에 대한 처리 결과값 보장

 

응답 지연 시 요청을 보낸 스레드는 응답을 계속 기다리는 상태가 됨

=> 이후 요청들은 연결 가능한 스레드가 없어 연결맺지 못하는 성능 이슈 발생 가능

 

 

 

 

  • 비동기식 통신

Asynchronous : 동시에 수행하지 않는다
=> 요청만 보내면 응답은 언제 받아도 상관없는 상태가 됨

요청 보낸 스레드는 응답을 기다리지 않음 → Non-block 상태

 

스레드가 여러 요청 전송 시 더 늦게 보낸 요청이 먼저 처리됐다면 그 응답값이 먼저 올 수 있음

=> 순서 보장 X

 

동기식 방식보다 성능적으로 좋으나 응답에 대한 처리 결과를 보장받고 동작해야 하는 서비스에는 부적합

 

 

 

Ajax (Asynchronous Javascript and XML)

자바스크립트를 이용해 서버, 브라우저가 비동기 방식으로 데이터를 교환할 수 있는 통신 기능

=> 자바스크립트를 통해 서버에 데이터를 비동기 방식으로 요청

 

 

 

  • 장점

페이지 갱신 없이 클라이언트-서버 간 비동기 통신 가능하게 함 (페이지 리로드 => 전체 리소스를 다시 불러와 불필요한 리소스 낭비 발생)

화면이 새로 로딩되는 것이 아니므로 속도, 성능 개선

별도 플러그인 사용하지 않음

http 전송 중에도 클라이언트가 웹 애플리케이션과 상호작용 가능

 

 

 

 

  • 단점

히스토리 관리가 안됨
페이지 이동 없는 통신으로 인한 보안상 문제 발생 가능
연속으로 데이터 요청 시 서버 부하 발생 가능

 

 

 

 

  • 사용법
$.ajax({
 
    url : requestUrl,
    type : 'DELETE',
    async : true,
    data : JSON.stringify(requestParam),
    dataType : "json",
    timeout : 10000,
    contentType : "application/json",
    
    beforeSend : function(){},
    complete : function(){},
    success : function(response, status, request){},
    error : function(){},
    fail : function(){}
    
});

url : 요청 url => 해당 주소로 데이터 보냄 (요청 전달)

type : 요청 방식 (get, post, put, delete)

async : 디폴트값 true (false : 동기 통신)

data : 보낼 데이터 => 받는 쪽에서는 파라미터에서 넘어온 값으로 인식, getParameter() 사용 가능 (결과 처리)

dataType : 서버에서 반환되는 데이터 형식 (text, html, xml, json, jsonp, script)

timeout : 제한시간 설정

contentType : 서버에서 데이터 보낼 때 사용 (content-type 헤더 값)

 

beforeSend : ajax 통신 보내기 전 실행할 함수

complete : 통신 성공 여부 상관없이 무조건 실행

success : ajax 통신 성공 시 실행할 함수 => ajax 통해 응답받은 데이터가 매개변수에 들어옴 (응답 받기)

 

※ success 시 함수의 매개변수

response: 응답받는 데이터 / status: http 코드 / request: 요청한 데이터 

일반적으로 응답받는 데이터만 매개변수에 넣음 ex. success : function(data){..

 

 

 

https://developer.mozilla.org/ko/docs/Web/Guide/AJAX/Getting_Started

cf. 자바스크립트

<script>
(function() {
  var httpRequest;
  document.getElementById("ajaxButton").addEventListener('click', makeRequest);

  function makeRequest() {
    httpRequest = new XMLHttpRequest();

    if(!httpRequest) {
      alert('XMLHTTP 인스턴스를 만들 수가 없어요 ㅠㅠ');
      return false;
    }
    httpRequest.onreadystatechange = alertContents; //onreadystatechange에 alertContents() 함수 설정
    httpRequest.open('GET', 'test.html');
    httpRequest.send();
  }

  function alertContents() {
  try{
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        alert(httpRequest.responseText); //test.html 파일의 내용 alert
      } else {
        alert('request에 뭔가 문제가 있어요.');
      }
    }
 }catch( e ) {
    alert('Caught Exception: ' + e.description);
}
})();
</script>

※ 정적 HTML 파일이 아닌 XML 파일을 받기 위한 request를 보내려면 응답 헤더를 반드시 설정해야 함

헤더에 Content-Type: application/xml을 설정 X → XML 요소에 접근할 때 "Object Expected" 예외 에러 발생

 

※ 헤더에 Cache-Control: no-cache 설정 X

→ 브라우저는 응답 캐싱해 다시 요청 안 할 수도

 

※ try, catch 로 에러 방지

통신 에러 (서버 다운 등) 때 status 필드 접근 => onreadystatechange 메서드에서 예외 에러를 발생

=> try, catch문 사용

 

 

 

 

jsp, 서블릿을 이용한 Ajax 비동기 통신

뷰에서 개인정보 입력 

→  AJAX로 서버에 요청 데이터 전달됨

→ 서버는 DB 연결해 결과 처리

→ 서버는 응답 데이터를 요청 페이지에 전달 (페이지 이동 X)

 

 

 

 

  • 기본 데이터 교환
  • ajax1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ajax1</title>
<script src="jquery-3.6.0.min.js"></script>
<script type="text/javascript">

$(document).ready(function() {
	$("#sendBtn").on("click", function(){
		$.ajax({
			type:"get",
			url:"send.jsp",
			data: { pages : "1", num : "3", name : "user" },
			dataType:"text",
			contentType:"application/x-www-form-urlencoded;charset=UTF-8",
			error:function(){console.log('오류 발생');
			},
			success:function(data){
				console.log('통신 성공 : '+data);
				$("body").append(data);
			}
		});
	});
});

//javascript
/* function sendRequest(){
	let httpRequest = new XMLHttpRequest();
	httpRequest.open("GET", "send.jsp", true);
	httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	httpRequest.send("page=1&num=3&name=user");
	
	httpRequest.onreadystatechange = function(){
		if(httpRequest.readyState == XMLHttpRequest.DONE && httpRequest.status == 200){
			document.getElementById("text").innerHTML = httpRequest.responseText;
		}
	}
} */
</script>
</head>
<body>
 <button id="sendBtn">ajax 전송</button>
</html>

//

콘술 => jsp 전체 내용 출력

현 페이지 => 페이지 : 1 번호 : 1 이름 : user 출력

 

 

 

  • send.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ajax1</title>
</head>
<body>
<%
	/* request객체에서 받아온 파라미터값 저장 */
	String pages = request.getParameter("pages");
	String num = request.getParameter("num");
	String name = request.getParameter("name");
	
	/* out 객체를 통해 응답 */
	out.println("페이지 : " + pages);
	out.println("번호 : " + num);
	out.println("이름 : " + name);
%>
</body>
</html>

 

 

 

 

  • 아이디 중복 여부 체크
  • idCheck.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="jquery-3.6.0.min.js"></script>
<script>
$(function () {
		$("#checkBtn").on("click", function() {
	        const id = $("#id").val();
	        if(id.length === 0 || id === null) {return alert("아이디를 입력하세요")};
	        $.ajax({
	            type:"post",
	            async:true, //안 적어도 기본값 true
	            url:"http://localhost:8070/ajax_jsp/loginCheck", 
	            dataType:"text",
	            data:{id:id},
	            success: function(data) { //응답받은 데이터
	                if(data === "usable") { 
	                    $("#message").text("사용할 수 있는 ID입니다.");      
	                    $("#checkBtn").prop("disabled", true) //버튼 비활성화
	                } else {
	                    $("#message").text("이미 사용 중인 아이디입니다.");
	                }
	            },
	            error:function () {
	                console.log("error");
	            }
	        });//ajax
	    });		
});
</script>
</head>
<body>
<form method="post">
    <input type="text" id="id" name="id" placeholder="아이디를 입력하세요" >
    <input type="button" id="checkBtn" value="중복확인" >
</form>
<div id="message"></div>
</body>
</html>

 

 

 

  • loginCheck
package ajax_jsp.ex01;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/loginCheck")
public class LoginCheck extends HttpServlet {

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		    request.setCharacterEncoding("utf-8");
	        response.setContentType("text/html; charset=utf-8");

	        MemberDAO dao = new MemberDAO();
	        
	        String id = request.getParameter("id");
	        PrintWriter out = response.getWriter();

	        boolean checkID = dao.idCheck(id); //아이디 존재 여부 확인
	        
	        if(checkID) { //결과 응답으로 전달
	            out.print("not-usable");
	        } else {
	            out.print("usable");
	        }
		
	}

}

 

 

 

  • MemberDAO
package ajax_jsp.ex01;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class MemberDAO {
    private Connection con;
    private PreparedStatement pstmt;
    private DataSource ds;

	//DataSource 연결
    public MemberDAO() {
        try {
            Context context = new InitialContext();
            Context envContext = (Context) context.lookup("java:/comp/env");
            ds = (DataSource) envContext.lookup("jdbc/oracle");
        } catch (Exception e) {
            e.printStackTrace();
        }

    } //MemberDAO()

	//아이디 존재 여부 확인
    public boolean idCheck(String id) {
        if(id == null || id.length() == 0) throw new NullPointerException("아이디가 없습니다.");
        
        System.out.println("id : " + id);
	
        String query = "select decode(count(*),1, 'true', 'false') as result from member_list where id = ?";
        //1개 추출 시 'true' 반환, 해당 없으면 'false' 반환
        System.out.println("query : " + query);

        try {
            con = ds.getConnection();
            pstmt = con.prepareStatement(query);
            pstmt.setString(1, id); //?에 id 세팅
            ResultSet rs = pstmt.executeQuery(); //쿼리 실행, ResultSet에 추가
            rs.next(); //ResultSet에서 데이터 꺼내기
            
            String result = rs.getString(1); //1번째 컬럼 값 추출
            System.out.println("result : " +result);

            rs.close();
            pstmt.close();
            con.close();

            return Boolean.parseBoolean(result); //쿼리 결과로 얻은 true / false 값 불린으로 변환 후 리턴

        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

 

 

 

 

  • 실시간 아이디 중복 체크
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="jquery-3.6.0.min.js"></script>
<script>
$(function () {
		$("#id").on("keyup", function() { /////
	        const id = $("#id").val();
	        //if(id.length === 0 || id === null) {return alert("아이디를 입력하세요")};
	        $.ajax({
	            type:"post",
	            url:"http://localhost:8070/ajax_jsp/loginCheck", 
	            dataType:"text",
	            data:{id:id},
	            success: function(data) {
	                if(data === "usable") {
	                    $("#message").text("사용할 수 있는 ID입니다.");      
	                    $("#checkBtn").prop("disabled", true); //사용할 수 있는 ID => 버튼 비활성화
	                } else {
	                    $("#message").text("이미 사용 중인 아이디입니다.");
	                    $("#checkBtn").prop("disabled", false); //버튼 활성화
	                }
	            },
	            error:function () {
	                console.log("error");
	            }
	        });//ajax
	    });		
});
</script>
</head>
<body>
<form method="post">
    <input type="text" id="id" name="id" placeholder="아이디를 입력하세요" >
    <input type="button" id="checkBtn" value="중복확인" >
</form>
<div id="message"></div>
</body>
</html>

 

=> 상단 사례와 비교했을 때 자바스크립트 이벤트 타겟(버튼 → 인풋), 종류(클릭 → 키업)만 변경하면 된다

 

 

 

 

  • 실시간 자동완성
  • google.html
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Google</title>
</head>
<script>
function startSuggest(){
	setTimeout("sendKeyword()", 500) // 500은 0.5초
}
function sendKeyword(){
	const keyword = document.search.keyword.value;
	console.log(keyword)
	if(keyword!=''){
		let httpRequest = new XMLHttpRequest();
		httpRequest.open("POST", "command.jsp", true); ////요청방식, 보내는 주소, 비동기식 여부
		httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		httpRequest.send("keyword="+encodeURIComponent(keyword));

		httpRequest.onreadystatechange = function(){
		if(httpRequest.readyState == XMLHttpRequest.DONE && httpRequest.status == 200){
			document.getElementById("suggestList").innerHTML = httpRequest.responseText; ////
		}
        
		show();
	} 
		
	}else{
    
		hide();
        
	}
}

function show(){
	const s = document.getElementById("suggest");
	s.style.display = '';
}

function hide(){
	const s = document.getElementById("suggest");
	s.style.display = 'none';
}

</script>
<body>
	<img src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"/>
	<form name="search">
		<input type="text" name="keyword" id="keyword" autocomplete="off" onkeydown="startSuggest()">
		<div id="suggest" name="suggest">
			<div id="suggestList" name="suggestList" style="padding-left: 10px;"> ////
			</div>
		</div>
	</form>
</body>
</html>

 

 

 

  • command.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
request.setCharacterEncoding("utf-8");

String keyword = request.getParameter("keyword"); ////

String[] keywords = {
	"AJAX", "AJAX실전프로그래밍", "자라", "자바프로그래밍",
	"자바서버페이지", "자바스터디", "자바서비스", "자바캔"};
		
	String line = "<div>";
	
	for(int i = 0 ; i < keywords.length; i++){
		if(keywords[i].contains(keyword))
			line +="<a href '" + keywords[i] + ".jsp'>" + keywords[i] + "</a><br>";
	}

	line+="</div>";

%><%=line%>

 

 

 

 

제이쿼리, AJAX 통한 JSON 데이터 교환

https://code.google.com/archive/p/json-simple/downloads

json-simple.jar 다운로드

 

 

 

 

  • JSON

경량화된 데이터 교환 형식 (프로그래밍 언어 X 데이터 표기 방식 O)

name:value 형태의 구조 (map 구조)

객체는 중괄호 { }, 배열은 대괄호 [ ]로 감쌈

 

//JSONObject (JSON 객체)

[
	"mycar":{"brand":"Ferrari","color":"Red","price":"65000"},
	"momcar":{"brand":"Genesis","color":"Black","price":"8000"}
]

 

※ toJsonString() : JSON 객체 (JSONObject) → 문자열

※ stringify() : 자바스크립트 객체/값 → 문자열
※ parse() : 문자열 → 자바스크립트 객체/값

 

 

 

 

  • 자바스크립트 통해 Json 객체 생성, 서블릿으로 전송
  • json6.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSON 자료형</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
	function fn_json(){
		$.ajax({
			type:"post",
			url:"${contextPath}/json2",
			success: function(data){ //연결 성공 시 서블릿으로부터 파라미터 받아옴
				let jsonInfo=JSON.parse(data); //문자열-> 자스 객체/값
				let memInfo="<h2>회원정보</h2>";
				memInfo+="-------------<br>";
				
				for (var i in jsonInfo.members){
					memInfo+="이름 : " + jsonInfo.members[i].name +"<br>";
					memInfo+="나이 : " + jsonInfo.members[i].age +"<br>";
					memInfo+="핸드폰 : " + jsonInfo.members[i].hp +"<br>";
					memInfo+="이메일 : " + jsonInfo.members[i].email +"<br><br>";
				}
				
				$("#output").html(memInfo);
			},
			error: function(data, textStatus){}
		});
	}
</script>
</head>
<body>
	<input type="button" onclick="fn_json();" value="데이터를 서버로부터 전송받기">
	<div id="output"></div>
</body>
</html>

 

 

 

  • JsonServlet02
package ajax_jsp.ex02;

import java.io.IOException;
import java.io.PrintWriter;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/json2")
public class JsonServlet02 extends HttpServlet {


	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		JSONObject totObject = new JSONObject(); //전체 회원정보 배열 저장할 최종 JSONObject 객체
		JSONArray memArray = new JSONArray(); //각 회원정보 객체들 HashMap 구조로 저장할 JSONArray 객체
	
		JSONObject memberInfo = new JSONObject(); //하나의 회원정보 저장할 JSONObject 객체
		memberInfo.put("name", "홍길동");
		memberInfo.put("age", 50);
		memberInfo.put("hp", "010-1234-5678");
		memberInfo.put("email", "hong@gmail.com");
		memArray.add(memberInfo);
		
		memberInfo = new JSONObject(); //두번째 회원정보
		memberInfo.put("name", "김철수");
		memberInfo.put("age", 30);
		memberInfo.put("hp", "010-1111-2222");
		memberInfo.put("email", "kim@gmail.com");
		
		memArray.add(memberInfo); //배열에 두 회원정보 저장
		
		totObject.put("members", memArray); //최종 객체에 배열 저장
		
		/*
		 * totObject =
		 * 
		 * 
		 * {
		 *   "members":
		 *     [
		 *       {"name":"홍길동", "age":50, "hp":"010-1234-5678", "email":"hong@gmail.com"},
		 *       {"name":"김철수", "age":30, "hp":"010-1111-2222", "email":"kim@gmail.com"}
		 *     ]
		 * }
		 * */
		
		String jsonStr = totObject.toJSONString(); //JSONObject 객체 -> 문자열
		
		out.print(jsonStr);

	}
}

 

 

 

 

 

스프링 이용한 Ajax 비동기 통신