4. Spring Framework 개발 따라하기-2
이번엔 일단 DB연동을 해보려 합니다.
MariaDB로 연동을 할것이며 기본 세팅은 아래 링크를 확인해주세요
DB/MariaDB] - MariaDB(Mysql) 설치 후 설정 및 외부 접속
JDBC 설정 및 MyBatis를 설정해주겠습니다.
mvnrepository.com
위의 홈페이지에서 Maven 혹은 Gadle의 추가 방법을 확인하셔서 추가하시면 됩니다. 찾는 라이브러리 버전을 확인할 수 있습니다.
pom.xml파일을 열어줍니다.
위와 같은 화면이 보입니다. 아래 맨 아래 pox.xml을 누르면 파일 자체를 수정가능하게 열립니다.
전에 한글 경로 문제로 추가했던 적이 있죠.
Dependencies에서도 추가가 가능합니다.
<!-- spring관련 jdbc와 test 라이브러리 (지금 사용중인 스프링 버전 사용) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- mybatis와 스프링에서 mybatis 사용 라이브러리 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<!-- Database Connection Pool관리 라이브러리 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.6.0</version>
</dependency>
<!-- mariaDB와 연결 라이브러리 -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.5.4</version>
</dependency>
위의 내용을 dependencies에 추가하시면 됩니다.
프로젝트 선택 후 ALT + F5를 눌러 프로젝트를 업데이트 해줍니다.
이제 Maven에는 적용이 됐습니다.
src -> main -> webapp -> WEB-INF -> spring에서 root-context.xml파일을 열어 Namespaces를 선택하고 위와 같이 선택해줍니다.
<!-- DB연동 부분 (JDBC 관련 설정) -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.mariadb.jdbc.Driver" />
<property name="url" value="jdbc:mariadb://172.30.1.9:3306/demo_db" />
<property name="username" value="demo_db_admin" />
<property name="password" value="qwer1234!" />
</bean>
<!-- mapper 등록하여 dataSource를 통한 연결 (mybatis-spring 관련 연결 부분) -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:/mappers/**/*Mapper.xml" />
</bean>
<!-- sqlSessionFactory에서 열린 session을 관리함 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
위의 내용을 복붙해주시면 됩니다.
주석을 보시면서 이해하시길...
위에서 url, username, password mapperLocations 4부분만 해당 프로젝트에 맞춰주시면 됩니다.
위의 db관련 생성 query문은 더보기를 눌러주세요.
CREATE DATABASE IF NOT EXISTS demo_db;
INSERT INTO mysql.user (user,host,password,plugin)
SELECT * FROM (SELECT 'demo_db_admin', '%', PASSWORD('qwer1234!'),'mysql_native_password') AS tmp WHERE NOT EXISTS (SELECT user FROM mysql.user WHERE user='demo_db_admin') LIMIT 1;
GRANT ALL PRIVILEGES ON demo_db.* to 'demo_db_admin'@'%' IDENTIFIED BY 'qwer1234!';
flush PRIVILEGES;
mapperLocations에서 해당 경로는 src/main/resources에 mappers폴더를 만들고 그 폴더에 앞으로 사용할 Mapper를 만들어주시면 됩니다.
이제 login관련 테이블을 만들어보겠습니다.
제가 했던거 기반으로 기본적인 내용으로만 생성하였습니다.
또한 기본으로 사용하던 id = test, password = 1111
원하는 Column이나 이런건 알아서 추가하시면 됩니다. 생성문이 궁금하시면 더보기 버튼 클릭하세요~
CREATE TABLE `USER_LOGIN_INFO_TB` (
`idx` INT NOT NULL AUTO_INCREMENT,
`id` VARCHAR(15) NOT NULL,
`password` VARCHAR(15) NOT NULL,
`session_id` VARCHAR(50) NULL DEFAULT '',
PRIMARY KEY (`idx`)
)
COLLATE='utf8mb4_general_ci';
INSERT INTO `demo_db`.`USER_LOGIN_INFO_TB` (`id`, `password`) VALUES ('test', '1111');
다시 돌아와
Mapper는 *Mapper로 이름을 만들어줘야 알아서 인식합니다.
mappers폴더에 LoginMapper.xml이라는 파일을 만들고 아래와 같이 작성합니다.
<?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">
<mapper namespace="loginMapper">
<!-- 등록된 회원 조회 및 로그인시 체크 -->
<select id="check_user" parameterType="com.test.login.LoginDTO" resultType="java.util.HashMap">
select id,password from USER_LOGIN_INFO_TB where id="${id}";
</select>
</mapper>
namespace는 sql연결시 사용할 이름입니다.
일단은 select만 해서 id만을 통해 조회하는 Query문을 작성했습니다.
parameterType과 resultType은 제작한 DTO나 자료형이 사용가능합니다.
항상 모든 경로를 다 적어주는건 힘든일이죠.
root-context.xml에 sqlSessionFactory에 한줄을 추가합니다.
<property name="configLocation" value="classpath:/mybatisAlias.xml" />
이제 해당 위치에 해당 파일을 만들어줍니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.test.login.LoginDTO" alias="loginDTO"/>
</typeAliases>
</configuration>
위의 내용을 그대로 넣으시면 됩니다.
typeAlias에서 type은 사용할 자료형입니다.
제작한 DTO를 넣고 loginDTO로 사용가능하게 선언합니다.
<!-- 등록된 회원 조회 및 로그인시 체크 -->
<select id="check_user" parameterType="loginDTO" resultType="java.util.HashMap">
select id,password from USER_LOGIN_INFO_TB where id="${id}";
</select>
작성한 Mapper와 비교를 하면 어떻게 사용하시는지 이해가 되실겁니다.
이제 DAO에 sql을 사용할수 있게 선언하고 만든 쿼리문이 동작하나 만들어보겠습니다.
@Autowired
protected SqlSessionTemplate sqlSession;
위의 코드를 추가해줍니다. root-context에서 선언한 내용에 의존성 주입을 해줍니다.
@Override
public LoginDTO user_check(String id, String password) {
LoginDTO login_data = new LoginDTO();
login_data.setId(id);
login_data.setPassword(password);
// selectOne은 결과가 없으면 null을 반환
HashMap<String, Object> check_user = sqlSession.selectOne("loginMapper.check_user",login_data);
logger.info("check_user : " + check_user != null ? check_user.toString() : "null");
if(check_user == null || check_user.get("id").equals(id) == false) {
// 아이디가 없을때
login_data = new LoginDTO();
} else if(check_user.get("password").equals(password) == false) {
// 입력한 비밀번호가 다를때
login_data.setPassword("");
}
return login_data;
}
위와 같이 코드를 작성합니다. 오버라이드를 했으니 DAO인터페이스에도 선언을 해줘야겠죠.
selectOne은 데이터 1개만 조회할때 쓰입니다.
아까 사용한 네임스페이스와 select문의 id로 해당 쿼리문을 매칭합니다.
전송할 데이터가 있을때만 뒤에 데이터를 실어보내면 됩니다.
LoginServiceImpl에 아래 내용을 추가해줍니다.
@Override
public HashMap<String, Object> user_check(String id, String password) {
// TODO Auto-generated method stub
HashMap<String, Object> returnVal = new HashMap<String, Object>();
if (id.isEmpty() && password.isEmpty()) {
returnVal.put("msg_code", "00001");
returnVal.put("msg", "입력값 없음");
} else {
LoginDTO loginDTO = loginDAO.user_check(id, password);
if (loginDTO.getId() == null || loginDTO.getId().isEmpty()) {
returnVal.put("msg_code", "00002");
returnVal.put("msg", "아이디 없음");
}else if(loginDTO.getPassword().isEmpty()) {
returnVal.put("msg_code", "00003");
returnVal.put("msg", "비밀번호 다름");
}else {
returnVal.put("msg_code", "00000");
returnVal.put("msg", "정상");
}
}
return returnVal;
}
Front-End단에서 msg_code를 통해 분기를 주려고 합니다.
이제 controller에 아래 내용을 붙여넣습니다.
@ResponseBody
@RequestMapping(value="/user_check", method = RequestMethod.POST)
public HashMap<String,Object> user_check(@RequestParam String id, @RequestParam String password) {
HashMap<String,Object> check_data = loginService.user_check(id, password);
return check_data;
}
그냥 Service에서 온 값을 그대로 보내줍니다.
@RequestMapping(value="/user_check", method = RequestMethod.POST)
public @ResponseBody HashMap<String,Object> user_check(@RequestParam String id, @RequestParam String password) {
HashMap<String,Object> check_data = loginService.user_check(id, password);
return check_data;
}
위와 같이 ResponseBody를 안에 써도 상관 없습니다. Rest Api 제작시 JSON형식을 받기 위해 해당 Annotation을 사용합니다.
이제 Front-End에서 Ajax통신을 통해 값을 받아보겠습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<c:choose>
<c:when test="${msg == 'succ' && typeVal == 'login'}">
로그인 성공함
<c:if test="${ !empty login_info }">
<form action="${pageContext.request.contextPath}/login/user_logout" method="post">
<input id="id" name="id" type="hidden" value='<c:out value="${login_info.id}" />'>
<input id="password" name="password" type="hidden" value='<c:out value="${login_info.password}" />'>
<input id="session_id" name="session_id" type="hidden" value='<c:out value="${login_info.session_id}" />'>
<input type="submit" value="로그아웃">
</form>
</c:if>
</c:when>
<c:when test="${msg == 'succ' && typeVal == 'logout'}">
로그아웃 성공함
</c:when>
<c:when test="${msg == 'fail'}">
로그인이든 로그아웃이든 뭔가 실패함
</c:when>
<c:otherwise>
<!-- 조건식 그외 -->
안녕 여긴 로그인 페이지
<script>
function rest_check_user() {
var send_data = new Object();
send_data.id = document.getElementById("id").value;
$.ajax({
url:'${pageContext.request.contextPath}/login/user_check', // 요청 할 주소
async:true, // false 일 경우 동기 요청으로 변경
type:'POST', // GET, POST, PUT, PATCH, DELETE 등
data: send_data,// 전송할 데이터
dataType:'json',// xml, json, script, html
//contentType:"application/json", // 기본값 : application/x-www-form-urlencoded; charset=UTF-8
beforeSend:function(jqXHR, settings) {
// 전송전
console.log("beforeSend",jqXHR, settings)
},success:function(data, textStatus, jqXHR) {
// 통신 성공
console.log("success",data, textStatus, jqXHR)
},error:function(jqXHR, textStatus, errorThrown) {
// 에러
console.log("error",jqXHR, textStatus, errorThrown)
},complete:function(jqXHR, textStatus) {
// 성공 or 실패 상관 없이 실행
console.log("complete",jqXHR, textStatus)
}
});
}
</script>
<input type="button" value="중복확인" onclick="rest_check_user()">
</c:otherwise>
</c:choose>
<form action="${pageContext.request.contextPath}/login/user_login" method="post">
<input id="id" name="id">
<br>
<input id="password" name="password" type="password">
<br>
<input type="submit" value="확인">
</form>
</body>
</html>
Ajax를 사용하기 위해 jquery를 추가합니다.
rest_check_user함수를 보면 ajax사용방법 설명이 있습니다.
기본적인 ajax는 form으로 동작하게 되어있더군요.
그래서 기존 웹페이지 에서 받던 @RequestParam으로 받아 값을 확인할 수 있습니다.
그럼 form이 아닌 json으로 통신을 해보겠습니다.
@ResponseBody
@RequestMapping(value="/user_check_json", method = RequestMethod.POST)
public HashMap<String,Object> user_check_json(@RequestBody Map<String, Object> map) {
String id = map.get("id") != null ? map.get("id").toString():"";
String password = map.get("password") != null ? map.get("password").toString():"";
HashMap<String,Object> check_data = loginService.user_check(id, password);
return check_data;
}
위와 같이 @RequestBody를 사용하여 전송되는 Data를 통으로 받습니다. DTO가 있다면 DTO로도 받을 수 있다고 합니다.
$.ajax({
url:'${pageContext.request.contextPath}/login/user_check_json', // 요청 할 주소
async:true, // false 일 경우 동기 요청으로 변경
type:'POST', // GET, POST, PUT, PATCH, DELETE 등
data: JSON.stringify(send_data), // 전송할 데이터
dataType:'json',// xml, json, script, html
contentType:"application/json", // 기본값 : application/x-www-form-urlencoded; charset=UTF-8
beforeSend:function(jqXHR, settings) {
// 전송전
console.log("beforeSend",jqXHR, settings)
},success:function(data, textStatus, jqXHR) {
// 통신 성공
console.log("success",data, textStatus, jqXHR)
if(data.msg_code == "00003" || data.msg_code == "00000") {
alert("아이디 중복 ")
} else if(data.msg_code == "00001") {
alert("아이디를 입력하세요")
} else {
console.log("가입가능")
}
},error:function(jqXHR, textStatus, errorThrown) {
// 에러
console.log("error",jqXHR, textStatus, errorThrown)
},complete:function(jqXHR, textStatus) {
// 성공 or 실패 상관 없이 실행
console.log("complete",jqXHR, textStatus)
}
});
위에 Ajax관련 부분 코드를 변경해주어야합니다.
주석되어있던 contextType에 주석을 제거합니다.
또한 url을 방금 controller 추가한 주소로 변경을합니다.
application/json으로 전송시 값을 받기 위해선 data를 JSON.stringify를 사용하여 JSON형식의 문자열로 변경을 해주어야 데이터를 받을 수 있습니다.제가 다룰지 안다룰지 몰라서 일단 간단하게만 작성하겠습니다.
드디어 기나긴 디비연동 및 Rest API제작 과정이 끝났네요..
사실 값을 전송할 때 @PathVariable이라던지 다른방식으로 처리든 사용이 가능합니다. 저와 함께했던 부분에서 더욱더 붙이며 연습을 해보시면 될것 같습니다.
또한 controller에서 사용하는 페이지를 넘기는 방법도 다양합니다.
또한 많이 사용하는 라이브러리들도 있구요 (예 : tiles, spring security 등)