예제를 통해 Spring MVC 처리 과정에 대해 알아보자.
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
http://localhost/scott/dept 요청
Select* dept
package org.doit.ik.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor //디폴트 생성자
@NoArgsConstructor //모든 필드에 대한 생성자
public class DeptDTO {
private int deptno;
private String dname;
private String loc;
}
/scott/dept 요청 URL
package org.doit.ik;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j //이것과 동일: private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
public class ScottController {
@RequestMapping(value = "/scott/dept", method = RequestMethod.GET)
// 이것을 컨트롤러 메소드 라고 한다
// -> 따로 properties 파일이 필요없다. 요청이 들어오면 아래 함수를 처리하겠다는 의미
public void dept(Locale locale, Model model) {
log.info(">/scott/dept get 요청됨");
}
}
WEB-INF views 폴더에 scott 파일 생성 후, 그 안에 dept.jsp 파일 생성
이제 일반 dept 파일에서 실행시켜보면 이렇게 실행이 된다.
src/main/java 하위의 org.doit.ik.mapper 패키지 안에 .scott으로 패키지를 생성한 뒤 그 안에 DeptMapper.java 파일을 만들자(인터페이스)
package org.doit.ik.mapper.scott;
import java.util.List;
import org.doit.ik.domain.deptDTO;
public interface DeptMapper {
List<deptDTO> selectDept(); //부서정보를 Select 해 List에 담는 인터페이스
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.scott.DeptMapper">
<!-- name은 반드시 인터페이스명, 경로와 동일하게 맞추어준다. -->
<!-- List<deptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
<!-- id 속성에는 반드시 인터페이스의 메소드명이 들어가야 한다. -->
<!-- resultType 속성: select 태그에는 반드시 resultType(return타입)이 들어가야 한다. 담기는 객체의 타입이 들어감 -->
SELECT *
FROM dept
ORDER BY deptno ASC <!-- 세미콜론(;) 찍으면 안 된다! -->
</select>
</mapper>
즉, 만약 가져오고자 하는 정보값이 늘어나 DTO를 수정해야 한다면, Mapper.xml의 쿼리를 수정 -> DeptDTO.java에서 원하는 변수 선언 추가 -> dept.jsp에 ${추가된 변수}를 추가해주면 원하는 정보를 추가적으로 가지고 올 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.scott.DeptMapper">
<!-- name은 반드시 인터페이스명, 경로와 동일하게 맞추어준다. -->
<!-- List<deptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
<!-- id 속성에는 반드시 인터페이스의 메소드명이 들어가야 한다. -->
<!-- resultType 속성: select 태그에는 반드시 resultType(return타입)이 들어가야 한다. 담기는 객체의 타입이 들어감 -->
SELECT d.deptno, dname, loc, COUNT(e.empno) numberOfEmps
FROM dept d FULL JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, dname, loc
ORDER BY deptno ASC
</select>
</mapper>
package org.doit.ik.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor //디폴트 생성자
@NoArgsConstructor //모든 필드에 대한 생성자
public class DeptDTO {
private int deptno;
private String dname;
private String loc;
private int numberOfEmps; //사원수
}
<%@ page 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">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Insert title here</title>
<link rel="shortcut icon" type="image/x-icon" href="../images/SiSt.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
</head>
<body class="wide">
<div class="message" title="Spring MVC 처리 과정">
<ul>
<li> pom.xml - 스프링 5.0.7 </li>
</ul>
</div>
<h3><span class="material-symbols-outlined">lists</span> Dept Info</h3>
<form action= "/scott/emp" method="post">
<table id="tbl-dept">
<caption></caption>
<thead>
<tr>
<th></th>
<th>DeptNo</th>
<th>DName</th>
<th>Loc</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<c:forEach items= "${list}" var="dto">
<tr>
<td><input type="checkbox" data-deptno="${ dto.deptno }" value="${ dto.deptno }" name="deptno"></td>
<td>${ dto.deptno }</td>
<td>${ dto.dname }<span class="badge right red">${dto.numberOfEmps}</span></td>
<td>${ dto.loc }</td>
<td align="center"><span class="material-symbols-outlined delete" data-deptno="${ dto.deptno }">close</span></td>
</tr>
</c:forEach>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<button id="search" class="search" >search</button>
<button id="add" type="button" class="add">부서추가</button>
</td>
</tr>
</tfoot>
</table>
</form>
</body>
</html>
package org.doit.ik;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.mapper.scott.DeptMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j //이것과 동일: private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
public class ScottController {
@Setter(onMethod=@__({@Autowired})) //@Autowired 해도 된다.
private DeptMapper deptMapper;
@RequestMapping(value = "/scott/dept", method = RequestMethod.GET)
//컨트롤러 메소드 -> 따로 properties 파일이 필요없다. 요청이 들어오면 아래 함수를 처리하겠다는 의미
public void dept(Locale locale, Model model) {
log.info(">/scott/dept get 요청됨");
List<DeptDTO> list = this.deptMapper.selectDept();
//list.forEach(dto -> log.info(dto)); //dto를 가져와서 하나씩 출력하는 람다식
model.addAttribute("list", list); //request.setAttribute와 동일
}
}
의존 관계 자동 주입 4가지 @Autowired @Setter @Inject @Resource
- DI(의존성 종속, Dependency Injection): 클래스간의 의존관계를 스프링 컨테이너가 자동으로 연결해주는 것
- Dependency: 객체가 다른 객체와 상호작용하는 것
- Ex) 클래스 A가 클래스 B,C와 상호작용한다면 객체 A는 객체B,C와 의존관계
- @Primary : 같은 Type의 빈에 대해 주입시 가장 높은 우선순위를 갖는다.
- @Autowired로 주입시 @Primary가 붙은 Bean이 주입된다는 것
- Bean만 바꿔서 테스트할때 편하게 사용할 수 있다.
- 같은 Type의 빈이 여러개 있을 때 @Primary가 붙은 빈 1개만 주입 받을 수 있다.
- @Qualifier
- 같은 Type의 빈이 여러개 있을 때 동시에 빈을 여러개 주입받아야 되는 상황에 사용(Application에서 여러개의 디비와 connection을 맺어야 할 경우 Multi DataSource가 필요)
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
emp 파일들 생성
EmpMapper.java 생성(org.doit.ik.mapper.scott 안에)
package org.doit.ik.mapper.scott;
import java.util.ArrayList;
import java.util.List;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpDTO;
public interface EmpMapper {
List<EmpDTO> selectEmp(ArrayList<Integer> deptnos); //체크한 부서번호를 매개변수로 넣어준다.
}
package org.doit.ik.domain;
import java.sql.Date;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmpDTO {
private int empno;
private String ename;
private String job;
private int mgr;
@DateTimeFormat(pattern = "yyyy/MM/dd")
private Date hiredate;
private int sal;
private double comm;
private int deptno;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.scott.EmpMapper">
<!-- name은 반드시 인터페이스명, 경로와 동일하게 맞추어준다. -->
<!-- List<EmpDTO> selectDept(); -->
<select id="selectEmp" resultType="org.doit.ik.domain.EmpDTO">
SELECT*
FROM emp
<where>
<foreach item="deptno" collection="list"
open="deptno IN(" separator=", " close=")">
<!-- collection에는 부서번호를 가진 매개변수를 입력, Mapper.java에서 준 매개변수명 -->
<!-- item(변수명)은 자유롭게 줘도 된다. -->
<!-- open: 반복해서 변수를 찍기 전에 그 앞에 들어갈 문장 -->
#{deptno}
</foreach>
</where>
ORDER BY deptno ASC
</select>
</mapper>
오류 주의
- " separator=", " => separator 앞에 띄어쓰기 안 해주면 404 오류 발생한다.
- org.springframework.beans.factory.UnsatisfiedDependencyException
- org.springframework.beans.factory.BeanCreationException
- java.lang.IllegalArgumentException
- org.apache.ibatis.builder.BuilderException
<%@ page 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">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Insert title here</title>
<link rel="shortcut icon" type="image/x-icon" href="../images/SiSt.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
</head>
<body class="wide">
<h3><span class="material-symbols-outlined">lists</span> Emp List</h3>
<form action= "/scott/dept" method="post">
<table id="tbl-emp">
<caption></caption>
<thead>
<tr>
<th></th>
<th>Empno</th>
<th>Ename</th>
<th>Job</th>
<th>Mgr</th>
<th>Hiredate</th>
<th>Sal</th>
<th>Comm</th>
<th>Deptno</th>
</tr>
</thead>
<tbody>
<c:forEach items="${ list }" var="dto">
<tr>
<td><input type="checkbox" value="${ dto.empno }" name="empno"></td>
<td>${ dto.empno }</td>
<td>${ dto.ename }</td>
<td>${ dto.job }</td>
<td>${ dto.mgr }</td>
<td>${ dto.hiredate }</td>
<td>${ dto.sal }</td>
<td>${ dto.comm }</td>
<td>${ dto.deptno }</td>
</tr>
</c:forEach>
</tbody>
<tfoot>
<tr>
<td colspan="9">
<button id="home" class="home">HOme</button>
</td>
</tr>
</tfoot>
</table>
</form>
<script>
$(function() {
$("#search").on("click", function(event){
if(!$("tbody :checkbox:checked").length){
alert("부서를 체크하세요.");
return;
}//if
$("form").submit();
}); //click
}); //ready
</script>
</body>
</html>
package org.doit.ik;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpDTO;
import org.doit.ik.mapper.scott.DeptMapper;
import org.doit.ik.mapper.scott.EmpMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j //이것과 동일: private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
public class ScottController {
@Setter(onMethod=@__({@Autowired})) //@Autowired 라고 써도 된다.
private DeptMapper deptMapper;
@Setter(onMethod=@__({@Autowired})) //@Autowired 라고 써도 된다.
private EmpMapper empMapper;
@GetMapping("/scott/dept") //이것과 같다 @RequestMapping(value = "/scott/dept", method = RequestMethod.GET)
//컨트롤러 메소드 -> 따로 properties 파일이 필요없다. 요청이 들어오면 아래 함수를 처리하겠다는 의미
public void dept(Locale locale, Model model) {
log.info(">/scott/dept get 요청됨");
List<DeptDTO> list = this.deptMapper.selectDept();
//list.forEach(dto -> log.info(dto)); //dto를 가져와서 하나씩 출력하는 람다식
model.addAttribute("list", list); //request.setAttribute와 동일
}//dept
//Post 방식으로 요청
@PostMapping("/scott/emp")
// @RequestMapping(value = "/scott/dept", method = RequestMethod.POST)
public String emp(@RequestParam(value="deptno")ArrayList<Integer> deptnos, Model model) {
//public void emp(String[]deptnos) 혹은 public void emp(ArrayList<Integer> deptnos)로 받으면 알아서 변환까지 된다.
//request해서 넘어오는 parameter 값은 무조건 String이기 때문
log.info(">/scott/emp post 요청됨");
List<EmpDTO> list = this.empMapper.selectEmp(deptnos);
model.addAttribute("list", list);
return "/scott/emp";
}
}//class
<%@ page 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">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Insert title here</title>
<link rel="shortcut icon" type="image/x-icon" href="../images/SiSt.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
<script src="/resources/js/dept.js"></script>
<style>
span.material-symbols-outlined{
vertical-align: text-bottom
}
</style>
<style>
tbody tr td:last-child span:hover{
color:red;
cursor: pointer;
}
tbody tr:hover{ background-color: rgba(240,240,240,0.5); }
</style>
<style>
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 40%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.close {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
.modal-header {
padding: 2px 16px;
background-color: white;
color: black;
}
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 2px 16px;
background-color: gray;
color: white;
}
</style>
</head>
<body class="wide">
<div class="message" title="Spring MVC 처리 과정">
<ul>
<li> pom.xml - 스프링 5.0.7 </li>
</ul>
</div>
<h3><span class="material-symbols-outlined">lists</span> Dept Info</h3>
<form action= "/scott/emp" method="post">
<table id="tbl-dept">
<caption></caption>
<thead>
<tr>
<th></th>
<th>DeptNo</th>
<th>DName</th>
<th>Loc</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<c:forEach items= "${list}" var="dto">
<tr>
<td><input type="checkbox" data-deptno="${ dto.deptno }" value="${ dto.deptno }" name="deptno"></td>
<td>${ dto.deptno }</td>
<td>${ dto.dname }<span class="badge right red">${dto.numberOfEmps}</span></td>
<td>${ dto.loc }</td>
<td align="center"><span class="material-symbols-outlined delete" data-deptno="${ dto.deptno }">close</span></td>
</tr>
</c:forEach>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<button id="search" class="search" type="button">search</button>
<button id="add" type="button" class="add">부서추가</button>
</td>
</tr>
</tfoot>
</table>
</form>
<!-- 부서 추가 모달창 -->
<!-- The Modal -->
<div id="add-modal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<h2>Ajax 부서 추가</h2>
</div>
<div class="modal-body">
<div class="group">
<label>부서번호</label>
<input type="text" class="short" name="deptno" value="50">
</div>
<div class="group">
<label>부서명</label>
<input type="text" class="short" name="dname" value="QC">
</div>
<div class="group">
<label>지역명</label>
<input type="text" class="short" name="loc" value="SEOUL">
</div>
<div>
<button id="add-dept" type="button" class="ok">확인</button>
<button type="button" class="cancel">닫기</button>
</div>
</div>
<div class="modal-footer">
<h3>Modal Footer</h3>
</div>
</div>
</div>
<script>
$(function() {
$("#search").on("click", function(event){
if(!$("tbody :checkbox:checked").length){
alert("부서를 체크하세요.");
return;
}//if
$("form").submit();
}); //click
//아래로는 부서 추가하기 위해 모달창 띄우는 코드
var addModal = $("#add-modal");
$("#add").on("click", function() { addModal.css("display", "block"); } );
$(".cancel").on("click", function() { addModal.css("display", "none"); } );
$("body").on("click", function (event){
if( event.currentTarget == addModal ) addModal.css("display", "none");
}); //on
//확인 버튼을 누르면 ajax로 부서 추가 + 테이블에도 추가
$("#add-modal #add-dept").on("click", function (){ //확인 버튼을 onclick할 때
//모달창 안의 텍스트박스에 입력한 값을 얻어오는 작업
let deptno = $("#add-modal :text[name=deptno]").val();
let dname = $("#add-modal :text[name=dname]").val();
let loc = $("#add-modal :text[name=loc]").val();
//dept.js 파일에 있는 ajax 함수 이용
//function add(dept, callback, error) { js Object }
let dept={
deptno : deptno,
dname : dname,
loc : loc
};
deptService.add(dept, function(result) {
//callback 함수에 의해 결과물을 가지고 왔다면(성공 시)
//모달창을 닫아준다
addModal.css("display","none");
//ScottRestController의 삼항연산자에 의해 insert값을 가져오는 것을 성공했다면
if(result==='SUCCESS'){
let tr = $(`
<tr>
<td><input type="checkbox" data-deptno="\${ deptno }" value="\${ deptno }" name="deptno"></td>
<td>\${ deptno }</td> //${ dto.deptno }와 동일
<td>\${ dname }<span class="badge right red">0</span></td>
<td>\${ loc }</td>
<td align="center"><span class="material-symbols-outlined delete" data-deptno="\${ deptno }">close</span></td>
</tr>
`);
//위에서 생성한 동적인 객체를 table의 마지막 자식으로 추가
$(tr).appendTo($("table tbody"));
}//if
alert(result);
});//add
}); // click
}); //ready
</script>
</body>
</html>
console.log("Dept Module...");
var deptService = (function(){
function add(dept, callback, error){
console.log("add dept...");
$.ajax({
type:'post',
url:'/scott/dept/new',
data:JSON.stringify(dept),
cache:false,
contentType:"application/json; charset=utf-8",
beforeSend:function(xhr){
//console.log("add dept... beforeSend");
},
success:function(result, status, xhr){
//console.log("add dept... success");
if(callback){
callback(result);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // add
function remove(deptno, callback, error){
console.log("remove dept...");
$.ajax({
type:'delete',
url:'/scott/dept/'+ deptno,
cache:false,
success:function(deleteResult, status, xhr){
if(callback){
callback(deleteResult);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // remove
return {
add : add, <!-- 이 부분이 ajax 처리해주는 것 -->
remove : remove
};
})();
package org.doit.ik;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpDTO;
import org.doit.ik.mapper.scott.DeptMapper;
import org.doit.ik.mapper.scott.EmpMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RestController //ajax 처리를 하는 컨트롤러라는 의미
@RequestMapping("/scott/*")
@Log4j
public class ScottRestController {
@Setter(onMethod=@__({@Autowired})) //@Autowired 라고 써도 된다.
private DeptMapper deptMapper; //DeptMapper set
//scott/dept/new 라는 url로 ajax 호출하므로
@PostMapping("/dept/new")
public ResponseEntity<String> InsertDept(@RequestBody DeptDTO dto){//성공/실패를 String으로 응답하는 ResponseEntity
// insert 하는 InsertDept()
//@RequestBody DeptDTO dto: JSON 형태의 dept 객체를 JAVA의 dept dto 객체로 변환
log.info("/scott/dept/now POST 요청함");
//DeptMapper.java 로 이동해 코드 추가 -> DeptMapper.xml로 이동해 코드 추가
//ResponseEntity: 응답데이터와 응답상태를 같이 보낼 수 있는 데이터 형태
int insertResult = this.deptMapper.insertDept(dto); //1, 0
return insertResult==1? new ResponseEntity<String>("SUCCESS",HttpStatus.OK): new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
//삼항연산자를 이용해 성공하면 성공메세지 SUCCESS와 함께 응답상태를 같이 보내고, 실패 시 응답 상태만 보내준다.
}
}//class
package org.doit.ik.mapper.scott;
import java.util.List;
import org.doit.ik.domain.DeptDTO;
public interface DeptMapper {
List<DeptDTO> selectDept(); //부서정보를 Select 해 List에 담는 인터페이스
int insertDept(DeptDTO dto); //코드 추가
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.scott.DeptMapper">
<!-- name은 반드시 인터페이스명, 경로와 동일하게 맞추어준다. -->
<!-- List<deptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
<!-- id 속성에는 반드시 인터페이스의 메소드명이 들어가야 한다. -->
<!-- resultType 속성: select 태그에는 반드시 resultType(return타입)이 들어가야 한다. 담기는 객체의 타입이 들어감 -->
SELECT d.deptno, dname, loc, COUNT(e.empno) numberOfEmps
FROM dept d FULL JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, dname, loc
ORDER BY deptno ASC
</select>
<!-- int insertDept(DeptDTO dto); -->
<insert id="insertDept">
INSERT INTO dept(deptno, dname, loc)
VALUES(#{deptno}, #{dname}, #{loc})
<!-- #{} 해주면 DeptMapper에 객체 안의 getter에 의해서 자동으로 가져온다. -->
</insert>
</mapper>
url:'/scott/dept/'+ deptno
이렇게 /scott/dept/10
가져가면 10번을 삭제 한다는 것, 이런 url 패턴을 미리 만들어서 활용할 수 있다.console.log("Dept Module...");
var deptService = (function(){
function add(dept, callback, error){
console.log("add dept...");
$.ajax({
type:'post',
url:'/scott/dept/new',
data:JSON.stringify(dept),
cache:false,
contentType:"application/json; charset=utf-8",
beforeSend:function(xhr){
//console.log("add dept... beforeSend");
},
success:function(result, status, xhr){
//console.log("add dept... success");
if(callback){
callback(result);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // add
function remove(deptno, callback, error){
console.log("remove dept...");
$.ajax({
type:'delete',
url:'/scott/dept/'+ deptno,
cache:false,
success:function(deleteResult, status, xhr){
if(callback){
callback(deleteResult);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // remove
return {
add : add,
remove : remove
};
})();
package org.doit.ik.mapper.scott;
import java.util.List;
import org.doit.ik.domain.DeptDTO;
public interface DeptMapper {
List<DeptDTO> selectDept(); //부서정보를 Select 해 List에 담는 인터페이스
int insertDept(DeptDTO dto);
int deleteDept(int deptno); // 삭제하고자 하는 부서번호를 준다.
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.scott.DeptMapper">
<!-- name은 반드시 인터페이스명, 경로와 동일하게 맞추어준다. -->
<!-- List<deptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
<!-- id 속성에는 반드시 인터페이스의 메소드명이 들어가야 한다. -->
<!-- resultType 속성: select 태그에는 반드시 resultType(return타입)이 들어가야 한다. 담기는 객체의 타입이 들어감 -->
SELECT d.deptno, dname, loc, COUNT(e.empno) numberOfEmps
FROM dept d FULL JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, dname, loc
ORDER BY deptno ASC
</select>
<!-- int insertDept(DeptDTO dto); -->
<insert id="insertDept">
INSERT INTO dept(deptno, dname, loc)
VALUES(#{deptno}, #{dname}, #{loc})
<!-- #{} 해주면 자바 객체 안의 값을 가져오는 것 -->
<!-- -->
</insert>
<!-- 삭제하는 코드 -->
<delete id="deleteDept"> <!-- <delete>코드 안에 써주는 것</delete> -->
DELETE FROM dept
WHERE deptno = #{deptno}
</delete>
</mapper>
<%@ page 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">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Insert title here</title>
<link rel="shortcut icon" type="image/x-icon" href="../images/SiSt.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
<script src="/resources/js/dept.js"></script>
<style>
span.material-symbols-outlined{
vertical-align: text-bottom
}
</style>
<style>
tbody tr td:last-child span:hover{
color:red;
cursor: pointer;
}
tbody tr:hover{ background-color: rgba(240,240,240,0.5); }
</style>
<style>
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 40%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.close {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
.modal-header {
padding: 2px 16px;
background-color: white;
color: black;
}
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 2px 16px;
background-color: gray;
color: white;
}
</style>
</head>
<body class="wide">
<div class="message" title="Spring MVC 처리 과정">
<ul>
<li> pom.xml - 스프링 5.0.7 </li>
</ul>
</div>
<h3><span class="material-symbols-outlined">lists</span> Dept Info</h3>
<form action= "/scott/emp" method="post">
<table id="tbl-dept">
<caption></caption>
<thead>
<tr>
<th></th>
<th>DeptNo</th>
<th>DName</th>
<th>Loc</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<c:forEach items= "${list}" var="dto">
<tr>
<td><input type="checkbox" data-deptno="${ dto.deptno }" value="${ dto.deptno }" name="deptno"></td>
<td>${ dto.deptno }</td>
<td>${ dto.dname }<span class="badge right red">${dto.numberOfEmps}</span></td>
<td>${ dto.loc }</td>
<td align="center"><span class="material-symbols-outlined delete" data-deptno="${ dto.deptno }">close</span></td>
</tr>
</c:forEach>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<button id="search" class="search" type="button">search</button>
<button id="add" type="button" class="add">부서추가</button>
</td>
</tr>
</tfoot>
</table>
</form>
<!-- 부서 추가 모달창 -->
<!-- The Modal -->
<div id="add-modal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<h2>Ajax 부서 추가</h2>
</div>
<div class="modal-body">
<div class="group">
<label>부서번호</label>
<input type="text" class="short" name="deptno" value="50">
</div>
<div class="group">
<label>부서명</label>
<input type="text" class="short" name="dname" value="QC">
</div>
<div class="group">
<label>지역명</label>
<input type="text" class="short" name="loc" value="SEOUL">
</div>
<div>
<button id="add-dept" type="button" class="ok">확인</button>
<button type="button" class="cancel">닫기</button>
</div>
</div>
<div class="modal-footer">
<h3>Modal Footer</h3>
</div>
</div>
</div>
<script>
$(function() {
$("#search").on("click", function(event){
if(!$("tbody :checkbox:checked").length){
alert("부서를 체크하세요.");
return;
}//if
$("form").submit();
}); //click
//아래로는 부서 추가하기 위해 모달창 띄우는 코드
var addModal = $("#add-modal");
$("#add").on("click", function() { addModal.css("display", "block"); } );
$(".cancel").on("click", function() { addModal.css("display", "none"); } );
$("body").on("click", function (event){
if( event.currentTarget == addModal ) addModal.css("display", "none");
}); //on
//확인 버튼을 누르면 ajax로 부서 추가 + 테이블에도 추가
$("#add-modal #add-dept").on("click", function (){ //확인 버튼을 onclick할 때
//모달창 안의 텍스트박스에 입력한 값을 얻어오는 작업
let deptno = $("#add-modal :text[name=deptno]").val();
let dname = $("#add-modal :text[name=dname]").val();
let loc = $("#add-modal :text[name=loc]").val();
//dept.js 파일에 있는 ajax 함수 이용
//function add(dept, callback, error) { js Object }
let dept={
deptno : deptno,
dname : dname,
loc : loc
};
deptService.add(dept, function(result) {
//callback 함수에 의해 결과물을 가지고 왔다면(성공 시)
//모달창을 닫아준다
addModal.css("display","none");
//ScottRestController의 삼항연산자에 의해 insert값을 가져오는 것을 성공했다면
if(result==='SUCCESS'){
let tr = $(`
<tr>
<td><input type="checkbox" data-deptno="\${ deptno }" value="\${ deptno }" name="deptno"></td>
<td>\${ deptno }</td> //${ dto.deptno }와 동일
<td>\${ dname }<span class="badge right red">0</span></td>
<td>\${ loc }</td>
<td align="center"><span class="material-symbols-outlined delete" data-deptno="\${ deptno }">close</span></td>
</tr>
`);
//위에서 생성한 동적인 객체를 table의 마지막 자식으로 추가
$(tr)
.appendTo($("table tbody"))
.find("span.delete") //'X'버튼을 나타내는 span 태그
.on("click", function() {
if (confirm("정말 삭제할까요?")) {
let deptno = $(this).data("deptno"); //data-deptno=50
var spanDelete = $(this);
deptService.remove(deptno, function(result) {
if(result==='SUCCESS')
spanDelete.parents("tr").remove();
}); //remove
}//if
});
}//if
alert(result);
});//add
}); // click
//'X'버튼 #tbl-dept > tbody > tr:nth-child(1) > td:nth-child(5) > span
$("#tbl-dept > tbody > tr > td > span").on("click", function(event){
if (confirm("정말 삭제할까요?")) { //삭제 전 삭제여부 확인 위해 경고창 띄워준다.
//<span class="material-symbols-outlined delete" data-deptno="${ dto.deptno }">close</span>
//DB 삭제 작업
let deptno = $(this).data("deptno"); //data-deptno=50
deptService.remove(deptno, function(result) { //첫번째 매개변수: 삭제할 부서번호, 두번째 매개변수: callback 함수
if(result==='SUCCESS') $(event.currentTarget).parents("tr").remove()
//현재 이벤트를 받은 객체('X')의 부모인 'tr' 태그로 DB에서도 화면에서도 삭제된다.
}); //remove
}//if
}); //'X' 삭제 버튼 클릭
}); //ready
</script>
</body>
</html>
package org.doit.ik;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpDTO;
import org.doit.ik.mapper.scott.DeptMapper;
import org.doit.ik.mapper.scott.EmpMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RestController //ajax 처리를 하는 컨트롤러라는 의미
@RequestMapping("/scott/ * ")
@Log4j
public class ScottRestController {
@Setter(onMethod=@__({@Autowired})) //@Autowired 라고 써도 된다.
private DeptMapper deptMapper; //DeptMapper set
//입력
//scott/dept/new 라는 url로 ajax 호출하므로
@PostMapping("/dept/new")
public ResponseEntity<String> InsertDept(@RequestBody DeptDTO dto){
//성공/실패를 String으로 응답하는 ResponseEntity
// insert 하는 InsertDept()
//@RequestBody DeptDTO dto: JSON 형태의 dept 객체를 JAVA의 dept dto 객체로 변환
log.info("/scott/dept/now POST 요청함");
//DeptMapper.java 로 이동해 코드 추가 -> DeptMapper.xml로 이동해 코드 추가
//ResponseEntity: 응답데이터와 응답상태를 같이 보낼 수 있는 데이터 형태
int insertResult = this.deptMapper.insertDept(dto); //1, 0
return insertResult==1? new ResponseEntity<String>("SUCCESS",HttpStatus.OK): new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
//삼항연산자를 이용해 성공하면 성공메세지 SUCCESS와 함께 응답상태를 같이 보내고, 실패 시 응답 상태만 보내준다.
}//insert
//삭제
//scott/dept/10(부서번호) 라는 url로 ajax 호출
//요청방식에는 get, post 말고도 다른 것이 많다. 이번에는 delete 요청방식을 이용해보자.
// Restful: 요청url에 요청방식이 담겨있는 것
@DeleteMapping(value = "/dept/{deptno}"
, produces= {MediaType.TEXT_PLAIN_VALUE})//제공되는 데이터는 text 형태의 value 값이라는 의미
//{변수명}을 주고 @PathVariable("deptno") int deptno하면 url 속에 있는 값을 받아서 변수 deptno에 저장하겠다는 것
public ResponseEntity<String> DeleteDept(@PathVariable("deptno") int deptno){
log.info("/scott/dept/"+deptno+ "DELETE 요청함");
int deltetResult = this.deptMapper.deleteDept(deptno); //1, 0
return deltetResult==1? new ResponseEntity<String>("SUCCESS",HttpStatus.OK): new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
//삼항연산자를 이용해 성공하면 성공메세지 SUCCESS와 함께 응답상태를 같이 보내고, 실패 시 응답 상태만 보내준다.
}
}//class
java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SCOTT.PK_DEPT) violated
- 데이터베이스 테이블의 고유 제약 조건(PK_DEPT)이 위배되었기 때문에 발생
- Unique Column에 대해 같은 값이 들어왔을 때 발생하는 에러
- 먼저 참조되는 데이터를 삭제해주도록 하면 에러 해결된다.