복습
web.xml
- 프로젝트 환경설정 파일
- Dynamic, Maven, Spring 프로젝트 모두 가지고 있는 환경설정 파일
- Apache Tomcat 이 시작되면 자동으로 실행된다
- Spring 환경설정 파일 2개를 불러서 연쇄적으로 실행시켜준다
Controller 클래스
- 여러개를 만들 수 있다, 단 @RequestMapping 이름값은 다르게 설정해야함
- 한가지만 만들어 사용할 수도 있다
@RequestParam 어노테이션
- 자동으로 앞에서 넘어온 값을 받아서 뒤의 변수에 저장한다
- 앞에서 넘어온 name 값과 매개변수 변수명이 같으면 @RequestParam 을 생략해도 된다
- 형변환할 필요 없이 @RequestParam 뒤에 쓰는 자료형에 자동 형변환 된다
ex) 여기선 String 형으로 자동형변환되어 저장됨
@ModelAttribute 어노테이션
- 한꺼번에 값을 받아서 @ModelAttribute 뒤에서 선언된 매개변수인 DTO 객체에 저장해준다
- 다만, DTO 객체의 필드명과 입력양식의 name 값들이 일치할때만 가능하다!
- 그땐 @ModelAttribute 생략도 가능하다
@Autowired
- @inject 어노테이션과 같은 역할
- 이걸 필요한 객체이자 프로퍼티 위에 쓰면 자동으로 객체 생성 및 주입까지 완료된다
- 객체 생성 및 주입 시점 : web.xml 에서 servlet-context.xml 을 불러올떄 모두 완료됨
+ servlet-context.xml 안에서 base-package 를 설정함
업로드 기능
- Spring 에선 fileupload 라이브러리를 많이 써서 첨부파일 업로드를 한다
cos 라이브러리 vs fileupload 라이브러리
cos 라이브러리
- 중복 문제를 자동으로 해결해준다
- 업로드 시켜주는 코드가 따로 없다
- 객체를 생성하면 업로드 된다
fileupload 라이브러리
- 중복 문제를 개발자가 해결해야한다
- 업로드 하는 코드가 따로 있다
ViewResolver
- servlet-context.xml 부분
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
- servlet-context.xml 안에서 View 파일들의 위치를 설정함
- 여기선 /WEB-INF/views/ 폴더로 지정
- View 파일들이 저장될 최상위 디렉토리를 정의
Spring Web Application 예제 2 (이어서)
프로젝트 ch07
파일 설명
- LoginCheck.java : Intercepter Class (구현 클래스)
- UploadController.java : Controller Class
인터셉터 (Interceptor)
- 각 페이지마다 세션이 있는지 확인하고 로그인폼으로 보내는 대신 인터셉터를 사용한다
- 어떤 이름으로 요청이 들어올때만 DispatcherServlet 에서 Controller 로 가기 전에 해당 요청을 가로챈다
- 주로 로그인 해야만 쓸 수 있는 기능에 대해 (정보수정, 회원탈퇴, 로그아웃) 매핑을 잡아서 세션이 있는지 확인하기 위해 인터셉터 사용
인터셉터 설정
- servlet-context.xml 부분
1. 인터셉터 클래스로 bean 을 하나 만든다
2. 인터셉터 클래스가 어떤 경우에 동작할지 매핑을 아래에서 수행한다
3. "/upload.do" 로 요청할때 인터셉터를 동작시키고, 위에서 만든 bean 의 id 값과 연결시켜줌
- 주로 로그인 해야만 쓸 수 있는 기능에 대해 (정보수정, 회원탈퇴, 로그아웃) 매핑을 잡아서 세션이 있는지 확인하기 위해 인터셉터 사용
인터셉터 구현 방법
1. abstract class HandlerInterceptorAdapter 클래스를 상속 받아서 구현
- 3개의 메소드 중 필요한 메소드만 오버라이딩
2. interface HandlerInterceptor 인터페이스를 상속 받아서 구현
- 인터페이스를 상속받았을때는 3개의 메소드를 모두 메소드 오버라이딩 해야한다
- 다음으로 3개의 메소드를 오버라이딩해서 원하는 기능을 작성
- boolean preHandle()
- void postHandle()
- void afterCompletion()
- 메소드 오버라이딩 했으므로 이름과 형식을 그대로 따라야함
인터셉터 메소드 호출 시점
- preHandle() 메소드 : Client 가 요청했을때 DispatcherServlet -> Controller 로 가기전에 호출됨
ex) 여기서는 preHandle() 안에, 세션이 있는지 확인하고 없다면 로그인폼으로 보내는 코드를 작성
- postHandle() 메소드 : Controller 에서 돌아가려고 Dispatcher 로 갈때 호출됨
- afterCompletion() 메소드 : 완전히 View 페이지에 간 후에 호출됨
- LoginCheck.java
package com.ch.ch07;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginCheck implements HandlerInterceptor {
// DispatcherServlet의 화면 처리가 완료된 상태에서 처리
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
// 지정된 컨트롤러의 동작 이후에 처리, Spring MVC의 Front Controller인 DispatcherServlet의 화면 처리가 완료된 상태에서 처리
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
}
// 지정된 컨트롤러의 동작 이전에 가로채는 역할 (세션이 없으면, 로그인 폼으로 이동 하도록 해준다 )
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("id") == null) { // 세션이 없으면
response.sendRedirect("loginForm.do"); // loginform 으로 이동
return false;
}
return true;
}
}
- preHandle() 메소드 안에서 세션 객체를 구한다
- 세션에 id 값이 있는지 확인 후 없다면 비정상 요청으로 판단해 loginForm.do 로 요청, 그 후 return false 하면 메소드를 빠져나감
- 세션값이 있다면 return true 를 통해, 정상적으로 원래 가던 Controller 클래스로 간다
- 로그인 해야만 쓸 수 있는 기능에 대해 (정보수정, 회원탈퇴, 로그아웃) 매핑을 잡아서 세션이 있는지 확인하기 위해 인터셉터 사용
인터셉터 활용
- index.jsp 를 실행하면 "upload.do" 로 요청
- Controller 클래스로 가기 전에 인터셉터가 가로챔
- 세션이 없으므로 loginForm.do 로 요청해서 loginform.jsp 로 이동한다
- 그래서 index.jsp 실행시 로그인 폼으로 가게 되는 것이다
"upload.do" 요청이 들어왔을때 가는 곳
- 같은 이름값 요청이지만 가는 곳이 다르다
- UploadController.java 부분
- 요청이름값이 같더라도 인터셉터 영향 또는 어떤 방식(GET/POST) 으로 요청되었는지에 따라 다른 내용이 실행된다
1) index.jsp 에서 "upload.do" 요청시 인터셉터가 잡아서 upload.jsp 가 아닌 loginform.jsp 로 간다
2) 로그인 성공 후 '업로드' 를 눌러 "upload.do" 요청시, 세션이 있으므로 인터셉터가 잡지못하고 Controller 클래스에 온다, 그 후 위의 @RequestMapping 에 의해 uploadForm() 을 실행하고 upload.jsp 로 가게된다
3) upload.jsp 에서 파일을 선택 후 '확인' 하면 "upload.do" 요청이 되고, 세션이 있으므로 인터셉터가 잡지 못하고 Controller 클래스에 온다, 그 후 아래의 @RequestMapping 에 의해 upload() 를 실행하고 uploadResult.jsp 로 가게 된다
틀린 비밀번호 입력시
1. loginForm.jsp 에서 비밀번호 입력하고 '확인'을 누르면 "login.do" 로 요청된다
2. Controller 에서 Model 객체에 "msg" 값을 저장하고, loginForm.jsp 로 이동한다
+ Controller 클래스에서는 로그인폼에서 온 입력값을 @RequestParam 으로 바로 매개변수에 저장
3. 다시 loginForm.jsp 로 이동했을때는 아래의 if 태그를 만족해서 msg 인 "똑바로 입력해" 를 출력
- 즉 한번 Controller 클래스로 갔다와야 msg 가 있으므로 msg 를 출력한다
- 처음 index.jsp 를 실행해서 loginForm.jsp 로 왔을때는 이 msg 가 없으므로 출력되지 않음
맞는 비밀번호 입력시
1. loginForm.jsp 에서 비밀번호 입력하고 '확인'을 누르면 "login.do" 로 요청된다
2. Controller 에서 id 값을 세션영역 공유설정하고, loginSuccess.jsp 로 간다
- 여기서부터 세션 영역이 시작된다
+ 매개변수에 Session 객체를 적기만 하면 자동으로 Session 객체가 구해진다, Spring 에선 getSession() 을 쓸 필요 없다
- 로그인 성공해서 세션 영역이 시작되었다
- 위 사진은 loginSuccess.jsp 이다
- loginSuccess.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2 class="text-primary">로그인 성공</h2>
<a href="upload.do" class="button button-success">업로드</a>
</body>
</html>
- '업로드' 메뉴를 선택하면 "upload.do" 로 요청한다
- "upload.do" 로 요청했으므로 인터셉터로 간다
+ 메소드 오버라이딩이므로, 메소드 형식을 유지해야한다, 그래서 Session 객체를 매개변수에 써서 자동으로 구하지 않고 직접 구하고 있다
- 세션값이 존재하므로 return true; 되어 원래 가던 Controller 클래스로 간다
- GET 방식 요청이므로 위의 uploadForm() 코드가 실행된다
- upload.jsp 로 간다
- upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container">
<h2 class="text-primary">파일 업로드</h2>
<form action="upload.do" method="post" enctype="multipart/form-data">
<table class="table table-bordered">
<tr>
<th>파일</th>
<td><input type="file" name="file"></td>
</tr>
<tr>
<th colspan="2"><input type="submit" value="확인"></th>
</tr>
</table>
</form>
</div>
</body>
</html>
- 첨부파일을 전송할때는 form 태그 안에 enctype="multipart/form-data" 를 반드시 쓴다
+ 첨부파일이 없을때는 enctype="multipart/form-data" 를 써선 안된다
- 첨부파일을 전송할때는 반드시 form 태그의 method 를 "post" 로 설정해야한다
- 첨부파일의 input type 은 "file" 이다
+ 파일의 name 값이 "file" 임을 주의해서 보자
- 첨부파일을 선택하고 '확인' 을 클릭시 action 인 "upload.do" 로 요청된다
- "upload.do" 로 요청했으므로 인터셉터에 걸린다
- 세션값이 존재하므로 return true; 되어 원래 가던 Controller 클래스로 간다
- 이번에는 POST 방식으로 요청이 왔으므로 아래의 upload() 가 실행된다
- 이 upload() 에서 첨부파일을 업로드 한다, 자세히 보자
첨부파일 업로드
- UploadController.java 에서 upload() 메소드 부분만
@RequestMapping(value = "upload.do", method = RequestMethod.POST)
public String upload(@RequestParam("file") MultipartFile mf, Model model, HttpSession session)
throws IllegalStateException, IOException {
String fileName = mf.getOriginalFilename();
int fileSize = (int) mf.getSize(); // 단위 : Byte
// 첨부파일 업로드
// mf.transferTo(new File("/path/"+fileName));
String path = session.getServletContext().getRealPath("/upload");
System.out.println("path : " + path);
FileOutputStream fos = new FileOutputStream(path + "/" + fileName);
fos.write(mf.getBytes());
fos.close();
model.addAttribute("fileName", fileName);
model.addAttribute("fileSize", fileSize);
return "uploadResult";
}
- fileupload 라이브러리 사용해서 첨부파일 업로드
- MultipartFile 은 fileupload 라이브러리에서 지원되는 클래스명, 파일 업로드를 하기 위한 클래스이며 import 되어있음
import org.springframework.web.multipart.MultipartFile;
- @RequestParam("file") 을 통해 MultipartFile 객체 mf 를 매개변수로 받아온다, upload.jsp 폼에서 사용자가 선택한 파일을 구해온 것
- 그럼 이 mf 는 사용자가 선택한 첨부파일에 대한 정보를 이미 가지고 있게 된다
파일명, 파일크기, 첨부파일 외 다른 입력값 구하기 (UploadController.java 부분)
String fileName = mf.getOriginalFilename(); // 파일명
int fileSize = (int) mf.getSize(); // 파일크기, 단위 : Byte
- mf.getOriginalFilename() 으로 사용자가 저장한 원래 파일명을 구해서 변수 fileName 저장
- mf.getSize() 는 첨부파일의 크기를 반환, 리턴자료형이 LONG 형(8 byte) 이다, 그래서 int 형으로 다운캐스팅해서 int 변수 fileSize 에 저장, 파일 크기 단위는 Byte 이다
+ 현재는 첨부파일 외 다른 입력값이 없다
- 만약 작성자명, 이메일주소, 제목, 내용등의 입력값들이 있다면 @ModelAttribute 어노테이션으로 DTO 객체에 세팅해서 가져오면 된다
실제 업로드되는 폴더 경로 구하기 (UploadController.java 부분)
String path = session.getServletContext().getRealPath("/upload");
- Session 객체를 통해 getServletContext().getRealPath("/upload") 로 upload 폴더의 진짜 절대경로를 구해서 path 변수에 저장한다
업로드하는 방법 2가지 (UploadController.java 부분)
1.
mf.transferTo(new File("/path/"+fileName));
- 1줄로 처리 가능, 업로드 시켜주는 코드이다
2.
FileOutputStream fos = new FileOutputStream(path + "/" + fileName);
fos.write(mf.getBytes());
fos.close();
- FileOutputStream 객체를 생성한다
- mf.getBytes() 로 파일 내용을 읽어옴
- 읽어온 내용을 write() 로 쓰고 객체를 닫음
- 다음은 첨부파일명과 파일크기를 Model 객체에 저장, 나중에 브라우저에 출력시키기 위해서 저장함
model.addAttribute("fileName", fileName);
model.addAttribute("fileSize", fileSize);
- 다음은 출력을 위해 uploadResult.jsp 로 이동
- uploadResult.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>업로드 성공</h2>
파일명 : ${fileName }
<p>파일크기 : ${fileSize }
</body>
</html>
- EL 로 파일명과 파일크기를 출력한다
- UploadController.java 에서 upload() 메소드 부분에서 upload 폴더의 진짜 경로를 구해서 출력하는 코드가 있다
- 콘솔창을 확인해서 진짜 경로를 확인해보자
- 콘솔창 확인
C:\Users\admin\Documents\workspace-sts-3.9.11.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\ch07\upload
- 여기가 진짜 첨부파일이 업로드 되는 upload 폴더의 절대경로이다
- 탐색기에서 확인 가능
MyBatis 연동해서 Oracle DB 연동
Spring - MyBatis 연동 예제 1
프로젝트 myBatis1
실습 준비
- 먼저 scott 계정이 활성화 되어있어야한다
- 다음으로 클라우드의 프로젝트 import 하기
프로젝트 구조
파일들 살펴보기 : pom.xml
Spring 버전
- pom.xml 을 보면 Spring 버전이 나타나있음
- Maven Repository 페이지에서 Spring 최신버전을 확인해 볼 수 있다
- 버전을 높이면 다시 최신버전을 로컬저장소로 다운받아서 사용한다
Connection Pool 을 쓰기 위한 라이브러리
<!-- dbcp jdbc connection pool -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
- Connection Pool 을 쓰기 위한 commons-dbcp 라이브러리, c3p0 라이브러리가 추가되어있다
MyBatis 연동위한 라이브러리
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.1</version>
</dependency>
- Spring 에서 MyBatis 를 쓰려면 MyBatis 라이브러리 뿐 아니라 MyBatis-Spring 연결 라이브러리도 필요하다
파일들 살펴보기 : web.xml
- Apache Tomcat 이 실행시 자동으로 실행되는 파일
- 코드는 생략
web.xml 에 들어가는 내용 3가지
1. Dispatcher Servlet 이름, 위치, 매핑 등록
- Dispatcher Servlet 클래스에 대한 위치가 등록되어있다
- Dispatcher Servlet 으로 찾아가기위한 매핑 등록
- 현재는 do 확장자로 요청할때만 Dispatcher Servlet 으로 찾아감
2. Spring 환경설정 파일 불러오기
- servlet-context.xml, root-context.xml 을 불러서 실행시켜준다
+ 두 파일이 resources 폴더 내에 있는 경우 classpath: 붙이기
3. 한글값 인코딩
- 한글값 인코딩시켜주는 필터와 필터매핑을 잡음
파일들 살펴보기 : servlet-context.xml
- web.xml 이 실행시켜준다
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="myBatis1" />
</beans:beans>
- View 파일들의 위치를 지정하고 있다
<base-package>
- base-package 는 기본적으로는 top-level 패키지 3단계로 만들어진다
- 여기선 수정해서 "myBatis1" 를 base-package 로 설정했다
- src/main/java 폴더 하위의 myBatis 폴더 안의 클래스들을 모두 읽어온다는 의미
파일들 살펴보기 : root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
</beans>
- 비어있다
DB 연동
- 현재 프로젝트인 myBatis1 프로젝트에서는 DB 연동을 MyBatis 에서 제어(처리)하도록 하므로 MyBatis 환경설정 파일 configuration.xml 에서 처리
- 즉, 현재는 root-context.xml 이 아닌 MyBatis 환경설정 파일인 configuration.xml 에서 DB 연동 처리
- 이후에 할 myBatis2 프로젝트에서는 DB 연동을 Spring 에서 제어하도록 하므로 root-context.xml 에 DB 연동 관련 bean 생성, 그땐 root-context.xml 에서 DB연동 처리
- 그래서 현재 myBatis1 프로젝트의 root-context.xml 은 비어있다
MyBatis 관련 내용 복습
https://laker99.tistory.com/139
파일들 살펴보기 : MyBatis 환경설정 파일
1. jdbc.properties
2, configuration.xml
3. Mapper 파일 (테이블 마다)
- MyBatis 환경설정 파일들은 resources 폴더 안에 저장되어야함
- 현재 프로젝트 myBatis1 프로젝트에서는 DB 연동 관련 내용을 MyBatis 에서 제어하도록 해뒀으므로 @Autowired 로 SqlSession 을 주입하고 있지 않는다
- 현재 DAO 클래스에서는 configuration.xml 을 읽어와서 그걸로 SqlSessionFactory 객체를 생성
1. jdbc.properties
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:xe
jdbc.username=scott
jdbc.password=tiger
jdbc.maxPoolSize=20
- DB 연동에 필요한 내용
2. Configuration.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>
<properties resource="jdbc.properties" />
<typeAliases>
<typeAlias alias="dept" type="myBatis1.model.Dept" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="Dept.xml" />
</mappers>
</configuration>
1) typeAlias 로 DTO 클래스 별칭 설정
2) DB 연동
- Setter DI 를 사용
3) Mapper 파일 불러오기
- 같은 폴더 resources 에 있고, 패키지가 없으므로 파일명만으로 불러옴
3. Mapper 파일
- Dept.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="deptns">
<!-- Use type aliases to avoid typing the full classname every time. -->
<resultMap id="deptResult" type="dept">
<result property="deptno" column="deptno" />
<result property="dname" column="dname" />
<result property="loc" column="loc" />
</resultMap>
<select id="list" resultMap="deptResult">
select * from dept order by deptno
</select>
<select id="select" parameterType="int" resultType="dept">
select * from dept where deptno=#{deptno}
</select>
<update id="update" parameterType="dept">
update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
</update>
<delete id="delete" parameterType="int">
delete from dept where deptno=#{deptno}
</delete>
</mapper>
- SQL문만 따로 빼서 만든 파일
- DAO 에서 Configuration.xml 을 읽어오고, Configuration.xml 에선 이 Dept.xml 을 읽어오므로 이 SQL문을 DAO 에서 불러올 수 있다
환경설정 파일
- 현재 프로젝트 myBatis1 의 환경설정 파일은 총 6개
프로젝트 환경설정 파일
- web.xml
Spring 환경설정 파일
- servlet-context.xml
- root-context.xml
MyBatis 환경설정 파일
- resources 폴더 내에 위치
- jdbc.properties
- Configuration.xml
- Dept.xml
Controller / Service / DAO
+ 각각 인터페이스와 구현클래스가 존재한다 ex) DeptService 는 인터페이스, DeptServiceImpl.java 는 구현클래스
+ 각 클래스들은 용도에 따라 myBatis 폴더 하위 다른 폴더 안에 들어가있다
- servlet-context.xml 에 의해 myBatis1 폴더 내의 모든 클래스들을 읽어온다
- 이때 이 myBatis1 폴더 내의 클래스들에는 4개의 어노테이션 중 하나가 붙어있어야한다
- DeptController.java 부분
- Controller클래스에서는 @Autowired 를 사용해서 Service 객체를 주입하고 있음
- Service 클래스에 Setter 메소드 없어도 Controller 클래스에 Service 객체가 주입됨
- DeptServiceImpl.java 부분
- Service 클래스에서는 @Autowired 를 사용해서 DAO 객체를 주입하고 있음
- DAO클래스에 Setter 메소드 없어도 Service 클래스에 DAO객체가 주입됨
- DeptDaoImpl.java 부분
- 현재 프로젝트 myBatis1 프로젝트에서는 DB 연동 관련 내용을 MyBatis 에서 제어하도록 해뒀으므로 @Autowired 로 SqlSession 을 주입하고 있지 않는다
- 현재 DAO 클래스에서는 configuration.xml 을 읽어와서 그걸로 SqlSessionFactory 객체를 생성
+ 나중에 할 myBatis2 프로젝트 에서는 @Autowired 로 SqlSession 객체 주입할 것
현재 프로젝트 myBatis1 흐름
- 뒤쪽 DB 와 연동하는 것은 Model 2 - MyBatis 연동과 같은 방식으로 되어있다
프로젝트 실행
- 오라클 서버(OracleServiceXE, OracleXETNSListener) 가 구동되어 있어야한다
- 오라클 scott 계정이 활성화 되어있어야 한다
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
location.href="deptList.do";
</script>
</body>
</html>
- do 확장자로 요청하므로 Dispatcher Servlet 으로 잘 찾아간다
- Dispatcher Servlet -> Controller 클래스로 이동
- index.jsp 를 실행
- 부서목록을 보여주고 있다
- 부서명 중 하나 클릭시 그 부서명의 상세정보를 보여주고 수정 / 삭제 가능
+ Foreign Key 때문에 삭제가 안된다, 즉 참조하는 자식이 있으므로 부모가 지워지지 않음
+ 삭제를 위해서는 On Delete Cascade 옵션을 붙이거나 해야함
프로젝트 myBatis1 : 부서 목록 페이지
Controller 클래스 DeptControlelr
- index.jsp 에서 "deptList.do" 로 요청했으므로 Controller 클래스쪽으로 가자
- DeptController.java
package myBatis1.controller;
import java.util.List;
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 myBatis1.model.Dept;
import myBatis1.service.DeptService;
@Controller
public class DeptController {
@Autowired
private DeptService ds;
// 부서 목록
@RequestMapping("deptList.do")
public String list(Model model) {
List<Dept> list = ds.list();
model.addAttribute("list", list);
return "deptList";
}
// 상세 페이지
@RequestMapping("deptView.do")
public String deptView(int deptno, Model model) {
Dept dept = ds.select(deptno);
model.addAttribute("dept", dept);
return "deptView";
}
// 수정 폼
@RequestMapping("deptUpdateForm.do")
public String deptUpdateForm(int deptno, Model model) {
Dept dept = ds.select(deptno);
model.addAttribute("dept", dept);
return "deptUpdateForm";
}
// 부서 정보 수정
@RequestMapping("deptUpdate.do")
public String deptUpdate(Dept dept, Model model) {
int result = ds.update(dept);
model.addAttribute("result", result);
return "deptUpdate";
}
// 부서 정보 삭제
@RequestMapping("deptDelete.do")
public String deptDelete(int deptno, Model model) {
int result = ds.delete(deptno);
model.addAttribute("result", result);
return "deptDelete";
}
}
+ @Controller, @Autowired 는 Spring 지원되는 라이브러리 클래스를 import 해야함
DeptController.java 에서 프로퍼티 부분만
@Autowired
private DeptService ds;
- 이 DeptService 는 Service 인터페이스이다
- 인터페이스로 주입해도 되고, 구현클래스로 주입해도 된다, 여기서는 인터페이스에 주입하고 있다
- 즉 어노테이션 기반 DI이며 이미 Service 객체 ds 에 주입이 완료되었음
+ 어노테이션 기반 DI 이므로 Service 클래스에 Setter 메소드가 없어도 여기서 주입 가능
DeptController.java 에서 "deptList.do" 요청 부분만
// 부서 목록
@RequestMapping("deptList.do")
public String list(Model model) {
List<Dept> list = ds.list();
model.addAttribute("list", list);
return "deptList";
}
- index.jsp 실행시 여기로 찾아옴, 자동으로 list() 실행됨
- Service 객체 ds 로 Service 의 메소드인 list() 를 호출한다,
- 그렇게 Service 로 넘어가는 것 * Service 로 넘어가기 아래에
- 리턴받은 결과인 List 를 리스트 list 에 저장하고, Model 객체를 통해 deptList.jsp 로 그 리스트 list 를 저장한다
+ prefix, suffix 생략한 나머지인 "deptList" 를 리턴
- 그 후 Dispacher Servlet -> View 인 deptList.jsp 로 이동
어노테이션 기반 DI 사용 조건
1. 해당 클래스는 servlet-context.xml 에 base-package 로 지정된 myBatis1 패키지의 하위에 들어가있어야함
2. 어노테이션 4가지 중 하나가 붙어있어야함
3. @Autowired 를 주입하고자하는 프로퍼티 위에 쓰기
- 이 DeptService 는 Service 인터페이스이다
Service 구현 클래스 DeptServiceImpl
- Service 의 list() 메소드를 호출했으므로 Service 로 넘어가자
- DeptServiceImpl.java
package myBatis1.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import myBatis1.dao.DeptDao;
import myBatis1.model.Dept;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao dd;
public List<Dept> list() {
return dd.list();
}
public Dept select(int deptno) {
return dd.select(deptno);
}
public int update(Dept dept) {
return dd.update(dept);
}
public int delete(int deptno) {
return dd.delete(deptno);
}
}
- @Autowired 를 통해 DAO 객체를 프로퍼티 dd 에 주입받고 있다
- DAO 객체 dd 를 통해 DAO의 list() 메소드를 호출한다 * DAO 로 이동 아래에
- DAO 클래스에서 돌아올땐 리턴받은 값을 바로 리턴함
- 현재는 MyBatis 쪽에서 DB 연동을 처리하므로 SqlSession 객체를 @Autowired 로 생성하지 않음
- 현재 프로젝트 myBatis1 프로젝트에서는 Mode 1 - MyBatis , Mode 2 - MyBatis 연동할때와 똑같이 연동한다
+ 나중에 할 프로젝트 myBaits2 프로젝트에서는 Spring 쪽에서 DB 연동을 처리할 것, @Autowired 로 SqlSession 객체 생성할 것
DAO 구현 클래스 DeptDaoImpl
- DAO 로 넘어가게 되므로 DAO 를 살펴보자
- DeptDaoImpl.java
package myBatis1.dao;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.stereotype.Repository;
import myBatis1.model.Dept;
@Repository
public class DeptDaoImpl implements DeptDao {
private static SqlSession session;
static {
try {
Reader reader = Resources.getResourceAsReader("configuration.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
session = ssf.openSession(true); // auto commit
reader.close();
} catch (IOException e) {
System.out.println("read file error : "+e.getMessage());
}
}
public List<Dept> list() {
return session.selectList("deptns.list");
}
public Dept select(int deptno) {
return session.selectOne("deptns.select",deptno);
}
public int update(Dept dept) {
return session.update("deptns.update",dept);
}
public int delete(int deptno) {
return session.delete("deptns.delete",deptno);
}
}
DeptDaoImpl.java 에서 SqlSession 객체 구하는 부분만
private static SqlSession session;
static {
try {
Reader reader = Resources.getResourceAsReader("configuration.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
session = ssf.openSession(true); // auto commit
reader.close();
} catch (IOException e) {
System.out.println("read file error : "+e.getMessage());
}
}
- SqlSession 객체를 static 을 붙여서 정적필드로 만듬
- static 으로 블럭을 만들어서 공유함, Model 1, Model 2 에서 했었던 getSession() 메소드 부분에 해당
- MyBatis 의 환경설정 파일 configuration.xml 을 읽어와서 Reader 객체 reader 생성
- reader 객체를 통해 SqlSessionFactory 객체 ssf 를 구함
- ssf.openSession(true) 를 통해 SqlSession 객체 session 을 구해옴
- openSession() 안에 true 가 들어가면 자동으로 커밋이 수행됨
DeptDaoImpl.java 에서 list() 부분만
public List<Dept> list() {
return session.selectList("deptns.list");
}
- 리스트를 구해오기 위해서는 2개이상의 데이터에 대한 상세정보를 구해와야한다, 그래서 selectList() 를 사용
+ selecList() 의 리턴자료형은 List 이다
- deptns 는 Mapper 파일 Dept.xml 안에 설정된 namespace 를 의미, list 는 id 를 의미
- Mapper 파일인 Dept.xml 에서 id 가 list 인 SQL문을 불러오라는 의미
- 이후 Mapper 파일 에서 돌아오면 그 리스트를 그대로 Service 클래스로 리턴한다
Mapper 파일
- Mapper 파일 Dept.xml 로 넘어가보자
- Dept.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="deptns">
<!-- Use type aliases to avoid typing the full classname every time. -->
<resultMap id="deptResult" type="dept">
<result property="deptno" column="deptno" />
<result property="dname" column="dname" />
<result property="loc" column="loc" />
</resultMap>
<select id="list" resultMap="deptResult">
select * from dept order by deptno
</select>
<select id="select" parameterType="int" resultType="dept">
select * from dept where deptno=#{deptno}
</select>
<update id="update" parameterType="dept">
update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
</update>
<delete id="delete" parameterType="int">
delete from dept where deptno=#{deptno}
</delete>
</mapper>
- namespace 가 "deptns"
Dept.xml 에서 id 가 "list" 인 태그 부분만 + resultMap
<!-- Use type aliases to avoid typing the full classname every time. -->
<resultMap id="deptResult" type="dept">
<result property="deptno" column="deptno" />
<result property="dname" column="dname" />
<result property="loc" column="loc" />
</resultMap>
<select id="list" resultMap="deptResult">
select * from dept order by deptno
</select>
- resultType 대신 resultMap 을 쓰고 있다
- resultMap 태그의 id 값으로는 아래의 select 태그에서 resultMap 속성에 쓴 "deptResult" 를 써야함
- resultMap 태그의 type 값으로 DTO alias 인 "dept" 가 들어가야함
- 즉 자동으로 DTO 객체에 세팅해서 돌려주는 resultType 대신 resultMap 으로 일일히 매핑하고 있다
- Dept 테이블 컬럼명과 DTO 프로퍼티명이 다를때 resultMap 을 사용해야한다
- 현재 Dept 테이블 컬럼명과 DTO 인 Dept 클래스 프로퍼티 명이 같으므로 resultMap 안써도 된다
+ join 을 해야할 경우 등 resultMap 을 써야만 하는 경우도 있다
- 아래의 select SQL문을 실행하고, 그 검색결과를 매핑을 통해 여러개의 DTO 객체에 세팅 하고, 그 DTO 객체들을 List 에 담고 그 List 를 DAO 로 돌려줌
- 다시 DAO 로 돌아가야하므로 위로 돌아가자
* 돌아올때 설명은 하늘색 하이라이트
- Contorller 까지 돌아간 후 Dispatcher Servlet -> View 로 가게 된다
View 페이지 deptList.jsp
- deptList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container">
<h2 class="text-primary">부서 목록</h2>
<table class="table table-hover">
<tr>
<th>부서코드</th>
<th>부서명</th>
<th>근무지</th>
</tr>
<c:if test="${not empty list }">
<c:forEach var="dept" items="${list}">
<tr>
<td>${dept.deptno }</td>
<td><a href="deptView.do?deptno=${dept.deptno}"
class="btn btn-info"> ${dept.dname}</a></td>
<td>${dept.loc }</td>
</tr>
</c:forEach>
</c:if>
<c:if test="${empty list }">
<tr>
<th colspan="3">데이터가 없습니다</th>
</tr>
</c:if>
</table>
</div>
</body>
</html>
- Controller 클래스에서 Model 에 저장했던 리스트의 네임값이 "list"
- Model 에 저장된 것이 리스트 이므로 forEach 태그의 items 에 ${list} 를 써서 출력한다
- forEach 태그를 통해 dept 로 개별 요소를 받고 있그 ${dept.deptno}, ${dept.dname}, ${dept.loc} 으로 목록을 출력
+ 부트스트랩 적용된것임
- include 태그로 불러온 header.jsp 파일에서 bootstrap, JSTL core 라이브러리등을 불러왔다
상세 페이지로 이동 (deptList.jsp 부분)
<td><a href="deptView.do?deptno=${dept.deptno}"
class="btn btn-info"> ${dept.dname}</a></td>
- 부서명 클릭시 상세 페이지로 가기 위해 ${dept.dname} 에는 a 태그로 링크를 걸고 "deptView.do" 를 요청한다
- "deptView.do" 로 요청하면서 Primary Key 인 부서 번호 ${dept.deptno} 을 가지고 간다
+ 이때 부서번호를 저장해서 가지고 가는 변수는 "deptno" 이다!
- Dispatcher Servlet -> Controller 로 간다
프로젝트 myBatis1 : 상세 페이지
- 부서명 클릭시 상세 페이지로 가기 위해 ${dept.dname} 에는 a 태그로 링크를 걸고 "deptView.do" 를 요청한다
DeptController.java 에서 "deptView.do" 요청 부분만
// 상세 페이지
@RequestMapping("deptView.do")
public String deptView(int deptno, Model model) {
Dept dept = ds.select(deptno);
model.addAttribute("dept", dept);
return "deptView";
}
- 넘어온 부서 번호를 저장하는 변수와 같은 이름인 "deptno" 를 매개변수에 사용하므로 넘어온 부서번호가 바로 dept 변수에 저장됨
- 즉 @RequestParam("deptno") 가 int deptno 앞에 생략된 것이다
- int deptno 에 맞춰서 형변환까지 int 형으로 되어서 자동으로 deptno 변수에 저장해줌
- Model 객체를 매개변수에 선언만하면 자동으로 Model 객체가 생성됨
- 위에서 @Autowired 로 구해온 Service 객체 ds 로 Service 클래스의 select() 메소드 호출, 호출하며 부서번호 전달
- 그럼 Service 클래스로 가게 됨 * 아래에 설명
< 돌아왔을때>
- DTO 객체를 돌려받아서 Dept 객체 dept 로 받음
- 그 객체 dept 를 Model 을 사용해서 저장하고 deptView.jsp 로 간다
+ DTO 객체가 Model 에 저장되었으므로 View 에선 ${이름값.필드명} 으로 출력함
Service 클래스 DeptServiceImpl.java 에서 select() 메소드 부분만
public Dept select(int deptno) {
return dd.select(deptno);
}
- 위에서 주입된 DAO 객체 dd 를 사용해서 DAO 의 select() 메소드 호출, 호출하며 부서번호 그대로 전달
<돌아왔을때>
- 받은 DTO 객체를 그대로 돌려준다
DAO 클래스 DeptDaoImpl.java 에서 select() 메소드 부분만
public Dept select(int deptno) {
return session.selectOne("deptns.select",deptno);
}
- 1개 부서에 대한 상세 정보 (1개 데이터) 를 구해오므로 selectOne() 메소드 사용
- Mapper 파일 Dept.xml 에서 id 가 select 인 SQL문을 호출
- 두번째 매개변수로 부서번호를 그대로 전달
<돌아왔을때>
- 받은 DTO 객체를 그대로 돌려준다
Mapper 파일 Dept.xml 에서 id 가 select 인 태그 부분만
<select id="select" parameterType="int" resultType="dept">
select * from dept where deptno=#{deptno}
</select>
- 전달되는 값은 부서번호 이므로 parameterType 은 "int"
- 돌려주는 값은 DTO 객체여야 하므로 resultType 은 DTO alias 은 "dept"
- 전달되는 값인 부서번호를 SQL문에서 #{deptno} 형식으로 사용함
- 다시 DAO -> Service -> Controller 로 가서 Dispatcher Servlet -> View 인 deptView.jsp 로 간다
* 돌아올때 설명은 하늘색 하이라이트
- deptView.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- deptList.do 요청한 결과 페이지(deptList.jsp)의 table태그만 불러옴 -->
<script type="text/javascript">
$(function() {
$('#list').load("deptList.do table");
});
</script>
</head>
<body>
<div class="container">
<h2>부서 상세정보</h2>
<table class="table table-bordered">
<tr>
<th>부서코드</th>
<td>${dept.deptno}</td>
</tr>
<tr>
<th>부서명</th>
<td>${dept.dname }</td>
</tr>
<tr>
<th>근무지</th>
<td>${dept.loc }</td>
</tr>
<tr>
<th colspan="2"><a href="deptList.do" class="btn btn-info">목록</a>
<a href="deptUpdateForm.do?deptno=${dept.deptno}" class="btn btn-info">수정</a>
<a href="deptDelete.do?deptno=${dept.deptno}" class="btn btn-info">삭제</a></th>
</tr>
</table>
</div>
<div id="list"></div>
</body>
</html>
- DTO 객체가 Model 에 저장되었으므로 ${이름값.필드명} 으로 출력함, 즉 ${dept.필드명} 으로 출력
ex) ${dept.deptno} 로 부서 번호 출력, ${dept.dname} 으로 부서명 출력
+ 표기법만 ".필드명" 이다, 의미는 getter 메소드로 가져오는 것임
ex) ${dept.dname} 은 dept.getDname() 과 같다
아래에 목록 출력하기 (비동기 처리) (deptView.jsp 부분)
- 아래쪽의 내용은 부서 목록 페이지와 같은 내용이 나타남
<!-- deptList.do 요청한 결과 페이지(deptList.jsp)의 table태그만 불러옴 -->
<script type="text/javascript">
$(function() {
$('#list').load("deptList.do table");
});
</script>
<div id="list"></div>
- ajax 기능 중 load() 함수를 사용해서 구현했다
- id 가 "list" 인 div 태그를 jQuery 로 불러와서 ajax의 load() 함수 사용, 파일을 불러와서 이 영역에 출력시켜라는 의미
- load() 안에는 불러올 파일명이 들어간다, index 파일에 들어간 요청과 같은 (목록 구하기)요청 인 "deptLIst.do" 를 넣는다
- 부서 목록을 출력하는 View 페이지 deptList.jsp 중에서 table 태그 안의 내용만 불러오기 위해 한칸 띄워서 " table" 도 추가 작성
+ 그래서 "부서 목록" 이라는 글자등은 나타나지 않고 딱 테이블만 나타남
+ 비동기로 서버에 요청하는 load() 함수
- $("출력시킬 태그").load("요청할 파일명");
- 특정 서버측의 문서를 불러옴, 안에는 특정 파일명이 들어간다
- 서버측에 비동기적으로 요청하고, 서버측의 지정된 파일의 내용을 모두 불러와서 앞의 태그에 결과 출력 시켜주는 역할
ajax load() 함수 관련 자세한 내용 (검색어 : load() 함수 예제 3)
https://laker99.tistory.com/105
목록 / 수정 / 삭제 링크걸기 (deptView.jsp 부분)
<tr>
<th colspan="2"><a href="deptList.do" class="btn btn-info">목록</a>
<a href="deptUpdateForm.do?deptno=${dept.deptno}" class="btn btn-info">수정</a>
<a href="deptDelete.do?deptno=${dept.deptno}" class="btn btn-info">삭제</a></th>
</tr>
- '목록' 버튼 클릭시 "deptList.do" 로 요청해서 부서 목록 페이지로 간다
- '수정' 버튼 클릭시 "deptUpdateForm.do" 로 요청해서 수정폼으로 간다, Primary Key 인 부서번호를 가져간다
- '삭제' 버튼 클릭시 "deptDelete.do" 로 요청해서 삭제, Primary Key 인 부서번호를 가져간다
- 현재 '삭제' 를 누르면 오류가 발생함 (참조하는 자식이 있기때문에 삭제 불가)
- '목록' 버튼 클릭시에 목록 페이지로 가는 것은 이미 설명했다
프로젝트 myBatis1 : 수정 폼
- '수정' 버튼을 클릭해서 수정폼으로 가보자
- "deptUpdateForm.do" 요청시 수정폼으로 가면서 get 방식으로 부서번호 deptno 를 가져간다
- 부서번호를 가져가야 상세 정보를 다시 구해서 수정폼에 뿌려줄 수 있다
DeptController.java 에서 "deptUpdateForm.do" 요청 부분만
// 수정 폼
@RequestMapping("deptUpdateForm.do")
public String deptUpdateForm(int deptno, Model model) {
Dept dept = ds.select(deptno);
model.addAttribute("dept", dept);
return "deptUpdateForm";
}
+ Controller 클래스 메소드에서 리턴자료형은 대부분 String
- 부서 번호가 get 방식으로 넘어왔고, 그게 넘어온 값의 변수명과 같은 이름인 deptno 변수에 자동으로 형변환 후 저장됨
- int deptno 뒤에 @RequestParam 생략 된 것
+ View 페이지로 값을 가져갈 것이므로 Model 객체를 생성해야함, Model 객체를 매개변수에 써주면 Model 객체가 생성됨
- 위에서 @Autowired 로 주입된 Service 객체 ds 로 Service 클래스의 select() 메소드 호출, 호출하면서 부서번호 전달
- 상세정보를 가져와서 수정폼에 뿌려줘야하므로 아까 목록을 구할때 호출했던 메소드 select() 를 여기서 또 호출
- @Autowired 가 써진 객체 생성 및 주입 시점 : web.xml 에서 servlet-context.xml 을 호출할때
<돌아올 때>
- 상세정보를 저장한 DTO 객체를 dept 로 받아서 Model 객체에 저장
- deptUpdateForm.jsp 로 이동
- Service 클래스로 이동한다
DeptServiceImpl.java 에서 select() 메소드 부분만
public Dept select(int deptno) {
return dd.select(deptno);
}
- 아까 목록을 구할때 호출했던 메소드와 같다
DeptDaoImpl.java 에서 select() 메소드 부분만
public Dept select(int deptno) {
return session.selectOne("deptns.select",deptno);
}
- 아까 목록을 구할때 호출했던 메소드와 같다
Mapper 파일 Dept.xml 에서 id 가 "select" 인 태그 부분만
<select id="select" parameterType="int" resultType="dept">
select * from dept where deptno=#{deptno}
</select>
수정 폼 View 페이지인 deptUpdateForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container" align="center">
<h2 class="text-primary">부서정보 수정</h2>
<form action="deptUpdate.do" method="post">
<input type="hidden" name="deptno" value="${dept.deptno}">
<table class="table table-striped">
<tr>
<th>부서코드</th>
<td>${dept.deptno}</td>
</tr>
<tr>
<th>부서명</th>
<td><input type="text" name="dname" required="required" value="${dept.dname }"></td>
</tr>
<tr>
<th>근무지</th>
<td><input type="text" name="loc" required="required" value="${dept.loc }"></td>
</tr>
<tr>
<th colspan="2"><input type="submit" value="확인"></th>
</tr>
</table>
</form>
</div>
</body>
</html>
- Model 객체에 저장된 값이 DTO 객체이므로 ${네임값.필드명} 으로 출력한다, 여기선 ${dept.필드명} 으로 출력
- 입력양식의 value 속성으로 수정폼에 값을 뿌린다
- 수정폼 입력양식에 값을 입력하고 '확인' 을 누르면 action 인 "deptUpdate.do" 로 요청한다
- 부서 코드는 입력양식이 아닌 출력을 하고 있으므로 넘어가지 않는다, 그래서 hidden 으로 전달해야한다
- 이 값을 전달해야만 where 조건절에 deptno 를 넣어서 1개 부서를 수정 가능하다
- "deptUpdate.do" 로 요청하면서 부서 코드인 deptno 를 hidden 객체로 전달한다
프로젝트 myBatis1 : 부서 정보 수정
- 수정폼 deptUpdateForm.jsp 의 입력양식에 값을 입력하고 '확인' 을 누르면 action 인 "deptUpdate.do" 로 요청한다
DeptController.java 에서 "deptUpdate.do" 요청 부분만
// 부서 정보 수정
@RequestMapping("deptUpdate.do")
public String deptUpdate(Dept dept, Model model) {
int result = ds.update(dept);
model.addAttribute("result", result);
return "deptUpdate";
}
- 수정폼 deptUpdateForm.jsp 에서 넘어온 값들을 DTO Dept 객체 dept 에 세팅(저장)해준다
- 즉 @ModelAttribute 어노테이션이 Dept dept 앞에 생략되어 있다
+ 넘어온 name 값과 DTO 프로퍼티명이 같은 이름인 경우에만 가능하다
- 위에서 @Autowired 로 주입한 Service 객체 ds 로 Service 클래스의 update() 메소드를 호출, 호출하며 사용자가 수정폼에 입력한 값들을 전달함
<돌아온 후>
- DB 수정을 성공하면 1 을 리턴할 것, 그 값을 result 변수에 저장한다
- 그 result 변수를 Model 객체에 저장 후, deptUpdate.jsp 로 간다
DeptServiceImpl.java 에서 update() 부분만
public int update(Dept dept) {
return dd.update(dept);
}
- 받은 객체를 그대로 다시 전달
<돌아올때>
- 성공시 받은 1 을 다시 리턴
DeptDaoImpl.java 에서 update() 부분만
public int update(Dept dept) {
return session.update("deptns.update",dept);
}
- 받은 객체를 그대로 다시 전달
<돌아올 때>
- 이 update() 메소드는 DB 수정 성공시 1을 자동으로 리턴함
Mapper 파일 Dept.xml 에서 id 가 "update" 인 태그 부분만
<update id="update" parameterType="dept">
update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
</update>
- 넘어오는 것이 DTO 객체이므로 그 값을 사용할땐 #{필드명} 으로 사용함
ex) #{loc} 은 dept.getLoc() 과 같은 의미이다
- 수행 후 돌아간다, update 문이므로 돌려주는 returnType 은 적지 않는다
View 페이지인 deptUpdate.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:if test="${result > 0 }">
<script type="text/javascript">
alert("수정 성공");
location.href = "deptList.do";
</script>
</c:if>
<c:if test="${result <= 0 }">
<script type="text/javascript">
alert("수정 실패");
history.go(-1);
</script>
</c:if>
</body>
</html>
- 수정 성공시 result 는 1 이므로 "수정 성공" 메세지를 뿌리고 "deptList.do" 로 요청해서 목록 페이지로 돌아간다
- 수정 실패시 "수정 실패" 메세지를 뿌리고 이전 페이지인 수정 폼으로 돌아간다
프로젝트 myBatis1 : 삭제
- deptView.jsp 에서 '삭제' 버튼을 클릭해서 삭제를 해보자
<a href="deptDelete.do?deptno=${dept.deptno}" class="btn btn-info">삭제</a></th>
- "deptDelete.do" 요청시 Controller 클래스로 가면서 get 방식으로 부서 코드 deptno 를 가져간다
- 부서코드를 가져가야 delet SQL문 실행시 where 절에 부서 코드를 넣어서 해당 부서를 삭제할 수 있다
DeptController.java 에서 "deltDelete.do" 요청 부분만
// 부서 정보 삭제
@RequestMapping("deptDelete.do")
public String deptDelete(int deptno, Model model) {
int result = ds.delete(deptno);
model.addAttribute("result", result);
return "deptDelete";
}
- 넘어온 부서코드가 저장된 변수명과 같은 이름인 deptno 를 매개변수에 써서 바로 deptno 에 넘어온 부서코드 저장
+ @RequestParam("deptno") 가 int deptno 앞에 생략
- Service 객체 ds 를 통해 Service 클래스의 delete() 메소드를 호출함, 호출하면서 부서코드 deptno 를 전달
<돌아올 때>
- 성공여부를 확인하기 위해 result 변수로 delete() 의 리턴값을 받는다
- 그 result 를 Model 객체에 저장하고 deptDelete.jsp 로 간다
DeptServiceImpl.java 에서 delete() 부분만
public int delete(int deptno) {
return dd.delete(deptno);
}
DeptDaoImpl.java 에서 delete() 부분만
public int delete(int deptno) {
return session.delete("deptns.delete",deptno);
}
- Mapper 파일에서 id 가 "delete" 인 SQL문을 불러온다
- 전달하는 값은 부서 코드인 deptno
Mapper 파일 Dept.xml 에서 id 가 delete 인 태그 부분만
<delete id="delete" parameterType="int">
delete from dept where deptno=#{deptno}
</delete>
- #{deptno} 로 가져옴
문제점
- '삭제' 누르면 오류 발생, 무결성 제약조건때문에 오류가 발생한다
- 즉, 해당 부서(부모)를 참조하는 Foreign Key (자식) 이 있으므로7 삭제가 되지 않는다
문제 해결
- SQL문에서 On Delete Cascade 를 사용해서 부모를 지울때 참조하는 자식까지 같이 지운다
View 페이지인 deptDelete.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:if test="${result > 0 }">
<script type="text/javascript">
alert("삭제 성공");
location.href = "deptList.do";
</script>
</c:if>
<c:if test="${result != 1 }">
<script type="text/javascript">
alert("삭제 실패");
history.go(-1);
</script>
</c:if>
</body>
</html>
- 삭제 성공 메시지 또는 실패 메세지를 뿌리고 이동하는 역할을 해야한다
- result 가 0 보다 큰 경우, 즉 삭제에 성공한 경우엔 "삭제 성공" 출력후 "deptList.do" 로 이동, 목록 페이지로 간다
- result 가 1 이 아닌 경우는 삭제 실패를 의미하므로 "삭제 실패" 출력 후 이전 페이인 상세 페이지로 돌아감
- 오늘 한 내용은 아님, 미리 설정 해두기
STS 에 Data Source Management 추가
- STS 는 이클립스와 다르게 Data Source Management 가 없다
- STS 에 plug-in 기능을 추가해야 Data Source Management 를 사용 가능
STS 에 Data Source Management 추가하는 방법