Mybatis & Ajax

[211224] Json-simple & 로그인 중복 체크

감자탈출기 2021. 12. 24. 22:18

Ajax란 Asynchronous JavaScript and XML의 약자입니다. Ajax는 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나입니다. Ajax는 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있습니다.

 

- 비동기 통신을 사용하여 웹  서버에 메시지를 보내기 위해 사용

- 서버에 데이터를 보낼 때 JSON, XML 등의 형식으로 보내고 응답 받는다.

- 비동기 통신은 요청과 응답이 서로 분리되어 동작하는 형태를 지칭 

    -> 요청 후 응답이 오지 않아도 사용자는 브라우저에서 지속적으로 작업을 진행할 수 있다.

- 웹 페이지의 전환 없이 데이터를 갱신하거나 화면 효과를 부여하는 등의 처리를 할 수 있다.

(ex 쇼핑몰 스크롤 아래로 내릴때마다 화면 갱신 없이 품목 리스트가 계속 나오는 것, 깃허브에서 생성할 레포지터리 이름 입력하면 사용 가능한 이름인지 검사)

- Ajax 를 사용해도 HTTP 통신이다.


 

메인 페이지에서 Ajax 사용하기

 

<script type="text/javascript" src="/static/js/jquery-3.6.0.min.js"></script>
	<div>
		<h1>Ajax 샘플</h1>
		<p>JQuery 라이브러리 사용 - Ajax에 대한 편의성을 제공</p>
		<button type="button" onclick="sendAjax();">누르면 Ajax 전송</button>
		<script type="text/javascript">
			function sendAjax(){
				console.log("Ajax를 시작 합니다.");
				$.ajax({
					url: "", 	// Ajax 처리를 수행할 서버 URL 주소 (Controller에 맵핑된 주소)
					type: "", 	// get/post
					data: {}, 	// 전송할 데이터의 키: "값" (키는 쌍따옴표 필요 x 값은 문자열일 경우 쌍따옴표 필요)
					dataType: "",	// 응답 데이터의 형식(json/xml ... 우리는 json 할 것)
					success: "", 	// 응답이 왔을 때 동작할 함수명을 지정 또는 직접 함수 작성(콜백함수)
					error: "" 		// 응답에 에러가 있을 때 ...
				});
			}
		</script>
	</div>

 

 

1. index.jsp (메인 페이지) 에 작성

	<div>
		<h1>Ajax 샘플</h1>
		<p>JQuery 라이브러리 사용 - Ajax에 대한 편의성을 제공</p>
		<button type="button" onclick="sendAjax();">누르면 Ajax 전송</button>
		<script type="text/javascript">
			function sendAjax(){
				console.log("Ajax를 시작 합니다.");
				$.ajax({
					url: "./ajax/sample", 	// Ajax 처리를 수행할 서버 URL 주소 (Controller에 맵핑된 주소)
					type: "get", 	// get/post
					data: {
						key: "value",
						x: 10,
						y: 20
					}, 	// 전송할 데이터의 키: "값" (키는 쌍따옴표 필요 x 값은 문자열일 경우 쌍따옴표 필요)
					dataType: "json",	// 응답 데이터의 형식(json/xml)
					success: function() {
						console.log("서버로부터의 응답이 왔습니다.")
					} 	// 응답이 왔을 때 동작할 함수명을 지정 또는 직접 함수 작성(콜백함수)
					//error: "" 		// 응답에 에러가 있을 때 ...
				});
			}
		</script>
	</div>

 

2. 메인 페이지 실행 /localhost

 

3. Ajax 전송(button onclick)

F12 개발자모드 - 네트워크 - 헤더, Payload 에서 보낸 Ajax 확인할 수 있음

클릭하여 헤더 확인
보낸 데이터 확인

 

404 에러 뜨므로 

받아올 컨트롤러  생성하여 값 확인

 

반환될 형식 = json  -> 다 문자열로 변환해서 보내주면 됨 

 

json 형식 예시

vsCode settiong.json

 

키 : 문자열 (원래)

값 : 불린, 문자열, 숫자, 객체, 배열[]

 

키 : 값 의 값 안에 또 키 : 값 있을 수 있음

 

 

 

문자열 직접 만들기

중괄호로 시작 {     중괄호로 끝나야 } 한다.

 

 

 

간단하게 라이브러리 사용하기

 

json-simple-1.1.1.jar  파일 설치

https://repo1.maven.org/maven2/com/googlecode/json-simple/json-simple/1.1.1/

 

 

Central Repository: com/googlecode/json-simple/json-simple/1.1.1

 

repo1.maven.org

 

간편하게 작성

package com.web.ajax;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONObject;

@WebServlet("/ajax/sample")
public class AjaxSampleController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println("key: " + request.getParameter("key"));
		System.out.println("x: " + request.getParameter("x"));
		System.out.println("y: " + request.getParameter("y"));
		int x = Integer.parseInt(request.getParameter("x"));
		int y = Integer.parseInt(request.getParameter("y"));
		//로직
		
		//로직의 결과를 반환 -> json 형식으로 만들기
		JSONObject json = new JSONObject();
		json.put("xy", x+y);
		
		response.getWriter().println(json.toString()); //쓰기를 통해 응답하겠다. 문자열로 작성한 키 xy와 더하기 값 전송되어 success함수 (?)
		response.getWriter().flush();
	}

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

}

 

버튼 하나 더 생성해서 post도 테스트

post용 버튼

 

한글 데이터는 깨지므로 인코딩 한다.

json 한글 인코딩

response.setContentType("application/json; charset=utf-8");

또는 인코딩 필터 작성한 것 수정 (요청 영역에 둘 다 넣는다. 왜?)  - 현재는 수정하지 않고 JSONFilter로 분리

package com.web.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
//전 서블릿(컨트롤러)에 대해 이 필터를 거치도록 해야 함 
@WebFilter("/*") //모든 요청에 대해 처리 annotation
public class EncodingFilter implements Filter {

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
			throws IOException, ServletException {
		// 요청 필터 작성 영역
		request.setCharacterEncoding("utf-8"); //클라이언트 -> 서버로 보내는 문자

		String reqURI = ((HttpServletRequest) request).getRequestURI();
		if(reqURI.contains("ajax")) {
			response.setContentType("application/json; charset=utf-8");
		} else {
			response.setContentType("text/html; charset=utf-8"); // 서버 -> 클라이언트
		}
		System.out.println("-------------encordingfilter--------");
		chain.doFilter(request, response); //기준
		
		// 응답 필터 작성 영역
	}
}

 

이유 

-> response.setContentType("text/html; charset=utf-8"); // 서버 -> 클라이언트

는 response 메세지(json.toString())를 utf-8 로 바꾸어주는게 아니라 애초에 전송(response)문자열을 생성할 때 utf - 8로 만들겠다는 말

 

따라서 request 필터 작성 영역부분에 response.setContentType("text/html; charset=utf-8"); 를 해주지 않았다면

이미 toString 했을 때 utf - 8 이 아님 이미 다른걸로 문자열이 만들어졌다. 그리고 보내는 과정 중에 

do filter 이후에 다른 응답필터로 이동할 건데 그때 가서 response.setContentType utf-8 해봤자 소용이 없기 때문.

 

그래서 문자열 만들어서(toString()) 응답!하기 전에 rresponse.setContentType utf-8 해두면 원하는 대로 utf 8 로 만들어진다.

 

 


실습 문제 1

 

JSONFilter를 생성하여 /ajax URL 주소가 있는 경우 아래와 같은 컨텐츠 타입이 설정 되도록 한다.

 -> EncodingFilter 수정한 것은 원래대로 되돌리고  JSONFilter 로 분리시켰음

 

자습 풀이

JSONFilter.java

package com.web.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter("/ajax/*")
public class JSONFilter implements Filter {

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("application/json; charset=utf-8");
		System.out.println("-------------jsonfilter-------");	

		chain.doFilter(request, response);

	}
}

실습 문제 2

자습 풀이

 

join.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*"
         import="com.web.account.model.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
<script type="text/javascript" src="/static/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="/static/js/join.js"></script>
<script type="text/javascript" src="/static/js/joinCheckId.js"></script>
<link type="text/css" rel="stylesheet" href="/static/css/join.css">
</head>
<body>
	<%
		AccountDTO initData = new AccountDTO();
		String error = "";
		
		if(request.getAttribute("init") != null) {
			//반드시 다운캐스팅 ㄱ (아마오브젝트로 업캐스팅된상태여서[11/18에 설명해줌])
			initData = (AccountDTO) request.getAttribute("init");
		}
		if(request.getAttribute("error") != null) {
			error = (String) request.getAttribute("error");
		}
	%>

	<h2>회원가입 하기</h2>
	<form id="join_form" action="./join" method="post">
		<div>
			<label>계정명</label>
			<input type="text" id="user_id" name="username" placeholder="계정 이름" value= "<%=initData.getUsername() %>" required>
			<input type="hidden" id="id_button" value="">
		</div>	
		<div>
			<label>패스워드</label>
			<input type="password" name="password" placeholder="패스워드" required>
		</div>
		<div>
			<label>패스워드 확인</label>
			<input type="password" name="password_check" placeholder="패스워드 확인" required>
		</div>
		<input type="email" name="email" placeholder="이메일 주소" value = "<%=initData.getEmail() %>" required>
		<button type="submit">전송</button>
	</form>
</body>
</html>

 

 

JoinIdCheckController.java

package com.web.ajax;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONObject;

import com.web.account.model.AccountDTO;
import com.web.account.model.AccountService;

@WebServlet("/ajax/joinIdCheck")
public class JoinIdCheckController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException {
		String want = request.getParameter("want"); //키로 받음
		System.out.println("want|" + want + "|");
		//로직 //회원가입 아이디 중복체크
		AccountDTO dto = new AccountDTO(want); 
		AccountService service = new AccountService();
		
		JSONObject json = new JSONObject();
		if(service.findAccount(dto) != null) { 	//이미 있는 계정이라면
			//로직의 결과를 반환 -> json 형식으로 만들기
			//response.setContentType("application/json; charset=utf-8"); 인코딩 필터를 수정
			json.put("alert", "동일한 이름의 계정이 이미 존재합니다");
		} else {
			json.put("alert", "사용 가능한 계정입니다.");
		};
		
		response.getWriter().println(json.toString()); //쓰기를 통해 응답하겠다. 문자열로 작성한 키 xy와 더하기 값 전송되어 success함수 (?)
		response.getWriter().flush();
	}

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

 

 

AccountDAO.java   

dao.selectAccount() 메서드 활용

package com.web.account.model;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.db.conn.MybatisConnect;
import com.db.conn.OracleConnect;

public class AccountDAO {
	private MybatisConnect mc;
	private SqlSession sess;
	
	public AccountDAO() {
		this.mc = new MybatisConnect();
		this.sess = this.mc.getSession(); 
	}

	public AccountDTO selectAccount(AccountDTO dto) {
		AccountDTO data = this.sess.selectOne("AccountMapper.selectAccount", dto);
		return data;
	}
	public List<AccountDTO> selectChoiceAccount(List<Integer> list) {
		List<AccountDTO> datas = this.sess.selectList("AccountMapper.selectChoiceAccount", list);
		return datas;
	}
	//세션에 인서트, 업데이트, 딜리트 메서드 있으므로 사용하면 됨
	//유저계정 추가
	public boolean insert(AccountDTO dto) { 
		int res = this.sess.insert("AccountMapper.insertAccount", dto);
		return res == 1 ? true : false;
	}
	//계정정보 수정
	public boolean updateAccountInfo(AccountDTO dto) { 
		int res = this.sess.update("AccountMapper.updateAccountInfo", dto);
		return res == 1 ? true : false;
	}
	//계정비번 수정
	public boolean updateAccountPassword(AccountDTO dto) { 
		int res = this.sess.update("AccountMapper.updateAccountPassword", dto);
		return res == 1 ? true : false;
	}
	//회원 탈퇴
	public boolean deleteAccount(AccountDTO dto) {
		int res = this.sess.delete("AccountMapper.deleteAccount", dto);
		return res == 1 ? true : false;
	}

	public AccountDTO selectLoginAccount(String username, String password){
		AccountDTO dto = new AccountDTO();
		dto.setUsername(username);
		dto.setPassword(password);
		//객체에 담아서 파라미터로 account.xml로 보낸다.
		AccountDTO data = this.sess.selectOne("AccountMapper.selectAccount", dto); //파라메터 하나밖에 못 싣음. DTO 객체로 주로 보낸다. 이때 account.xml 파라메터타입 해당 dto로 지정해야 함(com.web.---)
									//account.xml의 mapper namespace & select id
		System.out.println("AccountDAO.selectLoginAccount() ->" + data); //확인용(/login 페이지 가서 로그인 했을 때 유저정보 나와야 로그인 정상)
		return data;
	}

	public void commit() {
		mc.commit();		
	}
	
	public void rollback() {
		mc.rollback();
	}
	
	public void close() {
		mc.close();
	}
}
AccountDTO 생성자 오버로드 했음

public AccountDTO(String username) { //회원가입 아이디 중복체크
this.username = username;
}

 

account.xml 

mybatis 연결 객체로 db 불러 옴

<select id="selectAccount" parameterType="account" resultType="account"> 활용

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  <!-- 넘어오는 파라메터(AccountDTO select() - username)의 타입을 지정해주어야 한다.  -->
<mapper namespace="AccountMapper">
  <!--AccountDAO.selectAccount() -->
  <select id="selectAccount" parameterType="account" resultType="account"> <!--(원)com.web.account.model.AccountDTO -> mybatis-config.xml 에서 <typeAliases>로 별칭부여해서 사용 -->
  	SELECT * FROM ACCOUNTS 
  		<where> <!-- 가장 처음에 오는 AND 나 OR을 알아서 where로 변경 -->
  			<if test="id != 0"> <!-- test에 #{id} 이렇게 쓰지 않아도 됨 -->
  				AND ID = #{id}
			</if>
			<if test="username != null">
				AND USERNAME = #{username}
			</if>
  			<if test="password != null">
  				AND PASSWORD = #{password}
  			</if>
  	 </where> 
  </select>
 
  <select id="selectChoiceAccount" parameterType="list" resultType="account">
      SELECT * FROM ACCOUNTS
      <where>
          ID in
          <foreach item="n" collection="list" open="(" separator="," close=")">
              #{n}
          </foreach>
      </where>
  </select>
<!-- 유저 추가용, 리턴타입 기본적으로 int --> 
  <insert id="insertAccount" parameterType="account"> 
  	INSERT INTO ACCOUNTS VALUES(
  		   ACCOUNTS_SEQ.NEXTVAL
  		 , #{username}
  		 , #{password}
  		 , #{email}
  		 , #{joinDate}
  	)
  </insert>
  
  <update id="updateAccountInfo" parameterType="account">
	UPDATE ACCOUNTS
	   SET EMAIL = #{email}
	 WHERE ID = #{id}
  </update>
  
  <update id="updateAccountPassword" parameterType="account">
	UPDATE ACCOUNTS
	   SET EMAIL = #{password}
	 WHERE ID = #{id}
  </update>
  
  <delete id="deleteAccount" parameterType="account">
  	DELETE FROM ACCOUNTS
  	 WHERE ID = #{id}
  </delete>
  
</mapper>

 

 

joinIdCheck.js

window.onload = function() {

	$('#user_id').on('input', function(){
		//초기화?
		//$('#id_button').prop("type","hidden");
	    console.log($(this).val()); //실시간 입력 받은 것 사용. f2 shift 특수키 감지x
		var input = $(this).val();
		if(input == ''){
			console.log("입력값 없는 상태");
			$('#id_button').prop("type","hidden");
			
		} else if(input.indexOf(" ") == -1){
			$.ajax({
				url: "./ajax/joinIdCheck", 	// Ajax 처리를 수행할 서버 URL 주소 (Controller에 맵핑된 주소)
				type: "get", 	// get/post
				data: {
					want: input
				}, 	// 전송할 데이터의 키: "값" (키는 쌍따옴표 필요 x 값은 문자열일 경우 쌍따옴표 필요)
				dataType: "json",	// 응답 데이터의 형식(json/xml)
				success: function(data) {
					console.log(data.alert);
					$('#id_button').prop({"type":"button", "value":data.alert}); //배열로 한번에 지정
					//$('#id_button').prop("value", data.alert);
				}  	// 응답이 왔을 때 동작할 함수명을 지정 또는 직접 함수 작성(콜백함수)
				//error: "" 		// 응답에 에러가 있을 때 ...
			});
		} else if(input.indexOf(" ") != -1) {
			$('#id_button').prop("type","button");
			$('#id_button').prop("value", "공백을 포함할 수 없습니다");
		}
	});
	
	
	
}

function idCheck(alert){
	
};

function sendPost(){
	console.log("Post로 보내기");
	$.ajax({
		url: "./ajax/sample", 	// Ajax 처리를 수행할 서버 URL 주소 (Controller에 맵핑된 주소)
		type: "post", 	// get/post
		data: {
			key: "post한글데이터",
			x: 10,
			y: 20
		}, 	// 전송할 데이터의 키: "값" (키는 쌍따옴표 필요 x 값은 문자열일 경우 쌍따옴표 필요)
		dataType: "json",	// 응답 데이터의 형식(json/xml)
		success: function(data) {
			console.log(data.xy)
			console.log(data.msg)
		} 	// 응답이 왔을 때 동작할 함수명을 지정 또는 직접 함수 작성(콜백함수)
		//error: "" 		// 응답에 에러가 있을 때 ...
	});
}

 

결과

이미 있는 계정 이름

 

이미 있는 계정이름
공백도 거름

 

js 에서는 contains 함수를 쓸 수 없으므로 공백을 거르고 싶다면 indexOf(" ") == -1 로 조건 설정해 주어야 한다.

제이쿼리로 type, value 값을 지정하고 싶다면

$('#id_button').style("type","button")가 아닌 $('#id_button').prop("type","button")을 사용하여야 한다.

 

배열로 한번에 지정

$('#id_button').prop({"type":"button", "value":data.alert}); 

 

.attr() : HTML의속성을 취급

 

.prop() : javaScript프로퍼티를 취급

 

 

 

궁금!

왜 빨리 지우면 버튼 안 사라지고 천천히 지우면 잘 사라지는 가?

 

 

이유 -> 비동기 통신이므로 요청과 응답이 분리. 응답 하지 않아도 요청은 계속해서 보내지므로 

 

개선 -> 따라서 다음과 같이 조건식을 이용. 이전에 변수로 설정 된 input이 아닌 현재 시간의 사용자 입력값을 기준으로 걸러서 버튼이 동작하도록 만들어야 한다.

if($('#user_id').val() == '')

if($('#user_id').val().indexOf(' ') !== -1)

개선된 코드

joinIdCheck.js

window.onload = function() {
	$('#user_id').on('input', function(){
		var input = $(this).val();
			$.ajax({
				url: "./ajax/joinIdCheck", 	// Ajax 처리를 수행할 서버 URL 주소 (Controller에 맵핑된 주소)
				type: "get", 	// get/post
				data: {
					want: input
				}, 	// 전송할 데이터의 키: "값" (키는 쌍따옴표 필요 x 값은 문자열일 경우 쌍따옴표 필요)
				dataType: "json",	// 응답 데이터의 형식(json/xml)
				success: function(data) {
					if(data.state === "success"){
						$('#id_button').prop({"type":"button", "value":data.msg});
					}
					if (data.state === "fail") {
						$('#id_button').prop({"type":"button", "value":data.msg});
					}
					  
					if($('#user_id').val() == ''){
						$('#id_button').prop("type","hidden");
					}
					if($('#user_id').val().indexOf(' ') !== -1){
						$('#id_button').prop({"type":"button", "value":"공백을 포함하실 수 없습니다."});
					}
					//$('#id_button').prop("value", data.alert);
				}  	// 응답이 왔을 때 동작할 함수명을 지정 또는 직접 함수 작성(콜백함수)
				//error: "" 		// 응답에 에러가 있을 때 ...
			});
	});
}