복습

Setter DI

- Setter 메소드가 만들어져 있어야함

ex) Contoller 에 Service 클래스를 주입할때 Service 클래스 안에 DI 가 있어야함

Constructor DI

- 생성자가 만들어져 있어야함

어노테이션 기반 DI

- 3가지 조건을 만족해야함

- 생성자나 Setter 메소드가 없어도 가능함

 

servlet-xml

ViewResolver

- View 파일들이 저장될 최상위 디렉토리 지정

- servlet-context.xml 에서 설정

 

 base-package

- 자바 파일들이 저장될 최상위 디렉토리 지정

- servlet-context.xml 에서 설정

 

root-context.xml

- 직접 bean 을 만들어서 Setter DI, Constructor DI 로 DB 연동 관련 내용 처리

- Controller, Service 와는 달리, DAO 에서 SqlSession 객체를 주입하기 위해서는 root-context.xml 에 bean 이 만들어져 있어야한다

 

+ 프로젝트 실행방법

- index 파일이 만들어져 있을때 프로젝트 Run As -> Run on Server 로 실행 가능

- index 파일 없을땐 다른 방법으로 실행해야한다


프로젝트 myBatis2 : 사원 등록 (이어서)

- 사원 등록폼 및 등록 기능은 모두 구현했고 이제 실제로 50번 부서에 사원을 등록해보자

- 이제 개발부는 참조하는 사원(자식) 이 있으므로 삭제되지 않는다


프로젝트 myBatis2 : 사원 상세 페이지

- 해당 부서의 직원 목록을 출력하는 부서 상세 페이지 empList.jsp 에서 사원의 이름을 클릭하면 사원 상세 페이지로 이동

 

<td><a href="empView.do?empno=${emp.empno}"
		class="btn btn-info">${emp.ename}</a></td>

- 사원명을 클릭하면 "empView.do" 로 요청한다, 요청하면서 사원 번호인 empno 를 GET 방식으로 전달함

 

Controller 클래스 EmpController.java 에서 "empView.do" 요청 부분만

	// 사원 상세페이지
	@RequestMapping("empView.do")
	public String empView(int empno, Model model) {
		Emp emp = es.select(empno); // 사원 상세 정보 구하기
		model.addAttribute("emp", emp);
		return "emp/empView";
	}

- 앞에서 넘어온 값인 사원번호를 @RequestParam (생략) 으로 바로 변수 empno 로 값을 받는다

- 자동으로 int 형으로 형변환되어 저장됨

- 1명에 대한 상세정보를 DB에서 구해와서 View 에 뿌려야 하므로 Model 객체를 매개변수에 선언해서 받음

<돌아온 후>

- 사원의 상세정보는 select() 가 리턴되면서 Emp DTO 객체에 저장되어 돌아온다, 그걸 Emp DTO 객체 emp 에 저장함

- 그 객체 emp 를 Model 객체에 저장하고 /WEB-INF/views/emp/empView.jsp 파일로 이동

- View 페이지로 이동할때 prefix, suffix 를 빼고 작성하고, 하위의 패키지는 적어줘야하므로 "emp/empView" 로 작성

 

Service 클래스 EmpServiceImpl.java 에서 select() 메소드 부분만

	public Emp select(int empno) {
		return ed.select(empno);
	}

- DB 연동을 해야하므로 Service 로 넘어왔다

 

DAO 클래스 EmpDaoImpl.java 에서 select() 메소드 부분만

	public Emp select(int empno) {
		return sst.selectOne("empns.select", empno);
	}

- root-context.xml 에서 SqlSession bean 객체를 생성했고, 그걸 DAO 클래스에 주입했으므로 selectOne() 사용 가능

- 전달된 사원번호 empno 를 selectOne() 을 호추랗며 그대로 전달함

- 사원 1명에 대한 상세정보를 구해야하므로 selectOne() 을 사용

 

Mapper 파일 Emp.xml 에서 id 가 "select" 인 태그 부분만

	<select id="select" parameterType="int" resultType="emp">
		select * from emp where empno=#{empno}
	</select>

- 전달받은 사원 번호로 해당 사원의 상세정보(모든 정보) 를 검색함

- 전달받은 값은 사원번호이므로 전달받은 자료형 parameterType 은 "int"

- 돌려주는 자료형 resultType 은 Emp DTO alias 인 "emp"

 

- 다시 DAO -> Service -> Controller 로 갔다가 View 로 오게 된다

 

View 페이지 empView.jsp

- emp/empView.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>
<script type="text/javascript">
	$(function() {
		$('#list').load('empList.do?deptno=${emp.deptno}');
	});
</script>
</head>
<body>
	<div class="container">
		<h2 class="text-primary">직원 상세정보</h2>
		<table class="table table-bordered">
			<tr>
				<td>사번</td>
				<td>${emp.empno }</td>
			</tr>
			<tr>
				<td>이름</td>
				<td>${emp.ename}</td>
			</tr>
			<tr>
				<td>업무</td>
				<td>${emp.job }</td>
			</tr>
			<tr>
				<td>관리자</td>
				<td>${emp.mgr }</td>
			</tr>
			<tr>
				<td>입사일</td>
				<td>${emp.hiredate }</td>
			</tr>
			<tr>
				<td>급여</td>
				<td>${emp.sal }</td>
			</tr>
			<tr>
				<td>보너스</td>
				<td>${emp.comm }</td>
			</tr>
			<tr>
				<td>부서코드</td>
				<td>${emp.deptno }</td>
			</tr>
		</table>
		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>
		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>
		<a href="empList.do?deptno=${emp.deptno}" class="btn btn-default">목록</a>
		<div id="list"></div>
	</div>
</body>
</html>

- Emp DTO 객체 emp 가 Model 객체에 저장되어 넘어왔으므로 ${emp.필드명} 으로 값을 가져와서 출력한다

ex) 사원번호를 출력할때 ${emp.empno}

- header.jsp 안에 공통적인 내용들이 들어있다 (core 라이브러리, Bootstrap 등)

 

버튼 기능 (empView.jsp 부분)

		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>
		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>
		<a href="empList.do?deptno=${emp.deptno}" class="btn btn-default">목록</a>

- 사원 상세 페이지에도 '수정', '삭제', '목록' 버튼이 있다

- '목록' 을 누르면 해당 부서 상세 페이지로 간다, 해당 부서의 부서번호인 ${emp.deptno} 전달

- '수정' 을 누르면 사원 정보 수정 페이지로 간다

- '삭제' 를 누르면 사원 삭제 페이지로 간다

 


프로젝트 myBatis2 : 사원 정보 수정폼

- 사원 상세 페이지 empView.jsp 에서 '수정' 버튼을 누르면 사원 정보 수정폼으로 간다

		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>

- '수정' 을 누르면 "empUpdateForm.do" 료 요청한다, 요청하면서 사원 번호인 empno 를 전달

 

Controller 클래스 EmpController.java 에서 "empUpdateFomr.do" 요청 부분만

	// 사원 수정폼
	@RequestMapping("empUpdateForm.do")
	public String empUpdateForm(int empno, Model model) {
		Emp emp = es.select(empno);
		List<Dept> deptList = ds.list();
		model.addAttribute("emp", emp);
		model.addAttribute("deptList", deptList);
		return "emp/empUpdateForm";
	}

- 앞에서 넘어온 사원번호 empno 를 @RequestParam("empno") (생략) 을 통해 바로 매개변수의 empno 에 저장한다

사원 수정폼에서 할 DB 연동 작업 2가지

1. 사원 수정폼에 뿌려줄 사원 상세 정보 구하기

- Emp Service 객체 es 로 Emp Service 클래스의 select() 메소드를 호출하고, 사원 번호 empno 를 전달

2. 사원 수정폼에서 부서를 수정할때, 부서 목록을 가져와야한다 

- Dept Service 객체 ds 로 Dept Service 클래스의 list() 메소드를 호출함, 돌아올땐 부서 목록을 List 형태로 돌려줌

 

1. 사원 수정폼에 뿌려줄 사원 상세 정보 구하기

- 이전에 사원 상세 정보를 구할때 했던 내용과 비슷하므로 설명 생략 https://laker99.tistory.com/147

2. 사원 수정폼에서 부서를 수정할때, 부서 목록을 가져와야한다 

- 이전에 사원 등록폼에서 했던 내용과 비슷하므로 설명 생략 https://laker99.tistory.com/147

 

<돌아올때>

- 해당 사원의 상세 정보를 저장한 DTO 객체 emp 와 부서 전체 목록인 deptList 를 Model 에 저장해서 "empUpdateForm.jsp" 로 이동

 

View 페이지인 empUpdateForm.jsp

- emp/empUpdateForm.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="empUpdate.do" method="post">
			<table class="table table-bordered">
				<tr>
					<td>사번</td>
					<td><input type="text" name="empno" readonly="readonly"
						value="${emp.empno}"></td>
				</tr>
				<tr>
					<td>이름</td>
					<td><input type="text" name="ename" required="required"
						value="${emp.ename }"></td>
				</tr>
				<tr>
					<td>업무</td>
					<td><input type="text" name="job" required="required"
						value="${emp.job }"></td>
				</tr>
				<tr>
					<td>급여</td>
					<td><input type="text" name="sal" required="required"
						value="${emp.sal}"></td>
				</tr>
				<tr>
					<td>보너스</td>
					<td><input type="text" name="comm" required="required"
						value="${emp.comm }"></td>
				</tr>
				<tr>
					<td>부서코드</td>
					<td><select name="deptno">
							<c:forEach var="dept" items="${deptList}">
								<c:if test="${emp.deptno==dept.deptno}">
									<option value="${dept.deptno}" selected="selected">
										${dept.dname}(${dept.deptno})</option>
								</c:if>
								<c:if test="${emp.deptno!=dept.deptno}">
									<option value="${dept.deptno}">${dept.dname}(${dept.deptno})</option>
								</c:if>
							</c:forEach>
					</select></td>
				</tr>
				<tr>
					<td colspan="2"><input type="submit" value="수정"></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

- emp.deptno (해당 사원의 부서) 와 dept.deptno (forEach에 의해 모든 부서의 부서번호가 차례대로 들어감) 가 같으면 selected 함으로서 해당 사원이 가입할때 등록했던 부서는 select-option 에서 선택되어 나타나도록 했다

+ jQuery 로 처리하면 간단하게 처리 가능

- 사원 번호을 readonly 속성으로 비활성화했다, readonly 이므로 값이 넘어간다!

- select-option 에 출력되는 내용은 부서명(부서번호) 형식이지만, 저장하는 값인 value 는 부서 번호가 된다

- 사원 수정폼에 수정할 정보를 입력하고 '수정' 을 누르면 action 인 "empUpdate.do" 로 요청한다


프로젝트 myBatis2 : 사원 정보 수정

Controller 클래스 EmpController.java 에서 "empUpdate.do" 요청 부분만

	// 사원 정보 수정
	@RequestMapping("empUpdate.do")
	public String empUpdate(Emp emp, Model model) {
		int result = es.update(emp);
		model.addAttribute("deptno", emp.getDeptno());
		model.addAttribute("result", result);
		return "emp/empUpdate";
	}

- 사원 수정폼에서 넘어온 값들을 @ModelAttribuate (생략) 을 사용해서 Setter 메소드들로 한번에 Emp DTO 객체인 emp 에 저장한다

- Emp Service 객체 es 를 사용해서 update() 메소드를 호출하고, 호출할때 수정할 정보를 저장한 객체 emp 를 전달

- 수정할 사원을 특정하는 사원 번호는 앞의 사원 수정폼에서 넘어왔으므로 emp 객체 안 emp.empno 에 저장되어있음

<돌아온 후>

- 수정 성공시 result 에는 1이 저장된다

- 수정이 끝난 후 부서 상세 페이지로 돌아가므로, 해당 사원이 소속된 부서의 부서번호가 필요하다, 그래서 emp.getDeptno() 를 "deptno" 네임으로 Model 객체에 저장

- View 페이지에서 수정 성공 / 실패 처리를 하기 위해 update() 에서 돌려받은 값 result 를 Model 객체에 저장

- /WEB-INF/views/emp/empUpdate.jsp 로 이동

 

Service 클래스 EmpServiceImpl.java 에서 update() 메소드 부분만

	public int update(Emp emp) {
		return ed.update(emp);
	}

 

DAO 클래스 EmpDaoImpl.java 에서 update() 메소드 부분만

	public int update(Emp emp) {
		return sst.update("empns.update", emp);
	}

- 수정할 정보를 담은 Emp DTO 객체 emp 를 받아서 그대로 전달

- 수정 성공시 자동으로 1을 리턴한다

 

Mapper 파일 Emp.xml 에서 id 가 "update" 태그 부분만

	<update id="update" parameterType="emp">
		update emp set ename=#{ename},job=#{job},sal=${sal},
			comm=#{comm},deptno=#{deptno} where empno=#{empno}
	</update>

- "emp" 는 MyBatis 환경설정 파일 Configuration.xml 에서 설정한 Emp DTO 의 alias 이다

- 객체 emp 가 넘어왔으므로 #{ename} 은 emp.getEname() 과 같은 의미

- 사원번호는 객체 emp 의 emp.empno 에 저장되어 있으므로 #{empno} 로 가져와서 where 절에 넣는다

 

View 페이지 empUpdate.jsp

- emp/empUpdate.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 = "empList.do?deptno=${deptno}";
		</script>
	</c:if>
	<c:if test="${result <= 0 }">
		<script type="text/javascript">
			alert("수정 실패");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- Model 객체에 저장된 "result" 를 통해 성공 / 실패 처리를한다

- 수정이 성공한 후에 "empList.do" 로 요청하고, Model 객체에 저장된 "deptno" 를 전달하면서 해당 부서 상세 페이지로 이동한다

 


프로젝트 myBatis2 : 사원 정보 삭제

- 사원 상세 페이지 empView.jsp 에서 '삭제' 버튼을 누르면 사원 삭제를 할 수 있다

- 삭제 폼은 없고 바로 삭제 된다

		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>

- '삭제' 를 누르면 "empDelete.do" 료 요청한다, 요청하면서 사원 번호인 empno 를 전달

 

Controller 클래스 EmpController.java 에서 "empDelete.do" 요청 부분만

	// 사원 삭제
	@RequestMapping("empDelete.do")
	public String empDelete(int empno, Model model) {
		Emp emp = es.select(empno); // 사원 상세 정보 구하기
		int result = es.delete(empno);
		model.addAttribute("result", result);
		model.addAttribute("deptno", emp.getDeptno());
		return "emp/empDelete";
	}

- 앞에서 넘어온 사원번호 empno 를 @RequestParam("empno") (생략) 을 통해 바로 매개변수의 empno 에 저장한다
사원 삭제할때 DB 연동 작업 2가지

1. 사원 상세 정보 구하기

- 사원을 삭제 한 후 그 사원이 속해있었던 부서의 부서 상세 페이지로 이동할 것이다, 그러려면 부서 번호가 필요함

- 부서 상세 페이지로 가기 위해, 삭제될 사원의 상세 정보를 구해서 거기서 부서 번호를 가져올 것
- 사원의 상세 정보를 구하기 위해 select() 메소드를 호출하며, 사원번호를 전달

2. 사원 삭제 하기

- 사원 삭제를 위해 delete() 메소드를 호출하며, 사원번호를 전달

<돌아온 후>

- 삭제된 사원 상세정보를 받은 emp 에서 emp.getDeptno() 로 사원이 소속되어있던 부서번호를 가져와서 Model 객체에 저장하고 empDelete.jsp 로 이동

 

1. 사원 상세 정보 구하기

- 이전에 여러번 했으므로 설명 생략

2. 사원 삭제하기

Service 클래스 EmpServiceImpl.java 에서 delete() 메소드 부분만

	public int delete(int empno) {
		return ed.delete(empno);
	}

 

DAO 클래스 EmpDaoImpl.java 에서 delete() 메소드 부분만

	public int delete(int empno) {
		return sst.delete("empns.delete", empno);
	}

 

 

Mapper 파일 Emp.xml 에서 id 가 "delete" 인 태그 부분만

	<delete id="delete" parameterType="int">
		delete from emp where empno=#{empno}
	</delete>

 

View 페이지 empDelete.jsp

- emp/empDelete.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 = "empList.do?deptno=${deptno}";
		</script>
	</c:if>
	<c:if test="${result <= 0 }">
		<script type="text/javascript">
			alert("삭제 실패");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- 삭제 성공시 "empList.do" 로 요청하면서 삭제된 사원의 소속 부서인 deptno 를 넘겨주면서 해당 부서 상세 페이지로 이동

 

- 50번 부서에 등록되어있는 사원 LAY2 를 삭제해보자


STS 에 Data Source Management 추가

- STS 에는 이클립스와 다르게 Data Source Management 가 없으므로 DB 연결 불가능하다

- STS 에 plug-in 기능을 추가해야 Data Source Management 를 사용 가능

- 설치 이후 이제 필요한 SQL문을 STS 내에서 실행할 수 있다

 

STS 에 Data Source Management 추가하는 방법

- 이러면 STS 에 plug-in 을 설치한다, 원격 저장소에서 로컬 저장소로 다운받음

- 그 후 STS 를 Restart 시키면 plug-in 이 추가되어있다

- 이후 이제 필요한 SQL문을 STS 내에서 실행할 수 있다


게시판 직접 만들어보기

설계도

spring 실습 설계도

프로젝트 명
spring

계정생성
sqlplus system/oracle
create user spring identified by spring123;

권한 부여 (connect, resource 롤)
grant connect, resource to spring;

테이블명
myboard

create table myboard(
no number primary key,
writer varchar2(20),
passwd varchar2(20),
subject varchar2(50),
content varchar2(100),
readcount number, register date );

시퀀스명
myboard_seq

create sequence myboard_seq;

구조
- base-package 는 myspirng 으로 한다
- prefix 하위에 board 가 있다

환경 설정 파일 작성(세팅) 순서
1) pom.xml
2) web.xml
3) servlet-context.xml
4) configuration.xml
5) board.xml
6) root-context.xml
- 4), 5), 6) 은 DB 연동 관련 내용


오라클 계정 생성 및 권한 부여

- 기존 계정들은 복잡하므로 새 오라클 계정을 생성해서 프로젝트를 하자

 

Spring MVC 프로젝트 생성

- 도메인명 역순으로 지정해야한다

- com.myhome.spring 을 top-level 패키지로 지정

 

프토젝트 테스트

- 프로젝트 오른 마우스 -> Run As -> Run on Server

- 잘 실행되었음을 확인 가능

 

커넥션 생성, 테이블 생성하기

- webapp 폴더 하위에 sql 폴더 추가하고, 안에 SQL 파일 myboard.sql 생성

- 다음으로 커넥션을 연결해야함

- 커넥션이 생성되었다!

- Connection profile 도 설정한다

- 다음은 테이블 생성 위해 myboard.sql 작성

-- 게시판
select * from tab;
select * from seq;
select * from myboard;

-- 테이블명 : myboard
create table myboard(
	  no number primary key,
	  writer varchar2(20),
      passwd varchar2(20),
	  subject varchar2(50),
	  content varchar2(100),
	  readcount number,
	  register date );

-- 시퀀스명 : myboard_seq
create sequence myboard_seq;

- 테이블과 시퀀스 생성하기

- 테이블 및 시퀀스 생성 확인

 

환경설정 파일 세팅

+ 환경 설정 파일 작성(세팅) 순서
1) pom.xml
2) web.xml
3) servlet-context.xml
4) configuration.xml
5) board.xml
6) root-context.xml

 

pom.xml 파일 세팅

- Maven 의 환경설정 파일이다

- 잘 실행되는 내용을 가져와서 복붙하면 된다, 프로젝트 myBatis2 의 pom.xml 을 그대로 가져와서 복붙

- pom.xml 은 프로젝트 바로 하위에 있으므로 프로젝트를 선택하고 붙여넣기 (overwrite)

- 오라클, MyBatis, JSTL 라이브러리들이 모두 들어가있다

- 프로젝트 실행하기

- 환경설정 파일 만들면서 중간중간 실행해서 확인해야함

 

web.xml 파일 세팅

- url-pattern 을 / 에서 *.do 로 수정

	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

- 한글값 인코딩 처리 코드는 web.xml 생성시에 들어가있지 않는다, 직접 넣어줘야한다

- 프로젝트 myBatis2 에서 web.xml 에서 filter 와 filter-mapping 부분 가져오기

	<!-- 한글 입력 -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

- 이 부분 넣어주기

 

요청 -> Dispatcher Servlet -> Controller 테스트

- HomeController.java 부분

- url-pattern 을 *.do 로 바꾸었으므로 샘플로 만들어진 HomeController.java 클래스에서 @RequestMapping 에 value 를 "/" 에서 "/test.do" 로 수정

- 요청 -> Dispatcher Servlet -> Controller 까지의 흐름을 확인하기 위해서이다

- 요청은 index.jsp 에 넣어줄 것

 

index 파일 생성

- 자동으로 실행되도록 하기 위해서 반드시 WEB-INF 폴더 하위에 index.jsp 를 생성해야함

- index.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>
<script>
	location.href = "test.do";
</script>
</body>
</html>

- "test.do" 로 요청해준다

- index.jsp 실행

- 이 화면이 나오면 테스트 완료

- 요청 -> Dispatcher Servlet -> Controller -> View (home.jsp) 로 갔기때문에 이 화면에 나타난다

 

servlet-context.xml 파일 세팅

- base-package 가 top-level 패키지로 되어있다, 이 패키지는 java 폴더 하위에 생성되어잇음

- 즉 base-pacakge 는 자바파일들이 저장될 최상위 패키지

- 우리는 이 top-level 패키지 사용하지 않을 것

 

- myspring 패키지를 java 폴더 하위에 생성하고 myspring 을 base-package 로 설정하기

	<context:component-scan base-package="myspring" />

 

- 최상위 디렉토리 myspring 을 생성했으므로 그 myspring 아래에 controller, service, dao, model 폴더 생성

 

- 다음으로 샘플로 만들어진 HomeContorller.java 를 myspring/controller 로 옮기기

- 그 후 com.myhome.spring 패키지는 삭제 (com 만 삭제하면 다 삭제된다)

- HomeContorller.java 의 패키지도 수정해주기

package myspring.controller;

 

- 환경파일 하나를 설정했으므로 다시 프로젝트 실행해서 실행확인

- servlet-context.xml 환경설정 파일 설정 완료

- 나머지 환경설정은 DB 연동 관련 내용이다, 나중에 할 것

 

Java 파일들 생성하기

- Controller, Service, DAO, DTO 파일들을 myspring 하위의 해당 폴더 안에 파일 생성

- 생성 된 Java 파일들에 어노테이션 추가 (@Controller, @Service, @Repository 등)

- 그리고 주입할 객체 위에 @Autowired 까지 추가

+ 어노테이션은 import 를 해야한다

* 위에서 프로퍼티명을 bd 대신 dao 로 수정했다

- 일단 @Autowired 는 현재 주석해두고 @Controller, @Service, @Repository 만 붙이기

- 여기까지 설정한 후 index 파일 실행해서 확인

 

- 이제 Controller 와 Service클래스에서의 @Autowired 어노테이션 주석을 하나씩 풀고 실행을 다시 해보면 실행이 잘 된다

- DAO 클래스는 아직 @Autowired 주입이 되지 않는다, root-context.xml 세팅을 먼저 해야 주입 가능

 

- BoardDao.java

- SqlSession 또는 SqlSession 구현 클래스로 객체를 주입해야한다

- 현재는 @Autowired 로 객체 주입이 불가능하다, @Autowired 를 쓰면 실행시 오류 발생

-  @Autowired 를 주석으로 막자

- root-context.xml 에서 SqlSessionTemplate bean 객체를 생성해야하고, Constructor DI 를 해서 bean 객체를 생성해야 여기서 @Autowired 사용해서 주입 가능

 

DTO 클래스 작성

- Board.java (DTO) 부분

- 테이블안의 컬럼과 같은 이름으로 프로퍼티들을 만들고 getter / setter 메소드를 생성한다

- 테이블의 컬럼명과 DTO 프로퍼티명이 일치가 되어야 매핑이 자동으로 된다

- number 타입은 int로, Date 타입은 Date(java.util)로, varchar2 타입은 String 으로 변환

- DTO 클래스 안에는 어노테이션을 붙이지 않음

 

View 파일 위치 설정

- servlet-context.xml 에서 /WEB-INF/views/ 가 prefix 로 설정되어있다, 이건 View 파일들이 저장될 최상위 디렉토리이다

	<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>

- 이 /WEB-INF/views/ 하위에 Viwe 파일들을 저장할 폴더 board 를 생성하자

 

폼 가져오기

- 게시판 글 작성 폼 boardform.jsp 를 클라우드에서 폴더 board 로 가져오자

- 가져오는 파일은 이것 하나뿐임, 나머지는 직접 생성해야함

 

+ 현재 상태에서 프로젝트 시작해서 실행 확인해보기, 실행 잘 된다

+ boardform.jsp 는 View 페이지이므로 결과가 여기서 출력되는 것이다,

- boardform.jsp 를 그냥 실행하면 실행되지 않음, 오류발생

 

- boardform.jsp 를 실행해보자

 

- index 파일을 수정해서 boardform.do 로 요청시키자

- Controller 클래스 BoardController.java 에 boardform.do 요청을 받는 코드를 작성해주자

+ HomeController.java 는 삭제하기

 

- BoardController.java 에서 "boardform.do" 요청 받는 부분만

	// 글 작성 폼
	@RequestMapping("boardform.do")
	public String boardform() {
		return "board/boardform";
	}

- 접근제어자는 public, 리턴자료형은 주로 String 으로 메소드 작성

- DB 연동은 아직 처리하지 않으므로 Controller 에서 View 로 가기

- prefix 하위 폴더인 board 를 경로 앞에 붙여야한다

 

- 이제 index 파일 실행시 글 작성 폼이 나타남

- boardform.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글작성</title>
</head>
<body>

<form method=post action="boardwrite.do">
<table border=1 width=400 align=center>
	<caption>글작성</caption>
	<tr><th>작성자명</th>
		<td><input type=text name="writer" required="required"></td>
	</tr>
	<tr><th>비밀번호</th>
		<td><input type=password name="passwd" required="required"></td>
	</tr>
	<tr><th>제목</th>
		<td><input type=text name="subject" required="required"></td>
	</tr>
	<tr><th>내용</th>
		<td><textarea cols=40 rows=5 name="content" required="required"></textarea></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="글작성">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- 이 폼에서 넘어가는 내용은 writer, passwd, subject, content 이다

- 입력하고 "글작성" 클릭시 "boardwrite.do" 로 넘어간다, Controller -> Service -> DAO (Mapper 파일) 으로 가야한다

- 그러기 위해서 DB 연동을 먼저 처리해야함

 

configuration.xml 파일 생성 및 세팅

- 프로젝트 myBatis2 의 configuration.xml 파일 복사해서 현재 프로젝트인 spring 의 resources 폴더에 붙여넣기, 이후 수정

- 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>

	<typeAliases>
		<typeAlias alias="board" type="myspring.model.Board" />
	</typeAliases>	
	
	<!-- 
	<mappers>
		<mapper resource="sql/Dept.xml" />
		<mapper resource="sql/Emp.xml" />
	</mappers>  -->	
	
</configuration>

- 가져온 파일을 현재 프로젝트에 맞게 수정, DTO의 alias 값을 설정해준다

- base-package 부터 시작해서 DTO 까지의 경로를 써준다, myspring.model.Board

- DTO 클래스에 대한 alias 내용 한가지만 들어간다

 

Board.xml (Mapper 파일) 생성 및 세팅

- 먼저 resources 폴더 하위에 sql 폴더를 생성

- myBatis2 의 Mapper 파일인 Dept.xml 이나 Emp.xml 을 현재 프로젝트의 resources/sql로 가져와서 이름을 Board.xml 으로 수정

- Board.xml

- 루트엘리먼트인 mapper 빼고 모두 지우기, namespace 는 "boardns" 로 지정

- 여기까지 하고 프로젝트 실행해서 실행 확인, 잘 된다

 

root-context.xml

- DB 연동 관련 내용이 들어간다

- DB 설정하는 3개의 파일(configuration.xml, board.xml(Mapper), root-context.xml) 중에서 가장 마지막에 root-context.xml 내용을 세팅해야한다

- 프로젝트 myBatis2의 root-context.xml 을 복사해서 같은 위치인 /WEB-INF/spring 폴더에 붙여넣기 (overwrite)

- 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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
	
	<!-- <context:property-placeholder location="classpath:jdbc.properties" /> -->
	
	<!-- dataSource -->
	<!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close">
		<property name="driverClass" value="${jdbc.driverClassName}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
	</bean> -->
	
	<!-- Data Source -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
		<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="spring" />
		<property name="password" value="spring123" />
	</bean>
	
	<!-- 스프링 jdbc 즉 스프링으로 oracle 디비 연결 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:configuration.xml" />
		<property name="mapperLocations" value="classpath:sql/*.xml" />
	</bean>
	
	<bean id="session" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>	
	
</beans>

- 첫번째 bean 에서 username, password 를 테이블을 만든 계정의 username, password 인 "spring", "spring123" 로 수정

- 두번째 bean 에서 Configuration 파일 위치를 설정하는 configLocation 을 설정해야하는데 위치가 맞으므로 그대로 두기

- 두번째 bean 에서 Mapper 파일 위치를 설정하는 mapperLocations 를 설정해야하는데 위치가 맞으므로 그대로 두기

- 세번째 bean 에서 SqlSessionTemplate bean 객체를 생성하고 있다, 위의 sqlSessionFactory 를 Construct DI 로 주입

 

- 그리고 DAO 인 BoardDao.java 로 가서 @Autowired 주석 풀기, 이제는 주석 풀어도 실행 된다

- index 파일 실행시 잘 실행된다

 

- 환경설정이 모두 끝났다, 모든 환경설정 파일 기본세팅을 완료했음

 

- 이제 흐름에 따라 파일들의 내용을 추가하자

- boardform.jsp 파일을 다시 열어보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글작성</title>
</head>
<body>

<form method=post action="boardwrite.do">
<table border=1 width=400 align=center>
	<caption>글작성</caption>
	<tr><th>작성자명</th>
		<td><input type=text name="writer" required="required"></td>
	</tr>
	<tr><th>비밀번호</th>
		<td><input type=password name="passwd" required="required"></td>
	</tr>
	<tr><th>제목</th>
		<td><input type=text name="subject" required="required"></td>
	</tr>
	<tr><th>내용</th>
		<td><textarea cols=40 rows=5 name="content" required="required"></textarea></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="글작성">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- DB 연동을 처리한 후 넘어왔다

- 이 폼에서 넘어가는 내용은 writer, passwd, subject, content 이다

- 입력하고 "글작성" 클릭시 "boardwrite.do" 로 넘어간다, Controller -> Service -> DAO (Mapper 파일) 으로 가야한다

 

- Controller 클래스 BoardController.java 에서 "boardwrite.do" 요청 부분을 처리하자

- 아래 내용을 작성

	// 글 작성
	@RequestMapping("boardwrite.do")
	public String boardwrite(Board board, Model model) {
		int result = bs.insert(board);
		model.addAttribute("result", result);
		return "board/insertresult";
	}

- 폼에서 넘어온 값을 DTO 객체로 한번에 받기 위해 @ModelAttribute 사용

- 메소드 명은 요청 이름값과 같게 맞췄다

- 글을 작성(insert) 한 후 성공 / 실패를 View 에서 처리하기위해 어떠한 값들을 가져갈 것이므로 Model 객체 선언해서 받기

 

- Service 객체 bs 를 사용해서 Service 클래스의 insert() 메소드를 호출, 이때 삽입할 데이터인 객체 board 를 가져감, 리턴자료형은 int 이다

 

- Service 클래스 BoardService.java 에서 insert() 메소드를 생성해야한다

	public int insert(Board board) {
		return dao.insert(board);
	}

- Controller 에서 이 메소드를 호출해야하므로 public, 리턴자료형은 int, 메소드 명은 insert(), 매개변수 자료형은 Board

- DAO 의 insert() 를 호출

 

- 이 코드를 복사 후 DAO 클래스 BoardDao.java 로 가서 복붙 하고 수정

- BoardDao.java 에서 insert() 메소드 생성

	public int insert(Board board) {
		return session.insert("insert", board);
	}

- 이 insert() 가 실제 SQL문을 수행하고 성공시 1 을 반환함

 

- Mapper 파일인 Board.xml 에 id 가 "insert" 인 insert (글 작성) 문을 생성해야함

	<!-- 글 작성 -->
	<insert id="insert" parameterType="board">
		insert into myboard values(myboard_seq.nextval,#{writer},
		#{passwd},#{subject},#{content},0,sysdate)
	</insert>

- 전달되는 값이 DTO Board 객체 board 이므로 DTO Board 의 alias 인 "board" 를 parameterType 에 설정

- no 컬럼은 sequence 인 myboard_seq 로 세팅

- 앞에서 객체 board 가 넘어왔으므로 #{writer}, #{passwd}, #{subject}, #{content} 로 세팅

- 조회수 readcount 컬럼은 0, 등록일 register 컬럼은 sysdate 로 세팅

 

- DAO -> Service -> Controller 로 다시 돌아왔다

- Controller "boardwrite.do" 요청 부분 다시 보기

	// 글 작성
	@RequestMapping("boardwrite.do")
	public String boardwrite(Board board, Model model) {
		
		int result = bs.insert(board);
		if(result == 1) System.out.println("글 작성 성공");
		
		model.addAttribute("result", result);
		return "board/insertresult";
	}

- result 값을 한번 찍어보기

- 그리고 /WEB-INF/views/board/insetresult.jsp 로 이동한다, 이 insertresult.jsp 파일을 board 폴더 하위에 생성해야함

- 이 파일에서 성공 / 실패 처리, 즉 출력을 할 것이다

 

- insertresult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="${result == 1}">
	<script>
		alert("글 작성 성공");
		location.href = "boardlist.do";
	</script>
</c:if>

<c:if test="${result != 1}">
	<script>
		alert("글 작성 실패");
		history.go(-1);
	</script>
</c:if>

- JSTL core 라이브러리 사용하기 위해 core 라이브러리 불러오는 코드 한 줄 추가

- 글 작성 성공하면 목록 페이지로 갈 것이므로 "boardlist.do" 요청하기

- 글 작성 실패시 이전 페이지인 글 작성 폼으로 돌아감

 

- index 파일을 실행시켜서 글 작성폼에서 글을 작성해보자

+ 글 수정 / 삭제 시 비밀번호가 맞아야 삭제되도록 하기 위해 비밀번호를 입력받는 것임

- "boardlist.do" 요청 처리를 하지 않았으므로 오류가 뜬다

 

- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (미완성, 수정 전)

	// 글 목록
	@RequestMapping("boardlist.do")
	public String boardlist(HttpServletRequest request, Model model) {
		
		int page = 1;	// 현재 페이지 번호
		int limit = 10;	// 한 화면에 출력할 데이터 갯수
		
		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}
		
		int startRow = (page - 1) * limit + 1;
		int endRow = page * limit;
		
		int listcount = bs.getCount();	// 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		return "board/boardlist";
	}

 

- 기본변수, 파생변수를 설정해서 DB에서 목록을 구해오고, 페이징 처리를 하고, 그걸 View 에 뿌려야함

- request 객체를 매개변수로 부터 받는다, 이렇게 매개변수에 선언하면 자동으로 request 객체를 받아옴

- 결과를 View 페이지에 뿌려야하므로 Model 객체도 매개변수에 선언해서 받아온다

1. 기본변수 page : 현재 페이지를 저장할 변수

2. 기본변수 limit : 한 화면에 출력할 데이터 개수를 저장하는 변수

3. 파생변수 starRow, endRow : page, limit 을 이용해서 구해준다, page, limit 로 생성 가능하므로 굳이 생성하지 않아도 된다

4. 기본변수 listcount : 총 데이터 갯수를 저장, DB 와 연동해서 count(*) 로 가져올 것 

<총 데이터 개수 가져오기>

- Service 클래스의 getCount() 를 호출해서 리턴받는다 * 아래에서 Service 에서 getCount() 를 생성함

- 나머지 부분은 아래에서 다시 완성

 

- Service 클래스 BoardService.java 에서 getCount() 메소드를 생성해야함

	public int getCount() {
		return dao.getCount();
	}

 

- DAO 클래스 BoardDao.java 에서 getCount() 메소드 생성하기

	public int getCount() {
		return session.selectOne("count");
	}

- 그룹함수는 결과가 1개이므로 selectOne() 메소드 사용, id 가 "count" 인 SQL문 불러오기

 

- Mapper 파일 Board.xml 에서 id 가 "count" 인 SQL문 생성

	<!-- 총 데이터 갯수 -->
	<select id="count" resultType="int">
		select count(*) from myboard
	</select>

- 돌려주는 값이 총 데이터 갯수이므로 resultType 은 "int"

 

- 총 데이터 갯수를 가져왔는지 확인하기 위해 현재 상태에서 index 파일에서 "boardlist.do" 로 요청해보자

- index 파일 실행 하고 콘솔창 보기

- listcount 값이 찍혀 나왔다, 글을 하나 작성했었으므로 listcount : 1 이 맞다

 

- 이제 목록을 가져오는 작업을 Controller 부터 처리하자

- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (이어서, 완성, 추가되었다)

	// 글 목록
	@RequestMapping("boardlist.do")
	public String boardlist(HttpServletRequest request, Model model) {
		
		int page = 1;	// 현재 페이지 번호
		int limit = 10;	// 한 화면에 출력할 데이터 갯수
		
		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}
		
		int startRow = (page - 1) * limit + 1;
		int endRow = page * limit;
		
		int listcount = bs.getCount();	// 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		List<Board> boardlist = bs.getBoardList(page); // 게시판 목록
		System.out.println("boardlist : " + boardlist);
		
		// 총 페이지
		int pageCount = listcount / limit + ((listcount % limit == 0) ? 0 : 1);
		
		int startPage = ((page - 1) / 10) * limit + 1; // 1, 11, 21...
		int endPage = startPage + 10 - 1;	// 10, 20, 30...
		
		if(endPage > pageCount) endPage = pageCount;
		
		model.addAttribute("page", page);
		model.addAttribute("listcount", listcount);
		model.addAttribute("boardlist", boardlist); // 리스트
		model.addAttribute("pageCount", pageCount);
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		
		return "board/boardlist";
	}

<목록 가져오기>

- MyBatis 로 처리하면 DAO -> Mapper 파일로 갈때 단 하나의 값만 전달가능하다

- limit 은 고정되어있고, startRow, endRow 는 나중에 페이지 번호 page 로 계산 가능하므로 페이지번호 page 만 넘기기

+ 또는 map 객체에 키밸류 형태로 저장하는 방법도 있다

- getBoardList() 에서 글들이 저장된 리스트를 반환하도록 할 것 * 아래에서 설명

<돌아온 후>

- getBoardList() 에서 돌아온 후 콘솔창 출력해보면 boadlist 에 내가 썼던 글의 주소가 출력된다

5. 파생변수 pageCount : 총 페이지 수를 구함, listcount 와 limit 사용

6. 파생변수 startPage & endPage : 각(현재) 블럭의 시작 페이지, 끝 페이지, page 와 limit 사용

- 이후 기본변수, 파생변수, 가져온 목록(List) 들을 Model 객체에 저장하고 board/boardlist.jsp 로 이동

 

- Service 클래스 BoardService.java 에서 목록을 가져오는 메소드 getBoardList() 를 생성하자

	public List<Board> getBoardList(int page) {
		return dao.getBoardList(page);
	}

 

- DAO 클래스 BoardDao.java 에서 getBoardList() 를 작성하자

	public List<Board> getBoardList(int page) {
		return session.selectList("list", page);
	}

- 여러개의 글(DTO 객체) 을 가져올 것이므로 selectList() 메소드 사용

- 페이지 번호 page 를 전달함, 받는 값은 DTO 객체들이 저장된 리스트이다

 

- Mapper 파일 Baord.xml 에서 id 가 "list" 인 SQL문 작성

	<!-- 글 목록 -->
	<!-- &gt; : > , &lt; : < -->
	<select id="list" parameterType="int" resultType="board">
		<![CDATA[
		select * from (select rownum rnum, board.* from (
		select * from myboard order by no desc) board )
		where rnum >= ((#{page}-1) * 10 + 1) and rnum <= (#{page} * 10)
		]]>
	</select>

- 페이지 번호가 넘어오므로 parameterType 은 "int" , 돌려주는 값은 DTO 가 저장된 리스트 이므로 resultType 은 "board"(DTO alias) 

- limit 은 10으로 고정되어있으므로 10 으로 쓰고, startRow, endRow 는 페이지 번호 page 로 계산 가능해서 사용하기

+ <. > 인식을 못하므로 &gt; , $lt; 사용, 또는 <![CDATA[ ]]> 로 전체 SQL문으로 감싸주면 부등호 기호 <, > 를 인식함

 

- View 페이지를 보기 전에 페이징 처리를 확인하기 위해 myboard.sql 에서 데이터를 강제로 많이 삽입하자

insert into myboard values(myboard_seq.nextval,'Lay','1234','Practice1',
	'Content',0,systdate)

 

- View 페이지 board/boardlist.jsp 를 생성해서 Contorller 에서 Model 객체에 저장된 값들을 활용해서 목록을 출력하자

- boardlist.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>
	<a href="boardform.do">글작성</a> <br>
	글갯수 : ${listcount }
	
	<table border=1 align=center width=700>
		<caption>게시판 목록</caption>
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성자</th>
			<th>날짜</th>
			<th>조회수</th>
		</tr>
		
		<!-- 화면 출력 번호 -->
		<c:set var="num" value="${listcount - (page - 1) * 10}"/>

		<c:forEach var="b" items="${boardlist}">
		<tr>
			<td>${num}
				<c:set var="num" value="${num-1}"/>
			</td>
			<td>
				<a href="boardcontent.do?no=${b.no}&page=${page}">${b.subject}</a>
			</td>
			<td>${b.writer}</td>
			<td>
				<fmt:formatDate value="${b.register}" 
				pattern="yyyy-MM-dd HH:mm:ss"/>
			</td>
			<td>${b.readcount}</td>
		</tr>
		</c:forEach>
		
	</table>
<br>
<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">

<!-- 1페이지로 이동 -->
<a href="boardlist.do?page=1" style="text-decoration:none"> << </a>
<!-- 이전 블럭으로 이동 -->
<c:if test="${startpage > 10}">
	<a href="boardlist.do?page=${startPage - 10}">[이전]</a>
</c:if>
<!-- 각 블럭에 10개 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
	<c:if test="${i == page}"> <!-- 현재 페이지 -->
		[${i}]
	</c:if>
	<c:if test="${i != page}"> <!-- 현재 페이지가 아닌 경우-->
		<a href="boardlist.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>
<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="boardlist.do?page=${startPage + 10}">[다음]</a>
</c:if>
<!-- 마지막 페이지로 이동 -->
<a href="boardlist.do?page=${pageCount}" style="text-decoration:none"> >> </a>

</c:if>
</center>

</body>
</html>

- JSTL core 라이브러리, 국제화 라이브러리를 불러오기

7. 파생변수 num : 화면 출력 번호, 코어 라이브러리 set 태그 사용해서 생성했음, listcount, page 를 사용해서 생성

- forEach 태그 items 안에 목록 리스트인 ${boardlist} 를 써서 하나씩 글을 가져오기

- forEach 루프가 돌아갈때마다 num 을 1씩 감소시키기, 이때 코어 라이브러리 set 태그 사용해서 재정의 하는 방식

			<td>${num}
				<c:set var="num" value="${num-1}"
			</td>

- 등록일은 국제화 라이브러리 formatDate 태그로 패턴지정해서 적용

- 제목을 클릭하면 상세페이지로 가기 위해 "boardcontent.do" 로 요청하고 글번호 no, 페이지 번호 page 전달

- table 태그 아래에는 페이지 처리를 하고 있음, 자주 했으므로 설명 생략

 

프로젝트 myBatis1 vs 프로젝트 myBatis2

프로젝트 myBatis1

- 이전 프로젝트는 DAO 클래스에서 configuration.xml(MyBatis 환경설정 파일) 을 읽어와서 그걸로 SqlSessionFactory 객체를 생성

- 이전 프로젝트는 MyBatis 쪽에서 DB 연동을 처리하므로 SqlSession 객체를 @Autowired 로 생성하지 않음

- 이전 프로젝트 myBatis1 프로젝트에서는 Mode 1 - MyBatis , Mode 2 - MyBatis 연동할때와 똑같이 연동한다

 

프로젝트 myBatis2

- 현재 프로젝트는 DAO 클래스에서 @Autowired 로 SqlSession 을 주입할 것

- DB연동을 위해서 MyBatis 환경설정 파일들 대신 root-context.xml 에서 DB 접속에 대한 내용을 처리

- 내용은 프로젝트 myBaits1 과 연결된 내용

- 현재 프로젝트 myBatis2 프로젝트에서는 DB 연동 관련 내용을 Spring에서 제어, Spring 에서 DB 연동할 것


MyBatis 연동해서 Oracle DB 연동

 

Spring - MyBatis 연동 예제 2

프로젝트 myBatis2

 

실습 준비

- 먼저 오라클의 scott 계정이 활성화 되어있어야한다

- scott 계정 소유 테이블을 사용하므로 따로 테이블을 만들지는 않음

- 다음으로 클라우드의 프로젝트 import 하기

 

프로젝트 구조

- DB 연동에 필요한 MyBatis 환경설정 파일들은 있지만 안에 내용이 없음

- 인터페이스와 구현클래스가 1쌍씩 만들어져 있다 ex) Service 인터페이스, Service 구현클래스

- 테이블이 2개이므로 Controller, DAO, Service 가 2개씩 만들어져있다

 

테이블이 늘어나면

1. Controller 클래스가 늘어남

2. DAO 클래스도 늘어남

3. Service 클래스도 늘어남

4. Mapper 파일도 늘어남

- 여기서 테이블을 Dept, Emp 둘 다 사용하므로 Dept 클래스들이 따로, 따로 Emp 클래스들이 따로 만들어짐


파일들 살펴보기 : pom.xml

- 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

- web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<!-- 한글 입력 -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
</web-app>

- 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

<?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="myBatis2" />	
	
</beans:beans>

<ViewResolver>

- View 파일들이 저장될 최상위 디렉트리를 지정하고 있다

- 현재는 "/WEB-INF/views" 가 View 파일들의 최상위 디렉토리이다

- 주의 : /WEB-INF/views 아래 어떤 폴더가 있고 그 안에 어떤 View 페이지가 있다면 Controller 에서 View 페이지로 찾아갈때 그 하위 폴더를 경로에 추가해야함

ex) 현재는 views 폴더 아래 emp 폴더가 있고 그 안에 일부 View 파일들이 있다, Controller 클래스에서 그 파일로 갈때는 return "emp/empList.jsp" 로 가야함

<base-package>

- base-package 를 지정한다는 의미는 어노테이션 기반을 사용하겠다는 의미

-base-package는 base 가 되는 자바파일들이 저장될 최상위 디렉토리를 지정하는 것이다, 이 패키지는 java 폴더 하위에 있어야한다

- 이 base-package 안에 클래스들이 있어야함, 그 클래스들 위에는 4가지 어노테이션 중 하나가 붙어있어야만 Service / Controller / DAO 기능 수행 가능

- base-package 는 기본적으로는 top-level 패키지 3단계로 만들어진다

- 여기선 수정해서 "myBatis2" 를 base-package 로 설정했다

- src/main/java 폴더 하위의 myBatis2 폴더 안의 클래스들을 모두 읽어온다는 의미

 

+ servlet-context.xml 에서는 Setter DI (ViewResolver) 도 쓰고 있고 어노테이션 기반 DI (base-package) 도 있다

 

+ 원래 Spring 프로젝트 생성시 지정했던 top-level 패키지명 보기

- pom.xml 부분

- com.ch.myBatis2 이었다

- 이 패키지를 지우고 myBatis2 만 남긴것


파일들 살펴보기 : MyBatis 환경설정 파일

1. jdbc.properties

2, configuration.xml

3. Mapper 파일 (테이블 마다)

 


파일들 살펴보기 : Configuration.xml

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>

	<typeAliases>
		<typeAlias alias="dept" type="myBatis2.model.Dept" />
		<typeAlias alias="emp"  type="myBatis2.model.Emp" />
	</typeAliases>	
	
	<!-- 
	<mappers>
		<mapper resource="sql/Dept.xml" />
		<mapper resource="sql/Emp.xml" />
	</mappers>  -->	
	
</configuration>

 

- configuration.xml 의 3가지 내용(DTO alias, DB접속 내용, Mapper 파일 호출) 중 DTO alias 설정하는 코드만 남아있다

- DTO alias 설정 내용만 있다

 

3. Mapper 파일 (테이블 마다)

- Dept.xml, Emp.xml 이 있다

- Mapper 파일들 안에 내용은 모두 그대로 이다

- Spring - MyBatis 연동하고 Spring 으로 DB 연동을 처리하더라도 Mapper 파일의 내용은 같다, 그대로 SQL문들이 있음

- Spring - MyBatis 연동할때 Mapper 파일일은 JSP - MyBatis 연동때 Mapper 파일과 똑같다


파일들 살펴보기 : root-context.xml

- 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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
	
	<!-- <context:property-placeholder location="classpath:jdbc.properties" /> -->
	
	<!-- dataSource -->
	<!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close">
		<property name="driverClass" value="${jdbc.driverClassName}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
	</bean> -->
	
	<!-- Data Source -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
		<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="scott" />
		<property name="password" value="tiger" />
	</bean>
	
	<!-- 스프링 jdbc 즉 스프링으로 oracle 디비 연결 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:configuration.xml" />
		<property name="mapperLocations" value="classpath:sql/*.xml" />
	</bean>
	
	<bean id="session" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>	
	
</beans>

- DB 연동을 Spring 쪽에서 제어하므로, root-context.xml 안에 DB 연결에 필요한 내용을 처리

- 이전 프로젝트와 다르게 configuration.xml 에는 DTO alias 내용만 남고 나머지 설정은 모두 이 root-context. 가 처리

- root-context.xml 에서는 어노테이션 기반 DI + Setter DI 다 있다

- root-context.xml 에서 beans 가 루트 엘리먼트이고 안에서 3개의 bean 을 만들어서 처리하고 있다

- 3개의 bean 을 만들면서 2번의 Setter DI 와 1번의 Constructor DI 를 하고 있다

 

root-context.xml 부분 1

	<!-- Data Source -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
		<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="scott" />
		<property name="password" value="tiger" />
	</bean>

- 여러 클래스를 사용 가능하지만 현재는 SimpleDriverDataSource 클래스를 사용, 클래스마다 정해진 Property 들이 달라진다

- Setter DI 를 통해 필드 driverClass, url, username, password 에 value 의 값들을 주입하고 있다

- 오라클 연결 관련 내용(계정명, 비번 등) 들을 주입하고 있다

 

root-context.xml 부분 2

	<!-- 스프링 jdbc 즉 스프링으로 oracle 디비 연결 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:configuration.xml" />
		<property name="mapperLocations" value="classpath:sql/*.xml" />
	</bean>

- SqlSessionFactoryBean 객체 sqlSessionFactory 를 생성하고 있다

- SqlSessionFactoryBean 클래스의 프로퍼티 dataSource, configLocation, mapperLocations 에 값들을 주입하고 있다

 

DB 연결 코드 (root-context.xml 부분 2-1)

		<property name="dataSource" ref="dataSource" />

- dataSource 프로퍼티로 위에서 만든 SimpleDriverDataSource 객체인 dataSource 객체를 주입시킨다

- 이 코드가 DB 를 연결하는 코드이다

 

DB 연결 코드 (root-context.xml 부분 2-2)

		<property name="configLocation" value="classpath:configuration.xml" />

- configLocation : MyBatis 환경설정 파일(Configuration.xml) 의 위치를 설정하는 역할

- MyBatis 환경설정 파일인 configuration.xml 을 불러오고 있다

- MyBatis 환경설정 파일인 configuration.xml 은 resources 패키지 아래에 있으므로 classpath: 를 붙여서 불러온다

+ configuration.xml 에는 3가지 내용(DTO alias, DB접속 내용, Mapper 파일 호출) 중 DTO alias 설정하는 코드만 남아있다

 

DB 연결 코드 (root-context.xml 부분 2-3)

		<property name="mapperLocations" value="classpath:sql/*.xml" />

- mapperLocations : Mapper 파일들의 위치를 설정하는 역할

- Mapper 파일들은 resources 폴더 내에 있으므로 classpath: 를 붙이고 그 안에서도 sql 폴더 안에 있으므로 "classpath:sql/" 를 설정

- 그리고 resources/sql 안의 모든 xml 파일을 불러오라는 의미로 "classpath:sql/*xml"

 

 

root-context.xml 부분 3

	<bean id="session" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>

- SqlSessionTemplate 클래스는 SQL 5개 메소드를 제공하는 myBatis 에서 지원하는 구현클래스이다

- 인터페이스 SqlSession 로는 객체를 생성할 수 없으므로 구현 클래스인 SqlSessionTemplate 으로 객체를 생성해야한다

+ SqlSessionTemplate 객체를 생성했으므로 그걸 업캐스팅해서 SqlSession에 주입해도 되고 SqlSessionTemplate 에 주입해도 된다

- 이 코드로 SqlSessionTemplate 객체를 생성하고 있다, 이 객체는 자동으로 DAO 의 @Autowired 가 붙은 SqlSessionTemplate 또는 SqlSession 으로 주입된다

- 이 코드로 이렇게 SqlSessionTemplate 객체를 생성해야만 DAO 클래스 안에서 @Autowired 로 SqlSessionTemplate 또는 SqlSesssion 으로 객체 주입 가능하다

+ 이 코드가 없어도 Service 객체는 Controller 로 , DAO 객체는 Service 로 주입된다, 단 SqlSession 객체를 DAO 에 주입할때는 반드시 이 코드가 있어야함

- Constructor DI 를 하고 있다, 여기가 유일하게 Constructor DI 를 하는 곳이다

- 즉, 생성자의 매개변수로 주입한다, index = "0" 은 생성자 매개변수 중 첫번째 매개변수를 의미함, 그 생성자의 첫번째 매개변수에 위에서 생성된 sqlSessionFactory 객체를 주입함

- 생성자를 통해 SqlSessionTemplate 객체의 프로퍼티를 채우고, 그렇게 생성된 SqlSessionTemplate 객체를 DAO 의 필드로 주입하는 것임

 

+ 이러한 내용을 프로젝트 myBatis1 에서는 Configuration.xml 과 DAO 에서 처리했다

- Configuration.xml 에서 DB 연결하고 Mapper 파일을 불러왔다

- DAO 에서 Configuration.xml 을 읽어오고 SqlSession 객체를 생성했었다


-프로젝트 myBatis1 (MyBatis 에서 DB연동) 의 내용을 잠시 보자

프로젝트 myBatis1 의 Configuration.xml 

- 여기서 DB 연결과 Mapper 파일을 불러왔었다

- 그리고 DAO 에서는 이 Configuration.xml 을 읽어왔다

- 즉 흐름은 아래와 같았다


- 현재 프로젝트 myBatis2 에서 DB 연동하는 걸 보자

- 위와 비교

 

DB 접속 내용

- root-context.xml 에서 SimpleDriverDataSource 객체를 생성할때 필드값으로 들어간다

- 이 SimpleDriverDataSource 객체를 SqlSessionFactoryBean 객체를 생성할때 주입함

 

Mapper 파일을 불러오는 방법 (DAO 에서 Mapper 파일에 접근 가능한 이유)

- SqlSessionFactoryBean 객체에는 Mapper 파일의 위치가 들어간다

- SqlSessionFactoryBean 객체인 sqlSessionFactory 가 SqlSessionTemplate 객체를 생성할때 ref 로 주입되었기 떄문에 DAO 에서 Mapper 파일을 불러와서 id 로 SQL문에 접근 가능한 것

- 현재 프로젝트 myBatis2 에서는 root-context.xml 을 읽어서 SqlSession 객체를 생성한다


파일들 살펴보기 : DeptDaoImpl.java

- DAO 구현 클래스 중 하나를 살펴보자

- DeptDaoImpl.java

package myBatis2.dao;

import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import myBatis2.model.Dept;

@Repository
public class DeptDaoImpl implements DeptDao {
	@Autowired
	private SqlSessionTemplate st;

	public List<Dept> list() {
		return st.selectList("deptns.list");
	}

	public int insert(Dept dept) {
		return st.insert("deptns.insert", dept);
	}

	public Dept select(int deptno) {
		return st.selectOne("deptns.select", deptno);
	}

	public int update(Dept dept) {
		return st.update("deptns.update", dept);
	}

	public int delete(int deptno) {
		return st.delete("deptns.delete", deptno);
	}
}

- @Autowired 어노테이션을 사용해서 SqlSession 객체를 주입하고 있다

- 인터페이스인 SqlSession 을 주입해도 되고, 그걸 상속받는 구현 클래스인 SqlSessionTemplate 를 주입해도 된다

- SqlSession 은 SQL문을 실행시키는 메소드를 제공하는 인터페이스

- 여기선 구현클래스인 SqlSessionTemplate 으로 객체를 주입하고 있다

- 주입되어야만 5개의 메소드(insert(), update(), delete(), selectOne(), selectList() 를 사용 가능함

- root-context.xml 에서 DB 연동을 처리했기 때문에 여기서 @Autowired 로 SqlSession 객체를 주입 가능한 것

- root-context.xml 에서 SqlSessionTemplate 객체를 생성했고 어노테이션 기반 DI 로 여기에 주입했다

 

프로젝트 myBaits1 vs 프로젝트 myBatis2

프로젝트 myBaits1

- 이전 프로젝트 myBatis1은 여기서 MyBatis 환경설정 파일을 읽어와서 SqlSession 객체를 생성했다

- 이전 프로젝트에서는 MyBatis 환경설정 파일을 썼으므므로 @Autowired 로 SqlSession 주입 불가

 

프로젝트 myBatis2

- 현재 프로젝트 myBatis2는 여기서 @Autowired 어노테이션으로 SqlSession 을 주입받았다

- root-context.xml 에서 DB 연동을 처리했기 때문에 여기서 @Autowired 로 SqlSession 객체를 주입 가능한 것

- 그 root-context.xml 에서 객체 생성 및 주입을 했고 시점은 root-context.xml 이 web.xml 에 의해 읽어졌을때


파일들 살펴보기 : DeptServiceImpl.java

- Service 클래스 중 하나를 보자

- DeptServiceImpl.java

package myBatis2.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import myBatis2.dao.DeptDao;
import myBatis2.model.Dept;

@Service
public class DeptServiceImpl implements DeptService {
	@Autowired
	private DeptDao dd;

	public List<Dept> list() {
		return dd.list();
	}

	public int insert(Dept dept) {
		return dd.insert(dept);
	}

	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);
	}
}

- DAO 인터페이스 DeptDao 로 객체를 주입하고 있다

- @Autowired 로 객체를 주입할때 인터페이스로 객체를 주입해도되고, 구현클래스로 주입해도 된다

+ 단 어노테이션을 붙일때는 구현클래스 위에만 붙인다


파일들 살펴보기 : EmpController.java

- Controller 클래스 중 하나를 보자

- EmpController.java

package myBatis2.controller;

import java.sql.Date;
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 myBatis2.model.Dept;
import myBatis2.model.Emp;
import myBatis2.service.DeptService;
import myBatis2.service.EmpService;

@Controller
public class EmpController {
	@Autowired
	private EmpService es;
	@Autowired
	private DeptService ds;

	@RequestMapping("empList.do")
	public String empList(int deptno, Model model) {
		Dept dept = ds.select(deptno);
		List<Emp> list = es.list(deptno);
		model.addAttribute("dept", dept);
		model.addAttribute("list", list);
		return "emp/empList";
	}

	@RequestMapping("empInsertForm.do")
	public String empInsertForm(Model model) {
		List<Dept> deptList = ds.list();
		List<Emp> empList = es.empList();
		model.addAttribute("deptList", deptList);
		model.addAttribute("empList", empList);
		return "emp/empInsertForm";
	}

	@RequestMapping("dupCheck.do")
	public String dupCheck(int empno, Model model) {
		System.out.println("empno:"+empno);
		Emp emp = es.select(empno);
		String msg = "";
		if (emp != null)
			msg = "이미 있는 데이터입니다";
		else
			msg = "사용 가능한 사번 입니다";
		model.addAttribute("msg", msg);
		return "emp/dupCheck";
	}

	@RequestMapping("empInsert.do")
//	public String empInsert(Emp emp, String hiredate1, Model model) {
	public String empInsert(Emp emp, Model model) {
//		emp.setHiredate(Date.valueOf(hiredate1)); // String -> Date 형변환
		int result = es.insert(emp);
		model.addAttribute("result", result);
		model.addAttribute("deptno", emp.getDeptno());
		return "emp/empInsert";
	}

	@RequestMapping("empView.do")
	public String empView(int empno, Model model) {
		Emp emp = es.select(empno);
		model.addAttribute("emp", emp);
		return "emp/empView";
	}

	@RequestMapping("empDelete.do")
	public String empDelete(int empno, Model model) {
		Emp emp = es.select(empno);
		int result = es.delete(empno);
		model.addAttribute("result", result);
		model.addAttribute("deptno", emp.getDeptno());
		return "emp/empDelete";
	}

	@RequestMapping("empUpdateForm.do")
	public String empUpdateForm(int empno, Model model) {
		Emp emp = es.select(empno);
		List<Dept> deptList = ds.list();
		model.addAttribute("emp", emp);
		model.addAttribute("deptList", deptList);
		return "emp/empUpdateForm";
	}

	@RequestMapping("empUpdate.do")
	public String empUpdate(Emp emp, Model model) {
		int result = es.update(emp);
		model.addAttribute("deptno", emp.getDeptno());
		model.addAttribute("result", result);
		return "emp/empUpdate";
	}

	@RequestMapping("empAllList.do")
	public String empAllList(Model model) {
		List<Emp> list = es.empAllList();
		model.addAttribute("list", list);
		return "emp/empAllList";
	}
}

- Service 객체를 2개 주입받고 있음

- @Autowired 로 주입 2개 이상도 가능하다


흐름 보기

- @Autowired 로 객체를 주입할때 인터페이스로 객체를 주입해도되고, 구현클래스로 주입해도 된다

- 여기서는 다 인터페이스로 주입하고 있음 ex) BoardService, BoardDao, SqlSession

 

필요한 bean 객체를 어노테이션 기반으로 주입하기 위한 조건

1. base-package 에 적힌 해당 패키지의 모든 클래스를 읽어오는 코드가 있어야함

2. 어노테이션 4개 중 하나 붙어있어야함

3. 만들고자 하는 객체 위에 @Autowired 어노테이션을 붙여야함


프로젝트 실행

- index.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>
<script type="text/javascript">
	location.href="deptList.do";
</script>
</body>
</html>

 

전체 직원 목록

- 사번, 이름, 업무, 급여, 부서코드는 Emp 테이블에 있는 내용이다

- 부서모드, 부서명, 근무지는 Dept 테이블에 있는 내용이다

- 검색할때 조인해서 검색해서 그걸 뿌려준 것이다

 

부서 목록

- 현재는 삭제가 되지 않음, Emp 테이블에서 참조하는 Foreign Key 가 없을때는 삭제됨

- 그 외, 부서 입력(추가), 사원 입력(추가) 도 가능하다

 

조인(join) 과 DTO

- 조인한 결과를 DTO 객체를 생성하여 저장해야한다

- 현재 DTO 클래스는 Dept 와 Emp 가 있다

+ 테이블 개수가 늘어나면 Mapper 파일, Controller , Service , DAO , DTO 도 1개씩 늘어나는게 일반적

- Dept 테이블에 대해 매핑을 잡을때를 위해 Dept DTO의 필드명은 Dept 테이블의 컬럼명과 일치시켰다

- Emp 테이블에는 8 개의 컬럼이 있지만 Emp DTO 의 필드는 8개보다 많다, 조인을 했을때 조인한 결과를 이 DTO 객체에 매핑을 통해서 저장할 것

- deptno, dname, loc 는 Emp 테이블에는 없는 컬럼이다, 조인을 했을때 그 결과를 저장하기 위해 만든 컬럼이다

 

+ Configuration.xml 에서 Dept DTO 의 alias 값을 "dept", Emp DTO 의 alias 값을 "emp" 로 설정했다

 

- 이제 실행되는 순서대로 따라가보자



프로젝트 myBatis2 : 부서 목록

+ 전체 코드는 나중에 한번에 올림

- index.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>
<script type="text/javascript">
	location.href="deptList.do";
</script>
</body>
</html>

- "deptList.do" 로 요청한다, DispatcherServlet -> Controller 로 이동

- 2개의 Controller 클래스가 있지만 @RequestMapping 이 맞는 곳으로 찾아가므로 신경쓰지 않아도 된다

 

Controller 클래스 DeptController.java 에서 "deptList.do" 요청 부분만 + @Autowired 부분

	@Autowired
	private DeptService ds;
	
	// 부서 목록
	@RequestMapping("deptList.do")
	public String deptList(Model model) {
		List<Dept> list = ds.list();
		model.addAttribute("list", list);
		return "deptList";
	}

- @Autowired 를 통해서 DeptServiceImpl 객체를 인터페이스인 DeptService ds 에 주입

+ 엄밀히 말하면 구현클래스가 주입되는 것이지만, 어차피 업캐스팅 하므로 그냥 DeptService 객체가 됨

+ 그래서 하나의 인터페이스에 구현클래스를 2개를 만들면 안된다

- 위에서 주입한 Service 객체 ds 를 사용해서 Service 의 list() 를 호출

- 2개이상의 데이터를 검색해서 List 로 돌아올 것이므로 리턴을 List 로 받는다

<돌아온 후>

- Dept DTO 객체를 저장하고 있는 리스트를 반환받았고 그걸 list 에 저장함

- 그 list 를 Model 객체에 저장하고, /WEB-INF/views/deptList.jsp 로 간다

+ Model 에 리스트가 저장되었으므로 View 페이지에서 출력할때는 EL 이 forEach문의 items 에 들어감

 

Service 클래스 DeptServiceImpl.java 에서 list() 메소드 부분만 + @Autowired 부분

	@Autowired
	private DeptDao dd;

	public List<Dept> list() {
		return dd.list();
	}

- @Autowired 를 통해서 DeptDaoImpl 객체를 인터페이스인 DeptDao dd 에 주입

+ 엄밀히 말하면 구현클래스가 주입되는 것이지만, 어차피 업캐스팅 하므로 그냥 DeptDao 객체가 됨

+ 그래서 하나의 인터페이스에 구현클래스를 2개를 만들면 안된다

- 돌려받은 값(리스트)을 그대로 리턴

 

DAO 클래스 DeptDaoImpl.java 에서 list() 메소드 부분만 + @Autowired 부분

	@Autowired
	private SqlSessionTemplate st;

	public List<Dept> list() {
		return st.selectList("deptns.list");
	}

- @Autowired 를 통해서 SqlSessionTemplate 객체를 SqlSessionTemplate st 에 주입

- 2개 이상의 데이터를 검색할 것이므로 selectList() 를 호출

- 이 selectList() 메소드는 위에서 SqlSessionTemplate 객체를 구해왔으므로 사용 가능한 것

- 현재는 Mapper 파일이 2개이므로 id 중복을 피하기 위해 namespace 를 사용한다

+ Mapper 파일 중 하나인 Dept.xml 의 namespace 는 "deptns"

- 그래서 selectList("deptns.list") 는 Dept.xml 파일에서 id 가 list 인 태그의 select SQL문을 불러오는 것을 의미

- 돌려받은 값(리스트)을 그대로 리턴

 

Mapper 파일 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>

- Dept 테이블의 모든 데이터를 검색

- order by 로 부서번호 기준 오름차순 정렬되어서 돌려준다 

- 현재는 Dept DTO 클래스 프로퍼티명과 검색되는 컬럼명인 Dept 테이블의 컬럼명이 일치하므로 resultMap 을 사용하지 않아도 된다, 사용은 하고 있음

- resultMap 에서 type 은 "dept" 이다, 즉 Dept DTO 타입,

+ 리스트로 반환할때도 type 이나 returyType 에는 DTO 를 써야함

 

- 이제 다시 DAO 로 돌아가야한다, 돌아갈때의 설명은 하늘색 하이라이트로 표시

 

- 돌아갔다가 이제 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" align="center">
		<h2 class="text-primary">부서 목록</h2>
		<table class="table table-hover">
			<tr>
				<th>부서코드</th>
				<th>부서명</th>
				<th>근무지</th>
				<th></th>
				<th></th>
			</tr>
			<c:if test="${empty list}">
				<tr>
					<td colspan="5">데이터가 없습니다</td>
				</tr>
			</c:if>
			<c:if test="${not empty list }">
				<c:forEach var="dept" items="${list}">
					<tr>
						<td>${dept.deptno}</td>
						<td><a href="empList.do?deptno=${dept.deptno}"
							   class="btn btn-info">${dept.dname}</a></td>
						<td>${dept.loc }</td>
						<td><a href="deptUpdateForm.do?deptno=${dept.deptno }"
							   class="btn btn-success">수정</a></td>
						<td><a href="deptDelete.do?deptno=${dept.deptno }"
							   class="btn btn-danger">삭제</a></td>
				</c:forEach>
			</c:if>
		</table>
		<a href="deptInsertForm.do" class="btn btn-info">부서입력</a> 
		<a href="empAllList.do" class="btn btn-default">전체 직원목록</a>
	</div>
</body>
</html>

 

- list 가 비어있는 경우는 ${empty list} 로 확인하고, "데이터가 없습니다" 뿌려주기

- list 가 비어있지 않는 경우는 Model 에 저장되어있는 걸 EL 로 가져와서 부서 목록을 출력해준다

- forEach 문의 itmes 에 ${list} 가 들어가게 되고, 반복문으로 하나씩 Dept DTO 객체를 가져와서 뿌려준다

 

- '전체 직원 목록' 클릭시 "empAllList.do" 로 요청한다


프로젝트 myBatis2 : 전체 직원 목록

+ 전체 코드는 나중에 한번에 올림

- '전체 직원 목록' 클릭시

- Dept, Emp 테이블을 등가조인해서 결과를 한번에 출력하고 있다

- '전체 직원 목록' 클릭시 "empAllList.do" 로 요청한다

 

EmpController.java 에서 @Autowired 부분만

	@Autowired
	private EmpService es;
	@Autowired
	private DeptService ds;

- 주입할 객체가 여러개인 경우엔 @Autowired 를 개별적으로 써야한다, 1번만 써선 안된다

- DeptService 객체를 주입하고 있고, EmpService 객체도 주입하고 있다

+ 엄밀히 말하면 구현클래스가 주입되는 것이지만, 어차피 업캐스팅 하므로 그냥 DeptService 객체, EmpService 객체가 됨

+ 그래서 하나의 인터페이스에 구현클래스를 2개를 만들면 안된다

- 2개의 Service 객체를 주입받음

 

Controller 클래스 EmpController.java 에서 "empAllList.do" 요청 부분만

	// 전체 직원 목록
	@RequestMapping("empAllList.do")
	public String empAllList(Model model) {
		List<Emp> list = es.empAllList();
		model.addAttribute("list", list);
		return "emp/empAllList";
	}

- 위에서 주입받은 EmpService 객체 es 를 사용해서 Service 클래스 메소드인 empAllList() 호출

- 2개 이상의 데이터를 검색하므로 리스트로 값이 돌아올 것이므로 List 로 받음

<돌아올 때>

- 돌아온 List 에는 Emp DAO 객체들이 들어있다

- prefix 로 잡혀있는 경로까지는 생략하고, 그 하위의 경로는 적어야한다

- /WEB-INF/views 는 생략하고, 하위의 emp 폴더는 적어야함, 그래서 "emp/empAllList" 이다

- /WEB-INF/views/emp/empAllList.jsp 파일로 이동

 

Service 클래스 EmpServiceImpl.java 에서 empAllList() 메소드 부분만 + @Autowired 부분

	@Autowired
	private EmpDao ed;
	public List<Emp> empAllList() {
		return ed.empAllList();
	}

- 위에서 @Autowired 로 주입받은 EmpDao 객체 ed 를 사용해서 DAO 클래스의 메소드인 empAllList() 호출

 

DAO 클래스 EmpDaoImpl.java 에서 empAllList() 메소드 부분만 + @Autowired 부분

	@Autowired
	private SqlSessionTemplate sst;
	public List<Emp> empAllList() {
		return sst.selectList("empAllList");
	}

- 위에서 @Autowired 로 주입받은 SqlSEssionTemplate 객체 sst 를 사용해서 지원메소드인 selectList() 사용

- 2개 이상의 데이터를 검색하므로 selectList() 메소드 사용

- 반환되는 List 에 들어가는 것은 Emp DTO 객체만 들어간다는 의미로 제네릭 <Emp> 사용

 

Mapper 파일 Emp.xml 에서 id 가 "empAllList" 인 태그 부분만 + resultMap

	<!-- Use type aliases to avoid typing the full classname every time. -->
	<resultMap id="empResult"    	type="emp">
		<result property="empno" 	column="empno" />
		<result property="ename"  	column="ename" />
		<result property="job"		column="job" />
		<result property="mgr" 		column="mgr" />
		<result property="hiredate" column="hiredate" />
		<result property="sal"	  	column="sal" />
		<result property="comm"	   	column="comm" />
		<result property="deptno"   column="deptno" />
		<result property="dname"	column="dname" />
		<result property="loc"   	column="loc" />
	</resultMap>

+ 현재는 모든 10개의 테이블과 검색결과의 컬럼명이 일치하므로 일일히 resultMap 을 사용할 필요 없다, 여기선 resultMap 을 사용했음

- Emp DTO 가 저장된 리스트를 리턴하므로, type 을 "emp" 로 설정함

	<select id="empAllList" resultMap="empResult">
		select e.*,dname,loc from emp e, dept d 
			where e.deptno=d.deptno order by empno
	</select>

- from 절에서 emp 테이블의 별칭을 e, dept 테이블의 별칭을 d 로 설정

- 등가조인(과거부터 사용하던 방식, 안시 방식 등이 있다) 을 사용하고 있음

- e 의 모든 컬럼과 dname, loc 를 검색

+ 공통 컬럼이 아닌 경우는 dname, loc 처럼 그냥 쓰면 된다 

- 등가 조인을 위해 e.deptno 와 d.deptno 가 같은 경우만 가져옴

+ 두 테이블 사이 공통적인 컬럼이 있을때만 가능한게 등가조인

- 사원번호 순서대로 정렬, 사원번호가 빠른(작은) 순대로 나온다

- Emp DTO 는 10개의 정보를 저장하므로 검색결과를 모두 저장할 수 있으므로 resultMap 을 쓰지 않고 resultType 에 "emp" 를 써도 된다

 

View 페이지 empAllList.jsp

- emp/empAllList.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">
		<table class="table table-striped">
			<tr>
				<td>사번</td>
				<td>이름</td>
				<td>업무</td>
				<td>급여</td>
				<td>부서코드</td>
				<td>부서명</td>
				<td>근무지</td>
			</tr>
			<c:forEach var="emp" items="${list }">
				<tr>
					<td>${emp.empno }</td>
					<td>${emp.ename }</td>
					<td>${emp.job }</td>
					<td>${emp.sal }</td>
					<td>${emp.deptno }</td>
					<td>${emp.dname }</td>
					<td>${emp.loc }</td>
				</tr>
			</c:forEach>
		</table>
		<a href="deptList.do" class="btn btn-default">부서목록</a>
	</div>
</body>
</html>

- 검색된 결과가 Model 을 통해 넘어왔다, 이 값은 Emp DTO 객체를 저장하는 리스트이다

- 리스트가 Model 객체에 저장되었으므로 forEach 태그의 items 값으로 들어간다

- 각각의 Emp DTO 객체는 10개의 값을 저장하고 있음, 그 값들 중 일부를 출력한다

- 또한 forEach 를 통해 하나씩 가져온 각각의 DTO 객체의 필드에 접근할땐 ${emp.필드명} 를 사용함

+ ${emp.empno} 는 내부적으로는 ${emp.getEmpno()} 이다

- 사번, 이름, 업무, 급여, 부서코드는 Emp 테이블, 부서코드, 부서명, 근무지는 Dept 테이블의 데이터

 

- 아래의 '부서목록' 클릭시 "deptlist.do" 로 요청한다  (위에서 설명했다)

- index.jsp 에서 요청했던 내용과 같음

 

- 사원 14명에 대한 정보를 출력한다


프로젝트 myBatis2 : 부서 입력폼

+ 전체 코드는 나중에 한번에 올림

- 입력폼으로 먼저 이동한 후, 거기서 입력을 해야한다, 입력 폼만 먼저 보자

 

- deptList.jsp (부서 목록) 에서 '부서입력' 클릭시 "deptInsertForm.do" 로 요청한다

		<a href="deptInsertForm.do" class="btn btn-info">부서입력</a>

- 부서 입력 폼으로 가게 됨

 

Controller 클래스 DeptController.java 에서 "deptInsertForm.do" 요청 부분만

	// 부서 등록폼
	@RequestMapping("deptInsertForm.do")
	public String deptInsertForm() {
		return "deptInsertForm";
	}

- 입력폼으로 가는데는 DB 연동이 필요하지 않음, Service 클래스가 아닌 바로 View 페이지로 간다

- 바로 View 파일인 /WEB-INF/views/deptInsertForm.jsp 로 이동한다

 

View 페이지 deptInsertForm.jsp

- deptInsertForm.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="deptInsert.do" method="post">
			<table class="table table-hover">
				<tr>
					<td>부서코드</td>
					<td><input type="number" maxlength="2" name="deptno"
						required="required" autofocus="autofocus"></td>
				</tr>
				<tr>
					<td>부서명</td>
					<td><input type="text" name="dname" required="required"></td>
				</tr>
				<tr>
					<td>근무지</td>
					<td><input type="text" name="loc" required="required"></td>
				</tr>
				<tr>
					<td colspan="2"><input type="submit" value="확인"></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

- 부서 입력 폼에서 입력을 한 후 "확인" 을 누르면 "deptInsert.do" 로 요청한다

- "deptInsert.do" 는 실제 입력(insert) 을 위한 요청임

- 각 입력양식의 name 값은 Dept DTO 의 컬럼명과 일치시켰다, "deptInsert.do" 요청을 받는 곳에서 Setter 메소드 직접 사용 없이 바로 DTO 객체에 넘어온 값들이 저장되도록 하기 위해서이다

 

- 부서 입력 폼으로 가보자

- 부서 입력 폼에서 입력을 한 후 "확인" 을 누르면 "deptInsert.do" 로 요청한다

 


프로젝트 myBatis2 : 부서 입력 (부서 추가 등록)

- 부서 입력 폼에서 입력을 한 후 "확인" 을 누르면 "deptInsert.do" 로 요청한다

 

Controller 클래스 DeptController.java 에서 "deptInsert.do" 요청 부분만

	// 부서 등록
	@RequestMapping("deptInsert.do")
	public String deptInsert(@ModelAttribute Dept dept, 
			                 Model model) {
		Dept dt = ds.select(dept.getDeptno()); // 부서번호 중복 검사
		if (dt == null) { // 부서번호가 중복이 아닌 경우
			int result = ds.insert(dept);
			model.addAttribute("result", result);
		} else { // 부서번호 중복
			model.addAttribute("msg", "이미 있는 데이터입니다");
			model.addAttribute("result", -1);
		}
		return "deptInsert";
	}

- 앞의 부서 입력폼에서 넘어온 3가지값을 @ModelAttribute 로 한번에 받아서 Dept DTO 객체 dept 에 저장함

- 부서번호가 중복되지 않을때만 가입을 시키기 위해 부서번호가 중복인지를 중복검사를 검색해야함

<부서번호 중복 검사>

- 위에서 주입된 Dept Service 객체 ds를 사용해서 Service 클래스의 select() 를 호출, 호출하며 부서번호를 넘겨준다 *아래에

<select() 에서 돌아왔을 때>

- 부서 번호로 DB 에 검색했을때 돌아오는 값이 없어야 부서번호가 중복이 아닌 경우이다 그래서 dt == null 을 확인

<부서 입력>

- dt == null 인 경우, 즉 중복이 아닌 경우에는 위에서 주입된 Dept Service 객체 ds를 사용해서 Service 클래스의 insert() 를 호출

- insert() 를 호출하며 사용자가 부서 입력 폼에서 입력했던 데이터를 저장한 객체 dept 를 넘겨준다

<insert() 에서 돌아왔을 때>

- dt == null 인 경우, 즉 중복이 아닌 경우 insert()를 수행 후 돌아오면, result 에 1 을 받게 된다, Model 객체에 "result" (1) 을 저장해서 detpInsert.jsp 로 이동

- dt != null 인 경우, 즉 중복인 경우에는 Model 객체에 "msg" 와 "result"(-1) 을 저장해서 deptInsert.jsp 로 이동

- 입력에 성공했는지 확인하기 위해 리턴값을 result 변수로 받는다, 그 값을 Model 객체에 저장해서 View 에 넘김

+ 그럼 deptInsert.jsp 에서는 "result" 값을 확인해서 조건식을 통해 성공과 실패 처리를 한다


<부서번호 중복 검사>

Service 클래스 DeptServiceImpl.java 에서 select() 메소드 부분만

	public Dept select(int deptno) {
		return dd.select(deptno);
	}

 

 

DAO 클래스 DeptDaoImpl.java 에서 select() 메소드 부분만

	public Dept select(int deptno) {
		return st.selectOne("deptns.select", deptno);
	}

- 매개변수로 받은 부서번호 deptno 를 넘겨준다

 

Mapper 파일 Dept.xml 에서 id 가 "select" 인 태그 부분만

	<select id="select" parameterType="int" resultType="dept">
		select * from dept where deptno=#{deptno}
	</select>

- 전달받은 부서번호를 통해 (#{deptno}) 데이터를 검색한다

- 이때 검색되는 데이터가 있으면 부서번호가 중복되는 것임

- 리턴 자료형은 Dept DTO 객체인 dept


<부서 입력>

Service 클래스 DeptServiceImpl.java 에서 insert() 메소드 부분만

	public int insert(Dept dept) {
		return dd.insert(dept);
	}

- 전달받은 insert 할 Dept DTO 객체 dept 를 DAO의 insert() 를 호출할때 매개변수로 전달한다

 

DAO 클래스 DeptDaoImpl.java 에서 insert() 메소드 부분만

	public int insert(Dept dept) {
		return st.insert("deptns.insert", dept);
	}

- 위에서 주입받은 SqlSessionTemplate 객체 st 를 사용해서 insert() 메소드 사용

- insert 할 데이터를 저장한 객체 dept 를 전달함

 

Mapper 파일 Dept.xml 에서 id 가 "insert" 인 태그 부분만

	<insert id="insert" parameterType="dept">
		insert into dept values(#{deptno},#{dname},#{loc})
	</insert>

- 넘어온 객체 dept 에서 부서코드,부서명,지역 을 가져와서 DB의 dept 테이블에 삽입한다

- 넘어온 값이 객체이므로 그 ${객체의필드명} 으로 값을 가져온다

ex) #{deptno} 는 dept.getDeptno() 와 같은 의미


View 페이지인 deptInsert.jsp

- deptInsert.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>
	<c:if test="${result == -1 }">
		<script type="text/javascript">
			alert("${msg}");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- 부서번호가 중복된 경우 result 는 -1 이 되므로,  msg 를 출력하고 이전 페이지인 입력폼으로 돌아간다

- 부서 등록에 성공한 경우 result 는 1 이 되므로, "입력 성공" 을 출력하고 목록 페이지로 가기 위해 "deptList.do" 요청


- 부서를 새로 등록해보자

- 이때, 부서코드는 Primary Key 이므로 중복되선 안됨, 부서 입력시 10,20,30,40 을 부서코드로 쓰면 안된다

 

- 중복된 부서코드 입력시

 

- 중복되지 않은 부서코드 입력시

 


프로젝트 myBatis2 : 부서 정보 수정폼

- 부서 정보 수정을 위해선 먼저 수정폼으로 가야한다

- 부서 정보 수정폼에서는 부서의 상세 정보를 구해서 뿌려줘야함

- deptList.jsp (목록 페이지) 에서 "수정" 을 클릭해보자

<td><a href="deptUpdateForm.do?deptno=${dept.deptno }"
			class="btn btn-success">수정</a></td>

- "수정" 클릭시 "deptUpdateForm.do" 로 요청한다

- GET 방식으로 해당 부서의 부서코드 deptno 를 가져간다

 

Controller 클래스 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";
	}

- 앞에서 넘어온 네임값과 이름이 같은 변수 deptno 에 앞에서 넘어온 부서코드가 바로 저장됨

- @RequestParam("deptno") 가 int deptno 앞에 생략되어있다

- Service 객체 ds 를 사용해서 Service 클래스의 select() 메소드 호출, 호출 시 부서번호를 전달

<돌아온 후>

- select() 에서 돌려받은 Dept DTO 객체 dept 를 Model 객체에 저장

+ 객체 dept 가 Model 에 저장되었으므로 View 에서 출력할땐 ${dept.필드명} 으로 출력

- /WEB-INF/views/deptUpdateForm.jsp 로 간다

 

Service 클래스 DeptServiceImpl.java 에서 select() 메소드 부분만

	public Dept select(int deptno) {
		return dd.select(deptno);
	}

 

DAO 클래스 DeptDaoImpl.java 에서 select() 메소드 부분만

	public Dept select(int deptno) {
		return st.selectOne("deptns.select", deptno);
	}

- 하나의 부서에 대한 상세정보를 검색해서 가져오므로 selectOne() 메소드 사용

 

Mapper 파일 Dept.xml 에서 id 가 "select" 인 태그 부분만

	<select id="select" parameterType="int" resultType="dept">
		select * from dept where deptno=#{deptno}
	</select>

- 검색되는 결과는 1개이다

- 결과는 Dept DTO 형으로 돌려준다, 그래서 resultType 에 "dept" 를 썼다

 

View 페이지 deptUpdateForm.jsp

- 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-hover">
				<tr>
					<td>부서코드</td>
					<td>${dept.deptno}</td>
				</tr>
				<tr>
					<td>부서명</td>
					<td><input type="text" name="dname" required="required"
							   value="${dept.dname}"></td>
				</tr>
				<tr>
					<td>근무지</td>
					<td><input type="text" name="loc" required="required"
							   value="${dept.loc}"></td>
				</tr>
				<tr>
					<td colspan="2"><input type="submit" value="확인"></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

- 객체 dept 가 Model 에 저장되었으므로 View 에서는 ${dept.필드명} 으로 가져온다

- 부서 정보 수정폼에 수정할 정보를 입력하고 "확인" 을 누르면 "deptUpdate.do" 로 요청한다

- 부서코드는 수정할 수 없다, 수정할 수 있는 정보는 부서명과 근무지이다.

+ 현재 부서코드는 입력양식이 아니라 출력만 하고 있으므로 넘어가지 않음, 그래서 hidden 객체 사용

- 부서코드 또한 수정시에 전달해야하므로 hiddden 객체를 통해 "deptUpdate.do" 로 전달

 


프로젝트 myBatis2 : 부서 정보 수정

- 부서 정보 수정폼 deptUpdateForm.jsp 에 수정할 정보를 입력하고 "확인" 을 누르면 "deptUpdate.do" 로 요청한다

 

Controller 클래스 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";
	}

- 앞에서 폼을 통해 넘어온 입력값들을 한번에 @ModelAttribuate (생략) 을 통해 Dept DTO 객체 dept 에 세팅한다

- Service 클래스의 객체 ds 를 사용해서 Service 의 update() 메소드 호출, 호출 시 수정할 정보를 저장한 객체 dept 를 전달

<돌아온 후>

- 수정이 성공적으로 완료되면 update() 에서 1 을 받게 되고, 그걸 result 변수에 저장

- 그 result 변수를 Model 에 저장하고 deptUpdate.jsp 로 이동

 

Service 클래스 DeptServiceImpl.java 에서 update() 메소드 부분만

	public int update(Dept dept) {
		return dd.update(dept);
	}

 

DAO 클래스 DeptDaoImpl.java 에서 update() 메소드 부분만

 

	public int update(Dept dept) {
		return st.update("deptns.update", dept);
	}

- update() 수행 후 DB 에서 수정을 성공하면, update() 는 1 을 자동으로 리턴한다 (수정한 데이터 개수)

- Mapper 파일의 SQL문을 가져오는 것이고, 실제 SQL문의 실행은 여기 DAO의 update() 문이 실행한다

- 그 1 을 다시 Service 로 리턴함

 

Mapper 파일 Dept.xml 에서 id 가 "update" 인 태그 부분만

	<update id="update" parameterType="dept">
		update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
	</update>

- 수정할 정보를 저장한 Dept DTO 객체가 전달된다

- #{dname} 은 dept.getDname() 과 같은 의미

 

View 페이지 deptUpdate.jsp

- 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" 값을 사용해서 성공 / 실패 처리를 한다


 

 

+ 삭제로 넘어가기전 상세 페이지 잠시 보기

- 부서 목록 페이지에서 부서명을 클릭하면 상세 페이지에서 해당 부서 소속 사원들을 보여준다

- 상세페이지는 나중에 구현할 것

- 즉 20번 부서를 참조하는 사원 테이블의 데이터들이 있으므로 20번 부서가 삭제되지 않음

- 20번 부서 삭제 시도시


프로젝트 myBatis2 : 부서 삭제

- deptList.jsp (목록 페이지) 에서 부서 삭제를 해보자

<td><a href="deptDelete.do?deptno=${dept.deptno }"
			class="btn btn-danger">삭제</a></td>

- "삭제" 를 누르면 "deptDelete.do" 로 요청한다, 그때 부서 코드인 deptno 를 GET 방식으로 전달한다

 

삭제시 주의

- Emp 테이블과 Dept 테이블은 참조관계가 있다

- 현재 내가 입력한 50번 부서는 참조하는 Foreign Key 가 없으므로 부서 삭제가 된다

- 10,20,30 번 부서는 참조하는 Foreign Key 가 있으므로 삭제되지 않는다

- 10,20,30 번 부서는 사원 테이블 Emp 에서 참조한다

+ 40번 부서는 원래 참조하는 사원이 없으므로 삭제 된다

 

- 20 번 부서 삭제 시도시

 

Controller 클래스 DeptController.java 에서 "deptDelete.do" 요청 부분만

	// 부서 삭제
	@RequestMapping("deptDelete.do")
	public String deptDelete(int deptno, Model model) {
		int result = ds.delete(deptno);
		model.addAttribute("result", result);
		return "deptDelete";
	}

- 앞에서 넘어온 네임값과 이름이 같은 변수 deptno 에 앞에서 넘어온 부서코드가 바로 저장됨

- @RequestParam("deptno") 가 int deptno 앞에 생략되어있다

 

<돌아온 후>

- 삭제가 성공적으로 완료되었다면 delete() 메소드는 1 을 리턴함, 그 값을 result 변수에 저장

- result 변수를 Model 객체에 저장하고 deptDelete.jsp 로 이동

 

Service 클래스 DeptServiceImpl.java 에서 delete() 메소드 부분만

	public int delete(int deptno) {
		return dd.delete(deptno);
	}

 

DAO 클래스 DeptDaoImpl.java 에서 delete() 메소드 부분만

	public int delete(int deptno) {
		return st.delete("deptns.delete", deptno);
	}

- delete() 수행 후 DB 에서 수정을 성공하면, delete() 는 1 을 자동으로 리턴한다 삭제한 데이터 개수)

- Mapper 파일의 SQL문을 가져오는 것이고, 실제 SQL문의 실행은 여기 DAO의 delete() 문이 실행한다

- 그 1 을 다시 Service 로 리턴함

 

Mapper 파일 Dept.xml 에서 id 가 "delete" 인 태그 부분만

	<delete id="delete" parameterType="int">
		delete from dept where deptno=#{deptno}
	</delete>

- 넘어온 값은 부서코드이다, #{deptno} 로 사용함

- 해당 부서코드를 가진 부서를 dept 테이블에서 삭제

 

View 페이지 deptDelete.jsp

- 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 <= 0 }">
		<script type="text/javascript">
			alert("삭제 실패");
			location.href = "deptList.do";
		</script>
	</c:if>
</body>
</html>

- "result" 를 통해 삭제 성공 / 실패 처리를 한다

- 삭제 성공시 result 가 1 이므로, "삭제 성공" 을 출력하고, 목록 페이지로 가기 위해 deptList.do 로 요청

- 삭제 실패시 result 가 <=0 이므로 "삭제 실패" 를 출력하고, 목록 페이지로 가기 위해 deptList.do 로 요청

+ 목록 페이지에서 "삭제" 를 눌렀으므로 삭제 실패 후 돌아가는 것도 목록 페이지로 했다

 

- 참조하는 직원이 없는 50 번 부서를 삭제해보자

- 참조하는 직원(자식) 이 없으므로 지워짐

+ 삭제 후 다시 50번 부서를 등록하자(실습 위해)


 

문제

삭제시 주의 (중복)

- Emp 테이블과 Dept 테이블은 참조관계가 있다

- 현재 내가 입력한 50번 부서는 참조하는 Foreign Key 가 없으므로 부서 삭제가 된다

- 10,20,30 번 부서는 참조하는 Foreign Key 가 있으므로 삭제되지 않는다

- 10,20,30 번 부서는 사원 테이블 Emp 에서 참조한다

+ 40번 부서는 원래 참조하는 사원이 없으므로 삭제 된다

 

- 20 번 부서 삭제 시도시

 

문제 해결 방법

참조하는 자식이 있는 부모도 무조건 삭제하려면?

- 테이블 생성 또는 수정(Alert) 시에 On Delete Cascade 를 써야 한다

- 그럼 부서를 삭제할때 그 부서 소속 직원들 또한 연쇄적으로 삭제된다.



프로젝트 MyBatis2 : 부서 상세 페이지 (해당 부서의 직원들 목록)

- deptList.jsp 에서 부서명을 출력하는 코드에 링크가 걸려있다

<td><a href="empList.do?deptno=${dept.deptno}"
		 class="btn btn-info">${dept.dname}</a></td>

- 부서명 을 클릭시 "empList.do" 로 요청하고, 이때 그 부서 소속 직원을 구해와야 하므로 부서코드 deptno 를 가져간다

ex) 20 번 부서 부서명 클릭시 20 번 부서에 속하는 직원들을 select SQL문으로 구해와야한다

- 부서코드 deptno 를 통해 Emp 테이블에서 검색을 해야한다

 

Controller 클래스 EmpController.java 에서 "empList.do" 요청 부분만 + @Autowired 부분

	@Autowired
	private EmpService es;
	@Autowired
	private DeptService ds;

	// 부서 상세 페이지
	@RequestMapping("empList.do")
	public String empList(int deptno, Model model) {
		Dept dept = ds.select(deptno);
		List<Emp> list = es.list(deptno);
		model.addAttribute("dept", dept);
		model.addAttribute("list", list);
		return "emp/empList";
	}

- 앞에서 넘어온 네임값과 이름이 같은 변수 deptno 에 앞에서 넘어온 부서코드가 바로 저장됨

- @RequestParam("deptno") 가 int deptno 앞에 생략되어있다

- 상세 페이지에서 2가지 다른 정보를 DB 에서 구해와야하므로, 2개의 Service 객체를 @Autowired 로 구해옴

- Dept 테이블과 연동 위해 DeptService 객체, Emp 테이블과 연동 위해 EmpService 객체 구함

 

상세 페이지에서 2가지 정보를 구해야한다

1. Dept 테이블에서 부서 상세 정보를 구하기

- 아래에 표시한 부분(부서명) 을 뿌려주기 위해 부서 상세 정보도 구해야함

2. Emp 테이블에서 각 부서 소속 직원들 목록 구하기

<돌아온 후>

 

- 돌려 받은 부서 상세 정보를 저장한 Dept 객체 dept, 검색한 사원 정보들을 저장한 List 를 모두 Model 객체에 저장

- 그리고 /WEB-INF/views/emp/empList.jsp 로 이동


1. Dept 테이블에서 부서 상세 정보를 구하기

- select 는 많이 했으므로 코드 생략

 

2. Emp 테이블에서 각 부서 소속 직원들 목록 구하기

 

Service 클래스 EmpServiceImpl.java 에서 list() 메소드 부분만

	public List<Emp> list(int deptno) {
		return ed.list(deptno);
	}

 

DAO 클래스 EmpDaoImpl.java 에서 list() 메소드 부분만

	public List<Emp> list(int deptno) {
		return sst.selectList("empns.list", deptno);
	}

- Emp 테이블에서 특정 부서번호의 직원을 구할 것이므로 여러 데이터를 검색하는 것임, selectList() 를 쓴다

 

Mapper 파일 Emp.xml 에서 id 가 "list" 인 태그 부분만 + resultMap 

	<!-- Use type aliases to avoid typing the full classname every time. -->
	<resultMap id="empResult"    	type="emp">
		<result property="empno" 	column="empno" />
		<result property="ename"  	column="ename" />
		<result property="job"		column="job" />
		<result property="mgr" 		column="mgr" />
		<result property="hiredate" column="hiredate" />
		<result property="sal"	  	column="sal" />
		<result property="comm"	   	column="comm" />
		<result property="deptno"   column="deptno" />
		<result property="dname"	column="dname" />
		<result property="loc"   	column="loc" />
	</resultMap>
	<select id="list" parameterType="int" resultMap="empResult">
		select * from emp where deptno=#{deptno} order by empno
	</select>

- 현재는 꼭 resultMap 을 쓰지 않아도 된다

- Emp 테이블에서 특정 deptno 를 가진 사원들의 데이터를 모두 검색한다

- order by empno 에 의해 사원번호를 기준으로 오름차순 정렬된다

 

View 페이지 empList.jsp

- emp/empList.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>
<script type="text/javascript">
	$(function() {
		$('#list').load('deptList.do');
	});
</script>
</head>
<body>
	<div class="container" align="center">
		<h2 class="text-primary">${dept.dname}직원목록</h2>
		<table class="table table-striped">
			<tr>
				<td>사번</td>
				<td>이름</td>
				<td>업무</td>
				<td>급여</td>
				<td>부서코드</td>
			</tr>
			<tr>
				<c:if test="${empty list }">
					<tr>
						<td colspan="5">직원이 없는 부서입니다</td>
					</tr>
				</c:if>
				<c:if test="${not empty list }">
					<c:forEach var="emp" items="${list }">
						<tr>
							<td>${emp.empno }</td>
							<td><a href="empView.do?empno=${emp.empno}"
								   class="btn btn-info">${emp.ename}</a></td>
							<td>${emp.job}</td>
							<td>${emp.sal}</td>
							<td>${emp.deptno}</td>
						</tr>
					</c:forEach>
				</c:if>
		</table>
		<a href="deptList.do" class="btn btn-success">부서목록</a> 
		<a href="empInsertForm.do" class="btn btn-success">직원 입력</a>
		<div id="list"></div>
	</div>
</body>
</html>

- Model 객체에 저장된 해당 부서의 상세 정보 dept 를 사용해서 ${dept.dname} 으로 상단에 부서명을 출력

- Model 객체에 저장된 해당 부서 소속 사원 목록인 list 를 사용해서 ${list} 를 forEach 의 items 에 써서 사원 데이터를 하나씩 가져온다

- forEach의 emp 변수를 통해 하나씩 가져온 사원 데이터에서 ${emp.empno} 등을 활용해 사원 정보를 출력

- 부서 상세페이지에 출력된 사원 정보에서 사원명을 클릭하면 해당 사원의 상세정보를 보여주기 위해 "empView.do" 로 요청한다 

- 부서 상세페이지에서 "직원 입력" 을 누르면 직원을 새로 등록할 수 있는 직원 입력폼으로 가기 위해 "empInsertForm.do" 로 요청한다

 

아래에 부서 목록 뿌리기 (비동기 처리) (empList.jsp 부분)

- 부서 상세 페이지 하단에도 목록 페이지와 같은 부서 목록을 출력하고 있다

<script type="text/javascript">
	$(function() {
		// deptList.do 요청 결과를 div 태그 사이에 출력
		$('#list').load('deptList.do');
	});
</script>

- id 가 "list"인 div 태그를 구해와서 ajax 의 load() 함수를 사용해서 "deptList.do" 요청 결과 전체를 그 div 태그 사이에 출력

+ 이전에도 비슷한 내용을 했다 : https://laker99.tistory.com/146 

(검색어 : 아래에 목록 출력하기 (비동기 처리) (deptView.jsp 부분))

 


- 소속 직원이 없는 부서인 50번 부서 상세 페이지

- 참조하는 직원이 없는 부서이므로 삭제 가능하다

 

- 소속 직원이 있는 20번 부서의 상세 페이지

- 사원을 출력할때 사번이 빠른 순서대로 출력된다(오름차순)


프로젝트 MyBatis2 : 사원 등록폼

- 사원 등록 전 사원 등록폼 부터 가야한다

- 부서 상세페이지에서 "직원 입력" 을 누르면 사원을 새로 등록할 수 있는 사원 입력폼으로 가기 위해 "empInsertForm.do" 로 요청한다

		<a href="empInsertForm.do" class="btn btn-success">직원 입력</a>

- "직원 입력" 클릭시 "empInsertForm.do" 로 요청

 

직원 등록폼에서 DB 와 연동해서 할 일

- 직원 입력폼에서 직원을 등록할때 관리자, 부서코드를 가져와야함

- 사원, 부서는 추가할 수 있으므로 고정된 리스트가 아님, 그래서 DB 와 연동해서 가져와야함

- 즉 사원 목록, 부서 목록을 가져와서 이 직원 입력폼에 뿌려야한다

 

Controller 클래스 EmpController.java 에서 "empInsertForm.do" 요청 부분만

	@RequestMapping("empInsertForm.do")
	public String empInsertForm(Model model) {
		List<Dept> deptList = ds.list();
		List<Emp> empList = es.empList();
		model.addAttribute("deptList", deptList);
		model.addAttribute("empList", empList);
		return "emp/empInsertForm";
	}

- 사원을 새로 등록할때 사번이 중복되지 않은지 중복검사를 해야함

DB 에서 가져와야 할 것 2가지

1. 사원 목록

- 관리자를 선택할때 관리자의 사원명은 바뀔 수 있는 값(사원이 추가될 수도 있다) 이므로, 사원명 리스트를 DB 와 연동해서 가져와야함

2. 부서 목록

- 부서명을 선택할때 부서명은 바뀔 수 있는 값(부서가 추가될 수도 있다)이므로, 부서명 리스트를 DB 와 연동해서 가져와야함

- 사원, 부서는 추가될 수 있으므로 DB와 연동해서 그 리스트를 가져와야함

- 즉 부서명, 사원명 을 가져오기 위해 Dept 테이블, Emp 테이블 둘 다 연동해서 부서 목록, 사원 목록 을 가져와야함

 

- 부서 목록 가져오기는 위에서 했으므로 코드 생략

- 사원 목록 가져오기는 위에서 한것과 유사하므로 코드 생략

- 특정 부서의 사원이 아니라 전체 사원을 구한다는 점만 다르다

<돌아온 후>

- 사원 전체 목록, 부서 전체 목록을 모두 Model 객체에 저장 후 empInsertForm.jsp 로 이동

 

- View 페이지인 사원 등록 폼으로 가자

 

View 페이지 empInsert.jsp

- emp/empInsert.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>
<script type="text/javascript">
	$(function() {
		// 사원번호 중복검사
		$('#dupCheck').click(function() {
			var empno = $('#empno').val();
			if (!empno) {
				alert('사번 입력후에 체크');
				$('#empno').focus();
				return false;
			}// $.post("요청이름","전달될 값","콜백함수");
			$.post('dupCheck.do', 'empno=' + empno, function(msg) {
				alert(msg);
			});
		});
	});
</script>
</head>
<body>
	<div class="container">
		<h2 class="text-primary">직원 등록</h2>
		<form action="empInsert.do" method="post">
			<table class="table table-bordered">
				<tr>
					<td>사번</td>
					<td><input type="number" name="empno" required="required"
							   id="empno" autofocus="autofocus"> 
						<input type="button" value="중복" id="dupCheck"></td>
				</tr>
				<tr>
					<td>이름</td>
					<td><input type="text" name="ename" required="required"></td>
				</tr>
				<tr>
					<td>업무</td>
					<td><input type="text" name="job" required="required"></td>
				</tr>
				<tr>
					<td>관리자</td>
					<td><select name="mgr">
							<c:forEach var="emp" items="${empList }">
								<option value="${emp.empno}">${emp.ename}</option>
							</c:forEach>
					    </select>
					</td>
				</tr>
				<tr>
					<td>입사일</td>
					<td><input type="date" name="hiredate" required="required"></td>
				</tr>
				<tr>
					<td>급여</td>
					<td><input type="number" name="sal" required="required"></td>
				</tr>
				<tr>
					<td>보너스</td>
					<td><input type="number" name="comm" required="required"></td>
				</tr>
				<tr>
					<td>부서코드</td>
					<td><select name="deptno">
							<c:forEach var="dept" items="${deptList }">
								<option value="${dept.deptno}">${dept.dname}</option>
							</c:forEach>
						</select>
					</td>
				</tr>
				<tr>
					<td colspan="2"><input type="submit" value="확인"></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

- 가져온 사원, 부서 목록 forEach 태그의 items 태그에 ${empList} , ${deptList} 에 넣는다

- 그 forEach 태그에서 사원 1명 정보를 담은 객체를 가져와서, select 태그 안의 option 에 필요한 필드를 가져와서 뿌리기

- 그 forEach 태그에서 부서 1개 정보를 담은 객체를 가져와서, select 태그 안의 option 에 필요한 필드를 가져와서 뿌리기

<관리자>

- 가져온 관리자 목록에서 관리자를 1명씩 가져와서 그 관리자의 사원명, 사원번호를 꺼낸다

- 관리자를 선택할때, 사원명이 출력되지만 value 는 관리자(상사) 의 사원번호인 empno 이다

- 즉 실제 DB 에 저장되는 값은 관리자의 사원명이 아닌 관리자의 사원번호이다!

<입사일>

- input type 을 "date" 로만 해도 캘린더를 선택할 수 있는 양식이 나타남

<부서>

- 부서를 선택할때, 부서명이 출력되지만 value 는 부서의 부서번호은 deptno 이다

- 즉 실제 DB 에 저장되는 값은 부서의 부서명이 아닌 부서의 부서번호이다!

<"확인" 누른 후>

- 직원 입력폼 empInsertForm.jsp 에서 입력을 한 후 "확인" 을 누르면 "empInsert.do" 로 요청한다

- 이때 이 입력폼에 입력된 값들을 가지고 간다


프로젝트 myBatis2 : 사원 등록폼에서 사번 중복검사 (비동기 처리)

사원 번호 중복 검사 (empInsertForm.jsp 부분)

<script type="text/javascript">
	$(function() {
		// 사원번호 중복검사
		$('#dupCheck').click(function() {
			var empno = $('#empno').val();
			if (!empno) {
				alert('사번 입력후에 체크');
				$('#empno').focus();
				return false;
			}// $.post("요청이름","전달될 값","콜백함수");
			$.post('dupCheck.do', 'empno=' + empno, function(msg) {
				alert(msg);
			});
		});
	});
</script>

- DB 에서 이 사번이 사용가능한지 중복검사를 해야함, 이때 이걸 비동기로 처리하려고 한다

- "중복" 버튼 클릭시 click() 함수 실행됨, 사용자가 입력한 값을 구해오고 그 값이 null 인지 유효성 검사

- 입력 값이 있다면 ajax 기능을 사용해서 post() 함수로 사번 중복검사를 한다

+ 비동기 처리 : 페이지가 바뀌지 않고 처리

 

- 요청 이름값은 "dupCheck.do" 로 한다, 그리고 사용자가 입력한 사번 값을 키 밸류 형식(키=밸류) 으로 전달하며 요청

- 콜백함수로 메세지를 돌려받아서 alert 에 출력

 

ajax post() 함수에 대한 자세한 내용

https://laker99.tistory.com/106 (검색어 : $.post() 함수)

 

Controller 클래스 EmpController.java 에서 "dubCheck.do" 요청 부분만

	// 사원번호 중복검사
	@RequestMapping("dupCheck.do")
	public String dupCheck(int empno, Model model) {
		System.out.println("empno:"+empno);
		Emp emp = es.select(empno);
		String msg = "";
		if (emp != null) // 중복 사원번호
			msg = "이미 있는 데이터입니다";
		else // 사용 가능한 사원번호
			msg = "사용 가능한 사번 입니다";
		model.addAttribute("msg", msg);
		
		// 웹 브라우저에 출력되는 결과가 callback 함수로 리턴된다.
		return "emp/dupCheck";
	}

- 앞에서 키 밸류 형식으로 전달된 사번이, 키값과 같은 이름인 empno 변수로 바로 저장된다

- @RequestParam("empno") 가 int empno 앞에 생략되었다

- 아래에서 empno 를 출력해보면 사용자가 입력한 사번이 출력됨

 

- 그 empno 로 Service 클래스의 select() 를 호출해서 상세 정보를 구해와서 Emp 객체 emp 에 저장

- 만약 이 emp 가 null 이 아니면, 즉 데이터가 존재하면 해당 사번에 해당하는 데이터가 있다는 의미, 즉 중복인 사원번호라는 의미이다

- 사원번호가 중복이면 "이미 있는 데이터입니다", 중복이 아니면 "사용 가능한 사번입니다" 를 msg 변수에 저장

- 브라우저에 출력되는 값이 콜백함수로 리턴되기때문에 출력을 하는 View 페이지로 msg 를 넘겨서 거기서 브라우저 출력을 하자, 그럼 거기서 출력되는 값이 콜백함수로 리턴됨

+ 여기서 out 객체를 생성해서 출력하면 코드가 지저분해짐, msg 출력만을 하기 위한 View 페이지 dubCheck.jsp 를 따로 만든다

 

VIew 페이지 dubCheck.jsp

- dubCheck.jsp

${msg}

- 메세지가 브라우저에 출력되므로 그 결과가 콜백함수로 리턴된다

 

- 다시 empInsertForm.jsp 부분을 보자

<script type="text/javascript">
	$(function() {
		// 사원번호 중복검사
		$('#dupCheck').click(function() {
			var empno = $('#empno').val();
			if (!empno) {
				alert('사번 입력후에 체크');
				$('#empno').focus();
				return false;
			}// $.post("요청이름","전달될 값","콜백함수");
			$.post('dupCheck.do', 'empno=' + empno, function(msg) {
				alert(msg);
			});
		});
	});
</script>

- 콜백함수로 값을 받아서 alert 으로 메세지를 뿌려준다

- 그럼 중복일떄는 "이미 있는 데이터입니다", 중복이 아닐때는 "사용 가능한 사번입니다" 출력


프로젝트 myBatis2 : 사원 등록

- 사원 입력폼 empInsertForm.jsp 에서 입력을 한 후 "확인" 을 누르면 "empInsert.do" 로 요청한다

 

Controller 클래스 EmpController.java 에서 "empInsert.do" 요청 부분만

	// 사원 등록
	@RequestMapping("empInsert.do")
//	public String empInsert(Emp emp, String hiredate1, Model model) {
	public String empInsert(Emp emp, Model model) {
//		emp.setHiredate(Date.valueOf(hiredate1)); // String -> Date 형변환
		int result = es.insert(emp);
		model.addAttribute("result", result);
		model.addAttribute("deptno", emp.getDeptno());
		return "emp/empInsert";
	}

- 이전의 사원 등록폼에서 넘어온 값들을 한번에 @ModelAttribute (생략) 을 통해 DTO Emp 객체 emp 에 세팅

- Service 클래스의 insert() 메소드 호출, 호출하며 사원 등록폼에서 넘어온 값을 저장하는 객체 emp 를 넘겨줌

<돌아온 후>

- insert() 가 호출되어 삽입 성공하면 1 을 반환한다, result 에 그 값을 저장하고 Model 객체에 result 를 저장

- View 인 empInsert.jsp 에서는 삽입 성공시에, 그 사원의 부서 소속 모든 사원을 출력하는 부서 상세페이지로 이동할 것임

- 그러므로 삽입된 사원의 부서번호인 deptno 또한 가져가야한다, Model 객체에 저장 

- /WEB-INF/views/emp/empInsert.jsp 로 이동

 

Service 클래스 EmpServiceImpl.java 에서 insert() 메소드 부분만

	public int insert(Emp emp) {
		return ed.insert(emp);
	}

 

DAO클래스 EmpDaoImpl.java 에서 insert() 메소드 부분만

	public int insert(Emp emp) {
		return sst.insert("empns.insert", emp);
	}

 

Mapper 파일 Emp.xml 에서 id 가 "insert" 인 태그 부분만

	<insert id="insert" parameterType="emp">
		insert into emp values(#{empno},#{ename},#{job},#{mgr},
			#{hiredate},#{sal},#{comm},#{deptno})
	</insert>

- 객체 emp 를 통해 넘어온 값들을 사용해서 insert 를 해준다

- #{empno} 는 emp.getEmpno() 와 같은 의미

- 입사일(hiredate) 은 캘린더에서 선택한 값이었지만, 객체 emp 에서 입사일은 Date 형으로 저장되어 있다

- Emp DTO 클래스에 Date 형으로 되어있기 때문이다

 

View 페이지 empInsert.jsp

- emp/empInsert.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 = "empList.do?deptno=${deptno}";
		</script>
	</c:if>
	<c:if test="${result <= 0 }">
		<script type="text/javascript">
			alert("입력 실패");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- 삽입 성공시 result 가 1 이므로 "입력 성공" 출력 후 그 사원의 부서 상세 페이지로 이동하기 위해 "empList.do" 로 요청

- 요청하면서, 부서 상세 페이지에서는 해당 부서 소속 모든 사원 목록을 보여주므로 deptno 도 GET 으로 전달한다


- 사원 등록은 아직 하지 않았다

- 내일 등록하자

복습

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

 

코딩 66 일 / 2022.09.26 / MyBatis, JSP Model 1 에 MyBatis 연동

복습 Maven - Spring 프로젝트는 Maven 으로만 라이브러리 관리 가능 - Springboo 프로젝트는 Maven, Gradle 둘 다 사용 가능 Maven 파일 구조 - src/main/java 에 확장자가 Java 인 파일을 넣는다 - src/main/re..

laker99.tistory.com

 

파일들 살펴보기 : 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 추가하는 방법

복습

Spring Project (Web Application) 구조

 

환경설정 파일 정리

1. Spring 환경설정 파일

- servlet-context.xml

- root-context.xml

 

2. 프로젝트 환경설정 파일

- web.xml

 

3. Maven 환경설정 파일

- pom.xml

 

root-context.xml

- DB 연동 관련 내용

- 주로 bean 을 만들어서 DB 접속을 처리한다

- 어노테이션 기반 DI + Setter DI 도 사용함

- beans 루트 엘리먼트 안에 bean 을 추가해서 사용

- 어노테이션 방식 + 직접 bean 객체 생성 방식 둘다 사용함

 

servlet-context.xml

- View 파일들이 저장된 최상위 디렉토리 위치를 잡음, 그래야 View 파일로 찾아갈 수 있다

- ViewResolve (prefix, suffix)

- ViewResolve 에도 Setter DI 사용함

 

 

- 어노테이션 기반 DI로 처리됨

 

 어노테이션 기반 DI 사용 조건

1. servlet-context.xml 파일에 base-package 가 지정되어야한다

 

2.해당 클래스위에 4가지 어노테이션 중 하나가 와야한다

3. 주입할 프로퍼티(객체) 위에 @Autowired 작성

 

어노테이션 기반 DI

- Controller 클래스에서 Service 클래스로 넘어가는 법 : Service 객체를 생성해서 메소드를 호출

- Service 클래스에서 DAO 클래스로 넘어가는 방법 : DAO 객체를 생성해서 메소드 호출

- 객체를 생성할때 어노테이션 기반 DI 로 생성하는 것임


Spring Web Application 예제 1

실습 준비

- 기본파일들이 있는 클라우드의 hello 프로젝트를 STS로 import

 

Spring 예제 1

hello 프로젝트 구조

- src/main/java 안에 보통 DAO, Service, Controller 클래스들이 들어있다, 현재는 Controller 클래스만 존재

- View 파일들은 view 폴더 안에 저장되어 있다

- index 파일은 반드시 webapp 폴더 안에 있어야한다

 

- 파일을 하나씩 살펴보자


hello 프로젝트의 환경설정 파일 1 : Maven 환경설정 파일

pom.xml

- Maven 환경설정 파일 pom.xml 에 이미 기본적인 내용이 세팅되어 있다

 

pom.xml 에 작성된 Java 버전, Spring 버전

 

pom.xml 에 추가된 의존 라이브러리 부분 설명

- jacskon 라이브러리 : 콜백함수 비동기로 처리할때 DTO, 리스트를 Json 으로 변환시켜줄 수 있다

- 오라클 비공식 원격저장소, 오라클 JDBC 라이브러리

- MyBatis, MyBatis-Spring 연결 라이브러리 : MyBatis 라이브러리 뿐 아니라 MyBatis-Spring 연결 라이브러리까지 필요

- inject 라이브러리 : @inject 어노테이션 사용할때 필요 (@Autowired 어노테이션과 같은 역할)

- commons-fileupload 라이브러리 : cos 라이브러리를 대신해서 첨부파일 업로드를 위한 라이브러리


hello 프로젝트의 환경설정 파일 2 : 프로젝트 환경설정 파일

web.xml

- Dynamic Project 때부터 계속 만들어졌던 파일

- Apache Tomcat 이 구동될때 자동으로 실행되며, 가장 먼저 읽어오는 파일

- WEB-INF 폴더 내에 있어야 한다

 

web.xml 에 들어가는 주요 3가지 내용

1. Dispatcher Servlet 이름, 위치 및 매핑 설정

- web.xml 부분

- <servlet-class> : Dispatcher Servlet 위치 설정

- <servlet> : 요청시 내부적으로 Dispatcher Servlet (Front Controller) 로 먼저 찾아가기 위해 web.xml 에 Dispatehcer Servlet 의 이름과 위치 등록

- <servlet-mapping> : Dispatcher Servlet 으로 찾아가도록 Servlet 매핑을 잡는 내용

- <servlet-name> : <servlet> 과 <servlet-mapping> 의 <servlet-name> 을 일치시켜야한다

- <url-pattern> : 우리는 이 패턴값만 필요에 따라 조절하면 된다, Model 2 에선 @WebServlet 어노테이션으로 찾아갔었다

 

2. 환경설정 파일 2개 불러오기 : root-context.xml 과 servlet-context.xml 파일

- web.xml 부분

- web.xml 파일은 Apache Tomcat 구동시 자동으로 실행되므로, 자동으로 실행 안되는 다른 환경설정 파일 root-context.xml, servlet-context.xml 을 여기서 부른다

- Application 에서는 main() 메소드에서 환경설정 파일을 직접 읽어왔지만 여기서는 web.xml 이 불러주므로 root-context.xml, servlet-context.xml 은 자동으로 실행됨

+ 만약 이 두 환경설정 파일을 resources 폴더에 넣는다면, classpath: 를 앞에 붙여서 경로를 잡아야한다

 

3. 한글 인코딩

- web.xml 부분

- CharacterEncodingFilter 는 Spring 지정 라이브러리

- 한글값을 post 방식으로 전송할때 한글값을 UTF-8 로 인코딩 시켜주는 역할을 한다

- 자동으로 들어가있지 않고 추가해야하는 코드이다

- 더이상 값이 넘어오는 파일이나, Service 클래스에서 request.setCharacterEncoding("utf-8") 코드를 작성할 필요가 없다


hello 프로젝트의 환경설정 파일 3 : Spring 환경설정 파일

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 연동에 필요한 내용이 들어갈 것

- 현재는 DB연동을 안하는 프로젝트 이므로 비어있다

- 주로 필요한 bean 을 직접 안에 추가한다 이때 주입시엔 주로 Setter DI 사용 + Constructor DI 도 사용

- 어노테이션 기반 DI 도 사용한다


hello 프로젝트의 환경설정 파일 4 : Spring 환경설정 파일

servlet-context.xml

- DB 연동에 필요한 파일만 root-context.xml 에서 처리, 나머지 대부분의 환경설정은 주로 servlet-context.xml 이 처리

 

1. 어노테이션 기반으로 bean 을 사용하기 위해 base-package 를 지정

- base-package 로 지정된 패키지안의 모든 클래스들을 읽어온다는 의미

- base-package 로 지정된 패키지안의 클래스들은 4가지 어노테이션 중 하나의 어노테이션이 있어야한다

ex) Service 클래스 위에는 @Servlet 어노테이션, Controller 클래스 위에는 @Controller 어노테이션

+ 현재는 DB연동을 하지 않으므로 Service 클래스는 없고 Controller 클래스와 DTO 클래스가 있음

 

+ top-level 패키지

- Spring Project 만들때 설정했던 top-level 패키지 com.ch.hello 가 src/main/java 안에 들어가 있다

- 나중엔 이 top-level 패키지를 쓰지 않고 다른 패키지를 직접 만들어서 사용함

 

2. ViewResolve 로 View 페이지가 저장될 최상위 디렉토리 설정, 확장자 설정

- Spring 에서 지원되는 InternalResourceViewResolver 클래스로 bean 객체를 생성해서 Setter DI 로 주입하고 있다

- prefix : View 파일들이 저장된 최상위 디렉토리

- suffix : View 파일의 확장자

+ InternalResourceViewRoslver 클래스에는 필드 prefix, suffix 와 Setter 메소드들이 있음

- Controller 클래스에서 리턴할땐 경로인 prefix 와 확장자인 suffix 를 생략해서 return 해야한다

- /WEB-INF/view/home.jsp 가 아닌 "home" 만 작성해서 return 해야함

 

3. 기본적인 매핑을 통해 View 페이지 위치 설정

- 다음에 설명


hello 프로젝트 기능

- index 파일인 index.jsp 를 실행해보자

- hello 클릭시 현재 시간 출력

- 배경색 클릭시 랜덤하게 배경색을 보여줌

- 구구단 클릭시 랜덤하게 구구단 한단을 보여줌

- 회원가입 클릭시 회원가입 폼이 나오고, 회원가입 시킬수 있다 (DB연동은 되어있지 않고 메모리에만 저장)


index.jsp

<%@ 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>Insert title here</title>
<script type="text/javascript">
	function chk() {
		var url = document.getElementById("url").value;
		location.href = url;
	}
</script>
</head>
<body>
	<h2>원하는 작업을 선택하세요</h2>
	<!-- <script type="text/javascript">
			location.href="hello";
		</script> -->
	<select id="url" onchange="chk()">
		<option value="hello">hello</option>
		<option value="color">배경색</option>
		<option value="gugu">구구단</option>
		<option value="joinForm">회원가입</option>
	</select>
</body>
</html>

 

- select 옵션에서 옵션 선택시 change 이벤트 발생, onchage 시 chk() 호출

- chk() 에서 id 가 url 인 태그, 즉 select 태그를 구해오고 value 속성으로 옵션의 value 구해온다

- "gugu" 선택시 location.href 를 "gugu" 로 설정하며 요청하게 됨

 

- 요청을 하면 HomeController.java 의 @RequestMapping 에서 요청을 받는다

+ 옛날 버전에서 사용했던 Handler Mapping 대신 현재는 @RequestMapping 어노테이션으로 요청을 받음


@RequestMapping 어노테이션

- Controller 클래스에서 클라이언트 요청을 받을때 사용하는 어노테이션

- Get / Post 방식으로 요청을 받을 수 있다

ex) index.jsp 에서 요청을 하면 HomeController.java 의 @RequestMapping 에서 요청을 받는다

- value 속성값 : 요청 이름값을 작성, value 값은 서로 다르게 작성해야 한다

- method 속성값 : get 방식으로 요청시 RequestMethod.GET, post 방식으로 요청시 RequestMethod.POST 라 작성

- 그 요청을 받을때 아래의 메소드가 자동으로 실행된다

 

주의

- value 속성값은 "/hello" 처럼 요청이름명 앞에 / 를 붙여도 되고, "hello" 처럼 / 를 빼도 된다.

- 하지만 요청을 보내는 쪽에서는 "/" 를 써선 안된다

- 또한 value 도 생략해도 된다, 바로 value 속성값만 쓰면 된다

- 그리고 Get 방식은 method 속성 자체를 생략해도 된다


Spring MVC 패턴 흐름 설명 예시 1

 

index.jsp 에서 location 객체를 통해 "hello" 로 요청이 왔다면

- select-option 에서 "hello" 를 선택시 "hello" 로 요청이 간다

- 클라이언트 요청 -> Dispatcher Servlet -> Controller 클래스 -> Dispatcher Servlet -> View 페이지

- 이 흐름을 반으로 나눠서 각각 설명

 

클라이언트 요청 -> Dispatcher Servlet -> Controller 클래스

1. 가장 먼저 Dispatcher Servlet 으로 찾아감

- web.xml 에 Servlet 매핑을 "/" 로 잡아뒀으므로 아무거나 요청해도 Dispatcher Servlet 으로 먼저 찾아간다

 

2. 내가 만든 Controller 클래스에서 @RequestMapping 중 요청 이름값과 요청 방식이 일치하는 곳으로 간다

- 아래 코드가 요청 이름값과 요청 방식이 일치하는 곳이다. 

- 요청이름값인 value 는 모두 달라야한다

+ 옛날 버전에서 사용했던 Handler Mapping 대신 현재는 @RequestMapping 어노테이션으로 요청을 받음

 

3. 찾아간 @RequestMapping 아래의 메소드가 자동 호출됨

- hello() 가 자동으로 실행됨

+ 주로 요청이름값과 메소드 명을 일치시키는 경우가 많다 ex) "/hello" 요청시 실행되는 메소드는 hello()

+ 메소드 앞의 자료형은 대부분 String 자료형이 온다

 

+ 3-1. 값을 가져갈때만 :

- View 페이지로 값을 가져가기 위해 Model 객체 또는 ModelAndView 객체를 사용

- 매개변수로 Model 을 설정하면 Model 객체가 자동으로 생성된다

- 위에 Model 클래스를 import 하고 있음

+ 매개변수로 Session 을 설정하면 Session 객체가 자동으로 생성된다

 

Controller 클래스 -> Dispatcher Servlet -> View 페이지

+ 현재는 DB연동하지 않으므로 Service 클래스로 가지 않고 Controller 에서 바로 View 로 이동

 

hello() 안의 내용

4. Date 객체 생성하고 DateFormat 객체로 날짜 시간을 LONG 형으로 길게 설정하고 format() 으로 포맷 적용

 

5. Model 객체 model 로 addAttribute() 메소드를 사용해서 키-밸류 형태로 값을 가져간다

 

6. return "home" 처럼 return 뒤에 View 페이지의 경로값 설정

- servlet-context.xml 에서 prefix, suffix 로 설정했던 것을 생략해야함

- 리턴자료형이 String 이므로 String 형으로 리턴

 

7. Dispatcher Servlet 으로 내부적으로 이동 -> ViewResolver 에서 prefix, suffix 붙이기 -> View 인 WEB-INF/views 안의 home.jsp 로 이동

- servlet-context.xml 의 ViewResolver 에서 설정했던 prefix, suffix 를 붙여서 그 경로로 찾아간다

 

8. View 페이지에서 이 값을 가져온다

- home.jsp 부분

- value 값으로 어떤 값의 형태가 오는지에 따라 사용방법이 달라진다 (기본자료형, DTO, 리스트)

 

- "hello" 요청 캡처


Spring MVC 패턴 흐름 설명 예시 2 (간략)

 

index.jsp 에서 location 객체를 통해 "color" 로 요청이 왔다면 (간략)

- select-option 에서 "배경색" 를 선택시 "color" 로 요청이 간다

- 클라이언트 요청 -> Dispatcher Servlet -> Controller 클래스 -> Dispatcher Servlet -> View 페이지

 

1. index.jsp 에서 location.href 를 통해 "color" 로 요청

2. 내부적으로 Dispatcher Servlet 으로 찾아감

3. HomeController 클래스에서 요청 이름값, 요청 방식이 일치하는 @RequestMapping 으로 "color" 요청을 받음

- 이름값이 맞으면 무조건 값을 받는다

- Contorller 클래스는 여러개일 수 있지만 value 값은 달라야함

4. 난수를 발생시켜서 랜덤으로 색 하나를 구한 뒤 그걸 Model 객체 model 에 value 로 저장함

5. return "color" 와 ViewResolver의 prefix, suffix 에 의해 View 페이지인 /WEB-INF/view/color.jsp 로 찾아간다 

6. color.jsp 에서 body 태그의 bgcolor 속성으로 ${color} 로 해당 값 불러오기

 

- "color" 요청 캡처


Spring MVC 패턴 흐름 설명 예시 3 (간략)

 

index.jsp 에서 location 객체를 통해 "gugu" 로 요청이 왔다면 (간략)

- select-option 에서 "배경색" 를 선택시 "gugu" 로 요청이 간다

- 클라이언트 요청 -> Dispatcher Servlet -> Controller 클래스 -> Dispatcher Servlet -> View 페이지

 

1. index.jsp 에서 location.href 를 통해 "gugu" 로 요청

2. 내부적으로 Dispatcher Servlet 으로 찾아감

3. HomeController 클래스에서 요청 이름값, 요청 방식이 일치하는 @RequestMapping 으로 "gugu" 요청을 받음

- 이름값이 맞으면 무조건 값을 받는다

- Contorller 클래스는 여러개일 수 있지만 value 값은 달라야함

4. 난수를 발생시켜서 랜덤으로 2 ~ 9 중 하나의 숫자를 Model 객체 model 에 value 로 저장함

+ View 페이지에 가져갈 값이 없을땐 매개변수에 아무것도 작성하지 않아도 된다

- 값을 가져갈때만 매개변수로 Model 객체를 받는다

5. return "gugu" 와 ViewResolver의 prefix, suffix 에 의해 View 페이지인 /WEB-INF/views/gugu.jsp 로 찾아간다 

- 이때 이 파일은 반드시 설정되어있는 WEB-INF/views 안에 있어야한다

6. gugu.jsp 에서 JSTL forEach 태그 안에서 해당 단의 구구단을 구하기

 

+ JSTL 코어 라이브러리 불러오기

 

- "gugu" 요청 캡처


Spring MVC 패턴 흐름 설명 예시 4 (간략)

 

index.jsp 에서 location 객체를 통해 "joinForm" 로 요청이 왔다면 (간략)

- select-option 에서 "회원가입" 를 선택시 "joinForm" 로 요청이 간다

 

Controller 클래스는 여러개 만들 수 있다, @RequestMapping 값만 서로 다르게 설정하면 된다

- HomeController 클래스 안에는 "joinForm" 요청을 받는 어노테이션이 없다

- JoinController 클래스에서 @RequestMapping("/joinForm") 으로 요청을 받는 어노테이션이 있다

- JoinController 클래스로 간다

 

- 클라이언트 요청 -> Dispatcher Servlet -> Controller 클래스(JoinController.java) -> Dispatcher Servlet -> View 페이지

 

1. index.jsp 에서 location.href 를 통해 "joinForm" 로 요청

2. 내부적으로 Dispatcher Servlet 으로 찾아감

3. JoinController 클래스에서 요청 이름값, 요청 방식이 일치하는 @RequestMapping 으로 "joinForm" 요청을 받음

 

- 이름값이 맞으면 무조건 값을 받는다

- Contorller 클래스는 여러개일 수 있지만 value 값은 달라야함

4. return "joinForm" 와 ViewResolver의 prefix, suffix 에 의해 View 페이지인 /WEB-INF/views/joinForm.jsp 로 찾아간다 

- 바로 회원가입 폼인 "joinForm.jsp 로 간다

- View 페이지에 가져갈 값이 없으므로 매개변수에 아무것도 작성하지 않아도 된다

- 이때 이 파일은 반드시 설정되어있는 WEB-INF/views 안에 있어야한다

6. joinForm.jsp 인 회원가입 폼으로 간다

 

7. 전송버튼 누르면 post 방식 "join" 으로 요청한다

- 다시 Dispatcher Servlet -> Controller 클래스로 간다

- 입력양식의 name 값들을 유심히 보기

- 회원가입 할때 회원 정보를 저장하기 위한 DTO 클래스 Member 의 프로퍼티명과 입력양식의 name 값들을 일치시켜야한다

- 그래야만 매핑을 통해 자동으로 DTO 객체에 입력양식의 값들이 들어간다 * 아래에 자세히 설명

- Member.java 부분

8. Controller 클래스에 @RequestMapping 으로 찾아가서 join() 메소드 실행

9. 매개변수로 이미 값이 자동 세팅된 DTO 객체를 받는다

- DTO 객체인 member 에 값을 세팅하지 않아도 폼에서 넘어온 입력양식의 값들이 자동으로 DTO 객체 member 안에 세팅됨

 

- Member member 앞에 @ModelAttribute 생략된 것임

- 이 @ModelAttribute 가 뒤쪽의 DTO 객체에 Setter 메소드로 자동으로 세팅해준다

- 다만, DTO 객체의 필드명과 입력양식의 name 값들이 일치할때만 가능하다!

- 그 자동 세팅된 객체 member 를 View 로 보내기 위해 addAttribute() 사용

10. Model 객체 model 을 통해 addAttribute() 로 DTO 객체 member 를 저장

11. return "joinResult" 를 통해 WEB-INF/views/joinResult.jsp 로 간다

12. DTO 객체가 Model 객체에 저장되었으므로 View 페이지에서 EL 로 출력할때는 ${공유네임값.필드명} 인 ${member.id} 로 출력

 


@ModelAttribute 어노테이션

- 어노테이션을 선언하고 뒤에는 DTO 객체를 선언한다

- 뒤쪽의 DTO 객체에 폼을 통해 한꺼번에 넘어온 값들을 세팅해준다

- 가입되는 양식의 name 값 과 DTO 클래스의 필드명을 일치시켜야만 가능

- @ModelAttribute 어노테이션을 생략해도 DTO 필드명과 name값이 일치하면 DTO 객체에 값이 자동으로 들어감

- 더이상 Setter 메소드로 넘어온 값들을 DTO 객체에 값을 설정하지 않아도 된다

- 그래서 가입되는 양식의 name 값 과 DTO 클래스의 필드명을 일치시켜야함


+ header.jsp 파일에는 공통적으로 들어가는 내용들이 있다, 그걸 include 하고 있다

- joinForm.jsp 부분

- header.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/bootstrap.min.css" rel="stylesheet">
<script src="js/jquery.js"></script>
<script src="js/bootstrap.min.js"></script>
<style>	.err { color: red; font-size: 20px;  } </style>

- 부트스트랩을 적용, 각종 Javascript 파일을 포함

 

- "joinForm" 요청 캡처

- @ModelAttribuate 로 DTO 객체에 값이 세팅되는지 확인하기 위해 위 코드 작성 후 콘솔 확인

- 아이디를 잘 출력하는 것을 보아 DTO 객체에 값이 잘 세팅되었음을 확인 가능


요청이름명으로 Controller 로 찾아갈때 주의

- value 속성값은 "/hello" 처럼 요청이름명 앞에 / 를 붙여도 되고, "hello" 처럼 / 를 빼도 된다.

- 또한 value 도 생략해도 된다, 바로 value 속성값만 쓰면 된다

- 그리고 Get 방식은 method 속성 자체를 생략해도 된다

 

- 하지만 요청을 보내는 쪽에서는 "/" 를 써선 안된다

 

- joinForm.jsp 부분

- index.jsp 부분


- HomeController.java (Controller 클래스, 전체 코드)

package com.ch.hello;

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.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

	@RequestMapping(value = "/hello", method = RequestMethod.GET)
	public String hello(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		String formattedDate = dateFormat.format(date);
		model.addAttribute("serverTime", formattedDate);
		return "home";
	}

	@RequestMapping("/color")
	public String color(Model model) {
		String[] color = { "red", "orange", "yellow", "green", "blue", "navy", "violet" };
		int num = (int) (Math.random() * 7);
		model.addAttribute("color", color[num]);
		return "color";
	}

	@RequestMapping("/gugu")
	public String gugu(Model model) {
		int num = (int) (Math.random() * 8) + 2;
//		int a = num / 0;
		model.addAttribute("num", num);
		return "gugu";
	}
	/*
	 * @ExceptionHandler(ArithmeticException.class) 
	 * public String err() { 
	 * return
	 * "arr-err"; }
	 */
}

- 새로운 어노테이션을 보기 위해 다른 파일을 보자

 

- person.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>
	<form action="addr">
	<!-- <form action="addr2"> -->
		이름 : <input type="text" name="name">
		<p>
			주소 : <input type="text" name="addr">
		<p>
			<input type="submit" value="확인">
	</form>
</body>
</html>

- action 값이 "addr" 인 경우 form 태그 먼저 설명 * action 값이 "addr2" 인 경우는 아래에서 설명

- 이 person.jsp 실행시 이름, 주소 입력 양식

- 이름 입력양식의 name 값은 "name", 이 값을 받기 위해 request.getParameter() 를 사용했었다

- 주소 입력양식의 name 값은 "addr", 이 값을 받기 위해 request.getParameter() 를 사용했었다

- 이제 이걸 어노테이션으로 대체 가능

 

- '확인'을 누르면 이름과 주소가 Dispatcher Servlet -> Controller 클래스로 간다

- Controller 클래스에서 "addr" 을 받는 @RequestMapping 어노테이션을 찾아보자

- PersonController.java 부분

- 위에 @Controller 어노테이션이 붙어있는 Controller 클래스 중 하나이다

- action 값이 "addr" 인 요청을 @RequestMapping 어노테이션이 있다

- 폼에서 넘어온 값을 받아보자

 

폼에서 넘어온 값 받기

- @RequestParam 어노테이션을 통해 앞에서 넘어온 값들을 name 값을 통해 가져와서 매개변수에 넣어준다

 

@RequestParam 어노테이션

- name 으로 값을 받을때 사용하는 어노테이션

- @RequestParam("name") 으로 앞에서 넘어온 값을 받아서 뒤쪽의 String 형 변수 name 에 저장한다

- @ReuqestParam("addr") 으로 앞에서 넘어온 값을 받아서 뒤쪽의 String 형 변수 addr 에 저장한다

+ 다운캐스팅 명시하지 않아도 자동으로 뒤에 쓰인 자료형으로 자동 변환된다

- 그러면 넘어온 name, addr 값을 addr() 메소드 안에서 바로 사용가능

- 넘어온 값을 받을때 request.getParameter("name") 으로 받았던 것을 @RequestParam 으로 받을 수 있다

 

- 즉, 아래 두 줄의 코드는 같은 코드이다

String name = request.getParameter("name")
@RequestParam("name") String name

 

@RequestParam 어노테이션 생략가능한 조건

- 위 코드는 아래처럼 @RequestParam 을 생략할 수도 있다

- 전달되는 name 값과 같은 이름의 변수로 값을 받을때는 @RequestParam 을 생략 가능하다

- 앞에서 넘어온 name 값인 "name", "addr" 과 같은 이름인 "name", "addr" 을 매개변수에 썼으므로, @RequestParam 을 생략가능하다

 

- 이후, 받아온 name, addr 값들을 다시 Model 객체에 저장해서 View 페이지인 WEB-INF/views/addr.jsp 로 간다\

- addr.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>${name }님 ${addr }에 사시는 군요
</body>
</html>

- Model 객체에 String 형으로 저장되어있으므로 단순히 ${name}, ${addr} 을 통해 View 에서 출력 가능

 


 

- 개별적으로 전달되는 값을 받을때는 @RequestParam 을 사용했다

- 이번에는 전달되는 값을 한번에 받아서 객체에 자동으로 저장하는 @ModelAttribute 를 사용해보자

 

- person.jsp 에서 action 값이 "addr2" 인 경우 form 태그를 설명

- person.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>
	<!-- <form action="addr"> -->
	<form action="addr2">
		이름 : <input type="text" name="name">
		<p>
			주소 : <input type="text" name="addr">
		<p>
			<input type="submit" value="확인">
	</form>
</body>
</html>

- 입력하고 '확인' 을 누르면 이름과 주소가 Dispatcher Servlet -> Controller 클래스로 간다

 

- PersonController.java 부분

- 즉 앞에 @ModelAttribute 가 생략되어 있는 것이다

- 폼에서 넘어온 값들을 한번에 DTO Person 객체인 p 에 저장(세팅)한다

- 이때, DTO Person 클래스의 프로퍼티명과 넘어온 값의 name 이 일치하므로, 넘어온 값이 자동 매핑되어 Person 객체에 저장되는게 가능하다

 

- Person.java (DTO 클래스)

package com.ch.hello;

public class Person {
	private String name;
	private String addr;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddr() {
		return addr;
	}

	public void setAddr(String addr) {
		this.addr = addr;
	}
}

- 폼에서 넘어온 name, addr 을 받아 저장하도록 프로퍼티가 같은 이름으로 있다

- 그러므로 자동으로 이 Person 객체에 넘어온 값들을 세팅해서 저장 가능

 

- 그리고 그렇게 세팅한 Person 객체 p 를 Model 객체 model 에 저장한 후 WEB-INF/views/addr2.jsp 로 이동

- addr2.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>${person.name }님 ${person.addr }에 사시는 군요
</body>
</html>

- DTO 객체 p 가 "person" 이란 네임으로 저장되었으므로 View 페이지에서 출력할땐 ${person.필드명} 으로 출력하고 있다

 

+ 객체 p 에 값이 제대로 세팅되었는지 확인해보자

- 위 한줄을 추가하고 person.jsp 실행해서 값 입력 후 전송

- 콘솔창 보기

- 폼에서 넘어온 값들이 DTO Person 객체 p 에 잘 저장되었음을 확인 가능


정리

- @ModelAttribute : (주로 폼을 통해) 넘어온 값을 한번에 받아서 DTO 객체에 저장하고 싶을때 사용

- @RequestParam : 개별적으로 값을 받아 변수에 저장하고 싶을때 사용

- 수십개의 어노테이션이 있다, 최소 10개 정도는 알아야 사용 가능

 

Model 객체에 저장되는 값에 따라 값을 View 에서 출력하는 방법

1. 기본자료형 변수 : ${키} 로 출력

2. DTO 객체 : ${키.필드명} 으로 출력

3. 리스트 : forEach 태그의 items 속성에 ${키} 를 써서 사용



- Controller 클래스에서 메소드 앞에 자료형이 String 이 아닌 DTO, 리스트가 오는 경우를 보자

 

- sample.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>
	<script type="text/javascript">
		location.href="sample";
		// location.href = "list";
	</script>
</body>
</html>

- location.href 로 "sample" 을 주는 경우부터 먼저 하자 * "list" 로 주는 경우는 아래에

- sample.jsp 를 실행되자마자 "sample" 로 요청을 한다


- sample.jsp 실행시

- json 형태 데이터를 출력함

- json 은 이런 키:밸류 형태로 되어있다

- 1명에 대한 정보를 DTO 객체에 저장시켜서 JSON 형태로 돌려준 것이다

- pom.xml 에 추가된 jackson-databind 라이브러리가 DTO 객체를 JSON 데이터로 변환시킨것이다


- sample.jsp 에서 "sample" 로 온 요청을 받아주는 Controller 클래스의 @RequestMapping 어노테이션으로 가자

- SimpleController.java 부분

- 보통은 메소드의 리턴자료형이 String 이지만 이 경우엔 DTO 클래스인 SampleVo 클래스가 리턴자료형이다

- 비동기적으로 처리할때 많이 사용함

- DTO SampleVo 객체를 sv 를 생성 후 Setter 메소드로 값을 저장시킴

- DTO SampleVo 객체 sv 를 리턴하면 @ResponseBody 어노테이션때문에 값을 "요청한 곳(호출한 곳)"으로 돌려준다

- 즉, "sample" 로 요청했던 브라우저인 sample.jsp 로 돌려준다

- 돌려줄때 DTO 객체를 JSON 형태로 변환한 후 돌려준다

+ @RestController = @Controller + @ResponseBody

 

- sample.jsp 로 세팅한 DTO 객체를 돌려주는데 JSON 으로 변환해서 콜백함수 형태로 브라우저에 돌려주므로 브라우저에 아래처럼 출력됨 

- 이때, JSON 형태 데이터의 키는 그 DTO SampleVo 클래스의 필드명이고, 밸류는 DTO 객체에 세팅했던 값이 된다

 

@RestController 어노테이션

- Spring 4점대부터 지원하는 어노테이션

- 두가지 어노테이션을 결합한 어노테이션

- @RestController = @Controller + @ResponseBody

- 메소드 위에 @ResponseBody 어노테이션이 붙어있으면, 요청한 곳으로 결과를 콜백함수로 돌려주는 역할을 한다

- 따로 콜백함수를 설정하지 않았으므로 요청한 곳으로 값을 돌려줌, 콜백함수가 있을때는 거기로 돌려준다

 

- @RestController 어노테이션을 @Controller 와 @ResponseBody 로 나누는 방법

- 이러면 아래의 @RestController 만을 사용했던 코드와 같다

 

- SampleVo.java (DTO 클래스) 부분

- mno, firstName, lastName 3개의 프로퍼티가 만들어져있고, getter / setter 메소드가 있다


- sample.jsp 에서 "list" 로 요청하는 경우를 보자

- sample.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>
	<script type="text/javascript">
		// location.href="sample";
		location.href = "list";
	</script>
</body>
</html>

 

- SampleController.java 부분

+ Controller 클래스명위에 @Controller 가 있고 여기 함수명 위에는 @ResponseBody 를 붙였다

- 이번엔 List 로 리턴하는 경우이다

- 리스트를 생성한 후 반복문에서 DTO 객체를 생성, 세팅하고 리스트에 추가하는 것을 반복

- jackson 라이브러리가 DTO 객체를 JSON 형태로 변환한 후, 요청한 곳으로 해당 리스트를 돌려준다

- 즉, 요청한 브라우저인 list.jsp 로 돌려준다

 

- sample.jsp 실행시

- JSON 형태로 변환된 DTO 객체의 값들이 출력되어있다


Spring Web Application 예제 2

 

실습 준비

- 클라우드에서 ch07 프로젝트를 다운받아 import

 

Spring Web Application 예제 2\

ch07 프로젝트 구조

- 파일별 자세한 기능은 이전 프로젝트인 hello 프로젝트의 설명을 보기

- 지금은 hello 프로젝트에서 달라진 내용만 설명


추가된 라이브러리

- pom.xml 부분 1

- Spring 에선 cos 라이브러리 대신 주로 fileupload 라이브러리로 첨부파일 업로드

 

cos 라이브러리 vs fileupload 라이브러리

- fileupload 라이브러리는 업로드 시켜주는 코드가 따로 있다

- fileupload 라이브러리는 중복되는 이름의 파일명을 자동처리해주지 않아서 내가 처리해야함

 

- pom.xml 부분 2

- 이메일로 전송해주는 라이브러리

- 아이디, 비밀번호 찾기를 했을때 아이디, 비밀번호를 이메일로 전송해주는 기능을 위해서 이 라이브러리들을 추가해야함


URL 패턴 설정

- web.xml 부분

- <url-pattern> 을 *.do 로 설정했다

- Dispatcher Servlet 으로 찾아갈때 do 확장자를 가진 요청이름값일때만 Dispatcher Servlet 으로 보내겠다는 의미


Spring 환경설정 파일 위치 및 부르기

- 현재는 Web Application 이므로 webapp 폴더가 존재하고, 그 webapp 폴더 아래 어딘가에 servlet-content.xml, root-context.xml 이 있다

- 이 Spring 환경설정 파일들은 위치가 고정된 것이 아니며, 나중에 주로 resources 폴더에 저장시키기도 한다

- resources 폴더에 저장되어있을땐, web.xml 에서 환경설정 파일을 불러올때 classpath: 를 붙여야한다

 

ex) servlet-context.xml 이 resources 폴더에 저장되어있다면 web.xml 에서 불러올때 classpath:servlet-context.xml 으로 경로 설정

- web.xml 부분

	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

한글값 인코딩

- 위에서 web.xml 에 들어가는 내용 중 Spring 환경설정 파일 부르기, Dispatcher Servlet 위치 및 패턴 지정을 했다

- 마지막인 한글값 인코딩 내용 또한 들어가 있다

- post 로 넘어온 한글값에 대한 인코딩 완료


Spring 환경설정 파일

 

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">
	<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	</bean>		
		
</beans>

- 직접 bean 을 추가해서 객체를 생성해야한다

 

servlet-context.xml 부분 1

	<context:component-scan base-package="com.ch.ch07" />

- 어노테이션 기반으로 쓰기 위한 첫번째 조건이다

- com.ch.ch07 패키지 안의 모든 클래스를 읽어오겠다는 의미

 

- LoginCheck.java , UploadController.java 클래스를 읽어온다

- 해당 UploadController.java 클래스명 위에 @Controller 어노테이션이 붙어있다

- 어노테이션 기반으로 쓰기 위한 두번째 조건이다

 

servlet-context.xml 부분 2

	<!-- 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>

- View 파일들은 모두 /WEB-INF/views 안에 들어가있어야한다

- 확장자는 jsp 이다

 

servlet-context.xml 부분 3

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />
	<resources mapping="/css/**" location="/WEB-INF/css/" />
	<resources mapping="/js/**" location="/WEB-INF/js/" />
	<resources mapping="/fonts/**" location="/WEB-INF/fonts/" />

- "View 페이지에 사용할" CSS, JS, 폰트 등의 매핑을 설정하는 역할

- CSS 파일들은 /WEB-INF/css/ 폴더 안에 저장되어있어야함

- JS 파일들은 /WEB-INF/js/ 폴더 안에 저장되어있어야함

- Fonts 들은 /WEB-INF/fonts/ 폴더 안에 저장되어있어야함

- 이렇게 매핑이 잡혀있어야만 불러올 수 있음

 

- 여기까지는 아는 내용, 다음은 새로하는 내용

 

servlet-context.xml 부분 4 : Intercepter 설정

	<!-- Interceptor설정 -->	
	<beans:bean id="lc" class="com.ch.ch07.LoginCheck"/>
	<interceptors>
		<interceptor>
			<mapping path="/upload.do"/>
			<beans:ref bean="lc"/>
		</interceptor>
	</interceptors>

* Intercepter 내용 아래에서 자세히 설명

- 회원관리에서 주로 사용


인터셉터 (Intercepter)

- 클라이언트에서 특정 URL 요청시 DispatcherServlet이 Controller를 호출하기 이전에 해당 요청을 가로채는 역할을 수행한다.

 

인터셉터 (Intercepter) 사용 예제

- 세션을 공유한 후, 정상적으로 접근시에는 세션이 공유되지만 비정상적 접근시에는 세션이 공유되지 않는다

- 세션 있는지 확인한 후 없다면 로그인 폼으로 보내는 역할을 Intercepter 가 하게 됨

 

- servlet-context.xml 에서 Intercepter 를 설정한 부분을 보자

- "upload.do" 로 요청이 들어오면 Intercepter 인 LoginCheck 가 가로채도록 설정했다

 

- 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");
			return false;
		}
		return true;
	}
}

- LoginCheck.java 는 Intercepter 관련 인터페이스를 구현한 클래스

- preHandle() 메소드안에서 만약 세션이 없다면 로그인 폼으로 보내는 역할을 하고 있다

 

- 이제 제대로 예제를 설명

 

인터셉터 (Intercepter) 사용 예제의 흐름

Intercepter 흐름

- 특정 URL 로 요청시 Dispatcher Servlet 이 Controller 를 호출하기 이전에 Intercepter 가 가로챈다

- 여기 예제에선 "upload.do" 로 요청시 Controller 로 가기 전에 가로채서 로그인이 되어있는지 되어있지 않는지 판별한다

- 로그인이 되어있지 않으면 비정상 접근이라고 생각해서 로그인 폼으로 보낸다

+ Intercepter 는 회원관리 프로그램에서만 사용함

 

인터셉터(Intercepter) 구현 방법 2가지

1. abstract class HandlerInterceptorAdapter 클래스를 상속 받아서 구현

2. interface HandlerInterceptor 인터페이스를 상속 받아서 구현

 

- 이후 그 클래스에서 아래의 3개의 메소드를 오버라이딩 해서 인터셉터 기능을 구현한다

+ 인터페이스를 상속받은 경우에는 반드시 3개를 모두 오버라이딩 해야함

- boolean preHandle()

- void postHandle()

- void afterCompletion()

 

- 인터셉터를 어떻게 구현했는지 LoginCheck.java 에서 보자

- 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");
			return false;
		}
		return true;
	}
}

- Intercepter 를 구현하는 2가지 방법 중 HandlerInterceptor 인터페이스를 상속받았다

- 인터페이스의 추상메소드 3개를 모두 오버라이딩 한다

- 이 메소드들은 호출되는 시점이 다르다 * 호출 시점 아래에서 설명

- 현재 예제는 이 중 preHandle() 메소드만 사용하고있고 나머지는 오버라이딩 만하고 비어있다

- preHandle() 에서 session 객체를 생성하고, session 값이 있는지 없는지 따진다

1) session 값이 없다면 sendRedirect() 로 로그인폼으로 가고 return false; 를 돌려준다

2) session 값이 있다면 return true; 를 하면 원래 가던 Controller 클래스로 돌아간다

 

Intercepter 관련 3개의 메소드가 호출되는 시점

- preHandle() 메소드 : Dispatcher Servlet 에서 Controller 로 가기전에 가로채서 자동으로 실행됨

- postHandle() 메소드 : Controller 클래스에서 Dispatcher Servlet 으로 가기 전에 가로채서 자동으로 실행됨

- afterCompletion() 메소드 : View 페이지에서 리턴할때 호출됨 (View 페이지 완성될때)

- 현재 예제는 이 중 preHandle() 메소드만 사용하고있고 나머지는 오버라이딩 만하고 비어있다

- preHandle() 에서 세션이 있는지 없는지 따져서, 없다면 로그인폼으로 보내는 역할을 한다

 

Intercepter 의 장점

- 로그인이 되어있는지(세션이 있는지) 각 페이지마다 신경쓸 필요가 없다

 

Inetercepter 환경 설정 방법

- servlet-context.xml부분

	<!-- Interceptor설정 -->	
	<beans:bean id="lc" class="com.ch.ch07.LoginCheck"/>
	<interceptors>
		<interceptor>
			<mapping path="/upload.do"/>
			<beans:ref bean="lc"/>
		</interceptor>
	</interceptors>

- bean 을 만들고, class 속성값으로는 패키지부터 Intercepter 클래스인 LoginCheck 클래스까지의 경로 설정

- "/upload.do" 로 요청이 들어올떄 매핑을 잡는다, 그럼 id가 "lc" 인 곳의 class 속성으로 설정된 클래스 LoginCheck 가 실행된다


로그인 시도시 흐름

- index.jsp 를 실행해보자

- index.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>
	<script type="text/javascript">
		location.href = "upload.do";
	</script>
</body>
</html>

1. "upload.do" 로 요청하므로 Intercepter 에 걸린다! (servlet-context.xml 에서 설정)

2. 그럼 Intercepter 클래스인 LoginCheck 클래스가 실행된다

3. 세션값이 없으므로 비정상적인 접근으로 인식해서 Intercepter 가 로그인 폼으로 보내준다

- session.getAttribute("id") 가 null 이므로 로그인 폼 loginform.do 로 요청

4. Controller 클래스에서 @RequestMapping 이 맞는 곳으로 간다, "loginForm" 으로 간다

5. loginForm.jsp 가 실행됨

- loginForm.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">
		<form action="login.do">
			<h2 class="text-primary">로그인</h2>
			<c:if test="${not empty msg }">
				<span class="err">${msg }</span>
			</c:if>
			<table class="table table-bordered">
				<tr>
					<th>아이디</th>
					<td><input type="text" name="id" required="required"></td>
				</tr>
				<tr>
					<th>암호</th>
					<td><input type="password" name="pass" required="required"></td>
				</tr>
				<tr>
					<th colspan="2"><input type="submit" value="확인"></th>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

+ header.jsp 가 include 되어있다

- header.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<c:set var="path" value="${pageContext.request.contextPath }" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="${path}/css/bootstrap.min.css" rel="stylesheet">
<script src="${path}/js/jquery.js"></script>
<script src="${path}/js/bootstrap.min..js"></script>
<style>
.err {
	color: red;
	font-size: 20px;
}
</style>

- core 라이브러리 set 태그로 path 변수에 현재 프로젝트명을 저장했다

- ${pageContext.request.contextPath} 는 현재 프로젝트명을 가져옴, 그걸 변수 path 에 저장

- 링크로 부트스트랩, jQuery 파일들을 불러올때 앞에 ${pat} 를 붙여서 부트스트랩, jQuery 파일들의 절대경로를 구해오고 있음

- 아이디, 비번이 틀렸을때 .err 부분을 loginForm.jsp 에서 불러옴

 

6. loginForm.jsp 로그인폼에서 아이디, 비번 입력 후 로그인 시도

- "login.do" 요청되어 Controller 로 간다

UploadController.java 부분

+ 이때 loginForm.jsp 폼에서 넘어온 값이 바로 String id, String pass 에 저장된다

+ 즉 앞에 @RequestParam 이 생략되어있음

1) 아이디, 비번을 맞게 입력하여 로그인에 성공하면 id 를 세션설정하고 loginSuccess.jsp 로 간다

2) 로그인에 실패하면 loginForm.jsp 로 돌아오고 이떄 아래의 if 태그에서 msg 가 비어있지 않게되므로 if 조건을 만족하여 "똑바로 입력해" 메세지를 출력함

loginForm.jsp 부분

- Controller 로 갔다왔을때만 msg 가 비어있지 않게되어서 메세지를 출력함

 

7. 로그인 성공시 loginSuccess.jsp 로 이동

+ 실패시엔 위에작성, loginForm.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" 로 요청하므로 Intercepter 에 걸린다

- 인터셉터 클래스 LoginCheck 이 실행됨, preHandle() 메소드가 실행됨

- 지금은 세션값이 있는 상태이므로 null 값이 아니다, return true; 해서 원래 가려고 했던 Controller 쪽으로 찾아간다

 

8. 현재는 loginSuccess.jsp 에서 Get 방식으로 요청했으므로 return "upload" 에 의해 upload.jsp 로 간다

- 위로 찾아가서 upload.jsp 로 이동

 


index.jsp 실행 캡처

- 아이디는 "java" 비번은 "1234" 로 입력해야 로그인 성공이다

- 틀린 비번 입력시

 

- 맞는 비번 입력시

Spring / Springboot

- Model 2 를 빠르게 개발하기 위해 사용하는 프레임워크

 

Spring

- Maven 으로 라이브러리를 관리 (선택불가)

- 그래서 구조가 Maven 프로젝트와 비슷

- EL 과 JSTL 로 결과를 출력

 

Springboot

- Maven 또는 Gradle 로 라이브러리 관리 가능

- EL, JSTL, 타임리프로 결과를 출력

 

+ 타임리프

- 타임리프 사용시 View 페이지가 html 문서가 되어야함, 기존 JSP 태그를 사용하지 못하게됨


Spring Project (Web Application) 구조

 

Spring Project import / export 할때

- Maven, Spring 프로젝트를 import / export 할때는 war 파일이 아닌 zip 파일을 압축해제해서 import, 복붙해서 output

- 서버에 최종적으로 배포할때는 war 파일로 

 

환경설정 파일 정리

1. Spring 환경설정 파일

- servlet-context.xml

- root-context.xml

 

2. 프로젝트 환경설정 파일

- web.xml

 

3. Maven 환경설정 파일

- pom.xml

 

web.xml 에 들어가는 주요 3가지 내용

1. DispatcherServlet 위치 설정 : <servlet-class> 태그 안에 있다

2. 환경설정 파일 2개 불러오기 : root-context.xml 과 servlet-context.xml 파일

3. 한글 인코딩 (아직 안들어가있다)

 

root-context.xml

- DB 연동 관련 내용

- 주로 bean 을 만들어서 DB 접속을 처리한다

- 여기서 Setter DI 도 사용함

- beans 루트 엘리먼트 안에 bean 을 추가해서 사용

- 어노테이션 방식 + 직접 bean 객체 생성 방식 둘다 사용함

 

servlet-context.xml

- View 파일들이 저장된 최상위 디렉토리 위치를 잡음, 그래야 View 파일로 찾아갈 수 있다

- ViewResolve (prefix, suffix)

- ViewResolve 에도 Setter DI 사용함

- 어노테이션 기반으로 처리됨

 

DI
- 환경설정 파일에서 주로 사용한다 (MyBatis 환경설정 파일, Spring 환경설정 파일)

- Setter DI 를 주로 사용함

 

DI 에서 주의

- 할당(주입) 할때 다른 bean 의 id 를 사용할때는 value 속성 대신 ref 속성 사용

- 메인 메소드에서 resources 폴더에 저장된 환경설정 파일을 불러올때는 classpath: 를 붙이기 

 

+ SqlSession 와 객체 생성

- SqlSession 은 부모 인터페이스

- SqlSessionTemplate 는 SqlSession 을 상속하는 구현 클래스

- SqlSession 은 객체 생성할 수 없으므로 SqlSessionTemplate 으로 bean 객체를 만들고 업캐스팅 해야함

 

+ Anotaion 을 이용한 DI

- 나중엔 어노테이션 기반으로 바뀐다

- bean 객체를 만들지 않고 처리 가능


Spring DI 예제 (이어서)

Spring DI 예제 10 (sample 9 와 비슷)

- src/main/java/sample10/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- Vehicle.java : 부모 인터페이스, 추상메소드 rider()

- VehicleImpl.java : Vehicle 인터페이스를 상속하는 구현 클래스, rider() 메소드를 오버라이딩함, rider() 에서 매개변수로 전달된거 출력

- Outputer.java : 인터페이스, 추상메소드 output()

- FileOutputer.java : Output 인터페이스를 상속하는 구현 클래스, output() 메소드를 오버라이딩함

- 이 구현클래스 2개로 bean 객체 따로 만듬

 

- VehicleImpl.java

package sample10;

public class VehicleImpl implements Vehicle {
	private String name;
	private String rider;
	private Outputer out;

	public void setName(String name) {
		this.name = name;					// name="대박이"
	}

	public void setRider(String rider) {
		this.rider = rider;					// rider="비행기"
	}

	public void setOut(Outputer out) {
		this.out = out;						// out=out
	}

	public void rider() {
		String msg = name + "(이)가 " + rider + "을(를) 탄다";
		System.out.println(msg);
		out.output(msg);
	}
}

- 필드, setter 메소드, 메소드 로 구성되어있다.

- 프로퍼티에 값을 할당하는 방법 중 Setter 메소드를 사용할 것

- 인터페이스 Outputer 객체 output 의 setter 메소드 setOutput() , 매개변수는 인터페이스 Outputer 형

- setOut() 의 매개변수 자료형은 부모 인터페이스인 Output 이고, 여기 매개변수에 주입되는 값은 bean10.xml 에서 만들어진 자식인 FileOutputer 객체가 주입됨 (업캐스팅)

+ 인터페이스로는 객체 생성을 못하므로 자식 구현 클래스로 객체를 생성해서 업캐스팅 해야함

- 그럼 프로퍼티 out 은 FileOutpter 객체를 받은 것임

- Vehicle의 rider() 메소드를 여기서 오버라이딩

- rider() 안에서 초기화된 필드값을 출력하고, FileOutputer 객체 output 으로 FileOupter 클래스에서 오버라이딩된 메소드 output() 호출하자, 그럼 output() 에서는 파일을 생성해준다

* 잘 이해가 안되면 아래를 읽고 다시 여기 돌아와서 읽어보기

 

- Outputer.java

package sample10;

public interface Outputer {
	void output(String msg);
}

 

- FileOutputer.java

package sample10;

import java.io.FileWriter;
import java.io.IOException;


public class FileOutputter implements Outputer {
	private String fileName;

	public void setFileName(String fileName) {
		this.fileName = fileName;			// fileName="aa.txt"
	}

	public void output(String msg) {
		try {
			FileWriter fw = new FileWriter(fileName);
			fw.write(msg);
			fw.close();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}

- 부모 인터페이스 Output 을 상속

- output() 메소드를 메소드 오버라이딩 한다, 파일을 생성하고 매개변수로 받은 msg 를 파일에 쓰고 있음

- FileOutputer 로 bean 객체를 생성하고 그 필드 fileName 에 Setter 메소드인 setFileName() 으로 Setter DI 할 것

- 그리고 그 fileName 으로 파일명을 설정해서 FileWriter 객체 fw 를 생성

 

- Ex01.java

package sample10;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample10/beans10.xml");
		Vehicle vh = (Vehicle) ac.getBean("vh");
		vh.rider();
	}
}

 

- beans10.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">
	<bean id="vh" class="sample10.VehicleImpl">
		<property name="name" value="대박이"></property>
		<property name="rider" value="비행기"></property>
		<property name="out" ref="out"></property>
	</bean>
	<bean id="out" class="sample10.FileOutputter">
		<property name="fileName" value="aa.txt"></property>
	</bean>
</beans>

- 2개의 bean 객체를 생성하고 있다

 

첫번째 객체 생성 (beans01.xml 부분)

	<bean id="vh" class="sample10.VehicleImpl">
		<property name="name" value="대박이"></property>
		<property name="rider" value="비행기"></property>
		<property name="out" ref="out"></property>
	</bean>

- VehicleImpl 클래스로 bean 객체를 생성하고 프로퍼티 name, rider, out 에 Setter 메소드로 값 초기화 (= Setter DI)

- VehicleImple 객체 vh 의 프로퍼티 name 에 "대박이", rider 에 "비행기", out 에 아래에서 생성한 두번째 객체인 FileOutputter 객체 out 을 주입

- 프로퍼티 out 의 자료형은 Outputer 인터페이스, 주입되는 값은 FileOutputter 객체, 즉 업캐스팅

ex) Outputer out = new FileOutputter() 와 같다

 

- 위에서 이렇게 out 객체를 프로퍼티 out 에 주입해야만 VehicleImpl 객체 vh 에서 VehicleImpl 클래스의 오버라이딩된 메소드 rider() 를 호출하면서, 그 rider() 메소드 안에서 프로퍼티이자 FileOutputter 로 만든 객체 out 으로 오버라이딩 된 메소드 output() 호출 가능 

Ex01.java 에서 VehicleImpl 로 만든 객체 vh 로 메소드 오버라이딩된 rider() 호출
VehicleImpl.java 에서 rider() 를 메소드 오버라이딩 하는 코드


두번째 객체 생성 (beans01.xml 부분)

	<bean id="out" class="sample10.FileOutputter">
		<property name="fileName" value="aa.txt"></property>
	</bean>

- 힙메모리에 새 공간을 생성해서 FileOutputter 객체 out 을 생성한다

- FileOutputter 클래스로 객체 out 을 생성하고 프로퍼티 fileName 에 "aa.txt" 를 Setter DI 로 주입

- 객체 생성 및 주입되는 시점 : beans10.xml 을 메인 메소드에서 읽어올때 메모리에서 자동으로 모든 객체 생성, 모든 주입이 일어남

 

- Ex01.java 실행시

- 파일 aa.txt 가 생성되었고 원하는 메세지가 작성되었음


Spring DI 예제 11 (DAO, DTO, Service 등도 있다)

- src/main/java/sample11/ 안의 파일들

- 지금까지의 예제와 다른 형태, DAO, DTO, Service 등도 있음

 

Spring DI 예제 11 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- service 와 dao 폴더에는 인터페이스 하나, 구현 클래스 하나 씩 있다

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다

- dao/ProductDao.java : 인터페이스, 추상메소드 getProduct()

- dao/ProductDaoImpl.java : ProductDao 를 상속하는 구현클래스, 추상메소드 getProduct() 를 오버라이딩

- service/ProductService.java : 인터페이스, 추상메소드 getProduct()

- service/ProductServiceImpl.java : ProductService 를 상속하는 구현클래스, 추상메소드 getProduct() 를 오버라이딩

- model/Product.java : DTO 클래스, 상품이름 name 필드, 상품가격 price 필드, setter / getter 메소드가 있다, 값을 저장하고 돌려줄때 사용

 

main() -> Service -> DAO 로 가는 방법

- 먼저 Spring 환경설정 파일 beans11.xml 에서 구현 클래스 두개 ProductServiceImpl, ProductDaoImpl 로 bean 를 만들 것

- main() 메소드에서 getBean 으로 Service 클래스 객체를 가져오고, 그걸로 Service 객체의 메소드 사용 가능

- Service 클래스에서는 DAO 객체를 가져와서, 그걸로 DAO 객체의 메소드 사용 가능

 

- main() (Controller)에서는 Service 클래스 객체를 구해와야만 Service 의 메소드를 호출 가능

- Service 클래스 객체는 DAO 객체를 구해와야만 DAO 의 메소드를 호출 가능

- 구해온다 = 프로퍼티 자료형이 DAO

 

- beans11.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">
	<bean id="pd" class="sample11.dao.ProductDaoImpl"></bean>
	<bean id="ps" class="sample11.service.ProductServiceImpl">
		<property name="pd" ref="pd"></property>
	</bean>
</beans>

<첫번째 객체>

- ProductDaoImpl 클래스(DAO 클래스)로 객체 pd 생성

- 이 객체는 아래에서 참조할 것

<두번째 객체>

- 이 객체는 main() 에서 getbean("ps") 로 가져올 것

- ProductServiceImpl 클래스(Service 클래스)로 객체 ps 생성

- ProductServiceImpl 클래스의 프로퍼티에 주입을 해야하는데, 그 프로퍼티는 ProductDao 자료형이므로 ProductDaoImpl 클래스로 생성된 객체가 주입되어야함, 그래서 위에서 만든 ProductDaoImpl 객체 pd 를 ref 로 주입함

 

- ProductServiceImpl.java

package sample11.service;

import sample11.dao.ProductDao;
import sample11.model.Product;

public class ProductServiceImpl implements ProductService {
	private ProductDao pd;

	public void setPd(ProductDao pd) {
		this.pd = pd;
	}

	public Product getProduct() {
		return pd.getProduct("라면");
	}
}

- 프로퍼티 ProductDao pd 가 있다

- 이 Service 클래스로 객체를 생성과 주입을 할때 프로퍼티 pd 에는 객체가 들어가야하므로 ref 로 DAO 구현클래스 객체를 넘겨줘야한다 

- 이 프로퍼티 자료형을 인터페이스 ProductDao 로 해도 되고, 구현클래스 ProductDaoImpl 로 해도 된다

- 여기선 인터페이스 ProductDao 가 프로퍼티의 자료형이므로 ProductDaoImpl 으로 객체를 만들어서 업캐스팅 해야함

- beans11.xml 에서 이 프로퍼티이자 DAO 객체 pd 에 주입(SetPd() Setter DI 로) 을 할 것 * 위로 가서 beans11.xml 보자

 

- ProductDaoImpl.java

package sample11.dao;

import sample11.model.Product;

public class ProductDaoImpl implements ProductDao {
	public Product getProduct(String name) {
		return new Product(name, 2000);
	}
}

 

- Ex01.java

package sample11;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import sample11.model.Product;
import sample11.service.ProductService;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new GenericXmlApplicationContext("/sample11/beans11.xml");
		ProductService ps = (ProductService) ac.getBean("ps");
		Product product = ps.getProduct();
		System.out.println(product); // println(product.toString()) 와 같다
	}
}

- Web Application 에서는 Controller 클래스가 이 main() 메소드 역할을 한다

- 같은 패키지 안에 beans11.xml 이 있으므로 패키지명부터 해당 파일명까지 써준다, 읽어올때 객체생성되고 DI 까지 완료됨

- main() -> Service 로 넘어간다는 의미 = main() 에서 Service 클래스의 메소드를 호출한다

- main() -> Service 로 넘어가야하므로 beans11.xml 에서 만들어진 Service 객체인 "ps" 를 구해온다

- Service 객체를 구해왔으므로 Service 클래스에서 오버라이딩된 메소드인 getProduct() 호출 가능


전체 흐름 (중요)

main() -> Service

- main() -> Service 로 넘어간다는 의미 = main() 에서 Service 클래스의 메소드를 호출한다

- main() (Controller)에서는 Service 클래스 객체를 구해와야만 Service 의 메소드 getProduct()를 호출 가능

+ 이때 main() 에서는 Service 객체를 getbean("ps") 로 객체 ps를 구해온다

- Service 객체를 구해왔으므로 Service 클래스에서 오버라이딩된 메소드인 getProduct() 호출 가능

Service -> DAO

- Service  -> DAO로 넘어간다는 의미 = DAO 에서 DAO클래스의 메소드를 호출한다

- Service 클래스의 오버라이딩한 getProduct() 안에서는 DAO 의 getProduct() 를 호출하고 있다, 호출하면서 매개변수 "라면" 전달

- 이때 Service 클래스 객체는 DAO 객체를 구해와야만 DAO 의 메소드 getProduct() 를 호출 가능

- 구해온다 = 프로퍼티 자료형이 DAO

- 이미 main() 에서 beans11.xml 을 읽어올때 Service 객체가 생성되며 프로퍼티인 DAO 객체도 구해졌으므로 바로 DAO의 getProduct() 호출 가능

DAO

- DAO 클래스의 getProduct() 에 매개변수로 "라면" 이 전달되어 왔다

- 현재는 DB연동 안되었으므로 그냥 돌아간다, 돌아갈때 DTO 객체를 생성하고, 매개변수로 받은 "라면" 과 2000 을 저장한 DTO 객체를 Service 로 돌려줌

+ DTO 객체

- 프로퍼티 name , 프로퍼티 price

- 생성자는 public Product(String name, int price)

- 프로퍼티 name, price를 결합해서 리턴해주는 메소드 toString() 이 있다

DAO -> Service (돌아옴)

- DAO 로 부터 돌려받은 것(DTO 객체)을 그대로 main() 으로 리턴함

Service -> main() (돌아옴)

- Service 로 부터 돌려받은 결과(DTO 객체)를 출력함


Spring DI 예제 12 (DAO, DTO, Service 등도 있다, sample 11와 비슷)

- src/main/java/sample12/ 안의 파일들

-  DAO, DTO, Service 가 있다, sample11 과 비슷

 

Spring DI 예제 12 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- service 와 dao 폴더에는 인터페이스 하나, 구현 클래스 하나 씩 있다

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다

- dao/BookDao.java : 인터페이스, 추상메소드 getBook()

- dao/BookDaoImpl.java : BookDao를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- service/BookService.java : 인터페이스, 추상메소드 getBook()

- service/BookServiceImpl.java : BookService를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- model/Book.java : DTO 클래스, 책제목 title 필드, 가격 price 필드, setter / getter 메소드가 있다, 값을 저장하고 돌려줄때 사용

 

main() -> Service -> DAO 로 가는 방법

- 먼저 Spring 환경설정 파일 beans11.xml 에서 구현 클래스 두개 ProductServiceImpl, ProductDaoImpl 로 bean 를 만들 것

- main() 메소드에서 getBean 으로 Service 클래스 객체를 가져오고, 그걸로 Service 객체의 메소드 사용 가능

- Service 클래스에서는 DAO 객체를 가져와서, 그걸로 DAO 객체의 메소드 사용 가능

 

main() vs Service 에서 객체를 구하는 방법

- main() 에서 Service 객체를 구할때는 beans12.xml 을 읽어서 getbean() 으로 가져옴

- Service 클래스에서 DAO 객체를 구할때는 Service 클래스의 프로퍼티의 자료형을 DAO 로해서 그 프로퍼티로 DAO 객체를 구함

+ main() 에서 beans12.xml 을 읽을때 이미 Service 객체, DAO 객체 다 생성되고 할당되었다 -> 즉 Service 클래스에서 DAO 객체를 프로퍼티로 이미 자동으로 주입받았다

 

+ toString()

- toString() 은 Object 클래스의 메소드이고, toString() 을 메소드 오버라이딩해서 사용

+ 모든 클래스는 Object 클래스를 상속받는다

- DTO 리턴시 자동으로 toString() 이 실행되어 리턴된 값이 실행됨

 

- Ex01.java

package sample12;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sample12.model.Book;
import sample12.service.BookService;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample12/beans12.xml");
		BookService bs = (BookService) ac.getBean("bs");
		Book book = bs.getBook();
		System.out.println(book);
	}
}

+ 현재는 Application 이므로 main() 에서 beans12.xml 를 불러와야만 beans12.xml 가 실행된다

+ Web Application 으로 넘어가면 web.xml 이 실행될때 환경설정 파일을 불러오면서 beans12.xml 가 실행된다

- main() -> Service 로 가기 위해 beans12.xml 에서 Service 객체 bs 를 구해와서 Service 의 getBook() 호출 

 

- beans12.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">
	
	<bean id="bd" class="sample12.dao.BookDaoImpl"></bean>
	<bean id="bs" class="sample12.service.BookSericeImpl">
		<property name="bd" ref="bd"></property>
	</bean>
	
</beans>

<첫번째 객체>

- DAO 클래스로 객체 "bd" 를 생성

<두번째 객체>

- Service 클래스로 객체 "bs" 를 생성

- 객체 bs 를 생성하고, 자료형이 BookDao 인 프로퍼티 bd 에 값을 Setter DI 로 할당하는데, 이때 위에서 생성된 BookDaoImpl 객체 bd 를 할당함

- 프로퍼티 자료형은 인터페이스 BookDao , 주입되는 값은 구현클래스 BookDaoImpl, 즉 업캐스팅으로 주입

 

- BookServiceImpl.java

package sample12.service;

import sample12.dao.BookDao;
import sample12.model.Book;

public class BookSericeImpl implements BookService {
	private BookDao bd;

	// BookDao bd = new BookDaoImpl()
	public void setBd(BookDao bd) {
		this.bd = bd;
	}

	public Book getBook() {
		return bd.getBook("대박인생");
	}
}

- main() 에서 beans12.xml 을 불러올때 이미 메모리에 BookServiceImpl 객체 생성되고, 프로퍼티 bd 에도 값이 주입되었음, DAO 의 클래스 사용 가능해졌다

- main() 에서 Service 의 getBook() 을 호출했다, 여기서는 DAO 로 가기위해 DAO 객체 bd 로 bd.getBook("대박인생") 호출

 

- BookDaoImpl.java

package sample12.dao;

import sample12.model.Book;

public class BookDaoImpl implements BookDao {
	public Book getBook(String title) { //  title="대박인생"
		return new Book(title, 20000);
	}
}

- Service 클래스에서 이 DAO 의 getBook() 을 호출했다, "대박인생" 이 매개변수로 넘어옴

- 여기서 DTO 객체를 생성하고, DTO 객체 프로퍼티 title 에 "대박인생" , price에 20000 을 저장해서 돌려줌

 

Book.java (DTO)

package sample12.model;

public class Book {
	private String title;
	private int price;

	public Book(String title, int price) {
		this.title = title;			// title="대박인생"
		this.price = price;         // price=20000
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	public String toString() {
		return "책[제목:" + title + ", 가격:" + price + "]";
	}
}

+ 값을 리턴할때, 전달할때 DTO 객체 생성

- Object toString() 을 오버라이딩해서 저장된 값들을 한꺼번에 돌려주려한다

- toString() 은 DTO 객체가 리턴될때 자동으로 실행됨, 실행되면 값들을 리턴해줌

 

- 이제 DAO -> Service 로 돌아가야한다

- BookServiceImpl.java (중복)

package sample12.service;

import sample12.dao.BookDao;
import sample12.model.Book;

public class BookSericeImpl implements BookService {
	private BookDao bd;

	// BookDao bd = new BookDaoImpl()
	public void setBd(BookDao bd) {
		this.bd = bd;
	}

	public Book getBook() {
		return bd.getBook("대박인생");
	}
}

- 돌려받은 값을 그대로 main() 으로 리턴함

 

- Ex01.java (중복)

package sample12;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sample12.model.Book;
import sample12.service.BookService;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample12/beans12.xml");
		BookService bs = (BookService) ac.getBean("bs");
		Book book = bs.getBook();
		System.out.println(book);
	}
}

 

- Service 의 getBook() 으로부터 결과를 바당옴

<toString()>
- toString() 은 DTO 객체가 리턴될때 자동으로 실행됨, 그래서 객체 book 을 출력해도 book.toString() 과 같은 결과가 나옴

+ 만약 DTO 객체에 toString() 을 오버라이딩하지 않았으면, System.out.println(book) 할때 주소값이 나옴

- DTO 의 getter 메소드로 개별적인 값을 불러올 수도 있다

 


어노테이션을 이용한 DI

- 어노테이션 기반으로 처리시 Spring 환경설정 파일에 bean 객체 생성하지 않음

 

- Sample13 예제를 본격적으로 하기전에 Simple13 을 간략하게 보면서 어노테이션을 이용한 DI 가 뭔지 보자

- beans13.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
<!-- 
	1. sample13 패키지 하위 클래스를 스캔한다는 의미를 가진다.
    2. @Component, @Controller, @Service, @Repository 어노테이션이
             붙어있는 클래스는 @Autowired 어노테이션을 이용해서 필요한 빈 객체를  
       setter 메소드 없이 주입을 받는다.
 -->	
	
	<context:component-scan base-package="sample13"/>

</beans>

- bean 을 더이상 만들고 있지 않고 단 한줄만 있다, 이게 어노테이션 기반

- base-package 는 베이스가 되는, 기본이 되는 패키지를 의미함

- 저 한줄은 sample13 패키지 하위의 모든 클래스를 모두 읽어오라는 의미

- 지정된 패키지 sample13 하위의 클래스들에는 클래스명 위에 4가지 어노테이션 중 하나가 붙어있어야함

- @Component, @Controller, @Service, @Repository

 

어노테이션 4가지

- @Component : 아무 클래스에나 붙일 수 있는 어노테이션

- @Controller : 컨트롤러 클래스 위에 붙이는 어노테이션

- @Service : Service 클래스 위에 붙이는 어노테이션

- @Repository : Repository 클래스 위에 붙이는 어노테이션

- 클래스명 위에 이 4개의 어노테이션 중 한가지만 붙어있어도 된다

- sample13 에서는 모두 @Component 를 붙이지만, 나중엔 클래스에 맞는 어노테이션을 붙인다

ex) Service 클래스 위에 @Service 어노테이션

 

- 이후 Setter 메소드가 없어도 생성이 필요한 객체 위해 @Autowired 어노테이션을 붙이면 main() 에서 beans13.xml을 읽어올때 필요한 bean 객체가 생성 및 주입됨

 

필요한 bean 객체를 주입하기 위한 조건

1. base-package 에 적힌 해당 패키지의 모든 클래스를 읽어오는 코드가 있어야함

2. 어노테이션 4개 중 하나 붙어있어야함

3. 만들고자 하는 객체 위에 @Autowired 어노테이션을 붙여야함

 

ex) Service -> DAO 로 갈때

- Service 에서 DAO 객체를 구해와야하는데, 이때 Setter 메소드 필요없고, bean 객체를 직접 생성하지 않아도 된다

- 자동으로 DAO bean 객체를 생성해줌

- 조건에 맞는지 보자

1) base-package 에 sample13 패키지가 적혀있다

2) Service 클래스 위에 @Component 어노테이션이 붙어있다

3) 필요한 객체인 DAO 객체(Service의 프로퍼티) 위에 @Autowired 어노테이션이 붙어있다

- 조건이 맞으므로 Service 클래스에 Setter 메소드가 없어도 Service 클래스에 DAO 객체가 생성되어서 프로퍼티에 값(객체)이 주입됨, 이때 객체 생성 및 주입 시점은 main() 에서 beans13.xml 을 읽어왔을때이다.

- 즉 아래의 빨간선으로 표시한 내용이 자동으로 실행된다, 실행되는 시점은 main() 에서 beans13.xml 을 읽어왔을때

- sample11.xml 부분

 

+ 현재는 DAO 에서 DB 연동안하므로 DAO 에서 객체 생성하진 않음, 나중엔 SqlSession 객체를 생성

 


Spring DI 예제 13 (어노테이션 기반)

- src/main/java/sample13/ 안의 파일들

- 더이상 bean 을 만들지 않고, 어노테이션 기반으로 처리함

 

Spring DI 예제 13 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다

- productDao.java : 인터페이스, 추상메소드 getProduct()

- ProductDaoImpl.java : productDao를 상속하는 구현클래스, 추상메소드 getProduct() 를 오버라이딩

- ProductService.java : 인터페이스, 추상메소드 getProduct()

- ProductServiceImpl.java : ProductService를 상속하는 구현클래스, 추상메소드 getProduct() 를 오버라이딩

- Prodjct.java : DTO 클래스, 값을 저장하고 돌려줄때 사용

 

어노테이션

- Service 구현 클래스와 DAO 구현 클래스에만 어노테이션이 붙어있다

- Service 구현 클래스, DAO 구현 클래스위에 @Component 어노테이션이 붙어있음

 

문제

- 흐름은 main() -> Service -> DAO -> Service -> main()

- Service -> DAO 로 갈때는 @Autowired 로 DAO 객체를 구해온다

- 그럼 main() -> Service 로 갈때는 bean 객체가 없는데 어떻게 getbean() 으로 Service 객체를 구해올까?

 

- Ex01.java

package sample13;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample13/beans13.xml");
		ProductService ps = ac.getBean(ProductService.class);
		Product product = ps.getProduct();
		System.out.println(product);
	}
}

- 먼저 beans13.xml 을 불러온다

- getBean() 안에 bena 이 없고, Service 클래스명을 직접 구해와야한다 (인터페이스명도 되고 구현클래스명도 됨)

- 이러면 Service 객체를 구해와서 ps 로 받고, Service 클래스 안의 메소드 getProduct() 를 호출

 

- ProductServiceImpl.java

package sample13;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class ProductServiceImpl implements ProductService {
	@Autowired
	private ProductDao pd;

	public Product getProduct() {
		return pd.getProduct("짜장면");
	}
}

- beans13.xml 을 main() 에서 불러올때 이미 DAO 객체 pd 에 주입이 되었다

- 여기 SetterPd() 메소드가 없어도 주입됨!

- 객체 pd 가 주입되어있으므로 pd.getProduct() 로 DAO 클래스의 getProduct("짜장면") 호출

 

- ProductDaoImpl.java

package sample13;

import org.springframework.stereotype.Component;

@Component
public class ProductDaoImpl implements ProductDao {
	public Product getProduct(String name) {//name="짜장면"
		return new Product(name, 2500);
	}
}

- DTO 객체를 생성하고 매개변수로 넘어온 "짜장면", 2500 을 객체에 저장해서 그 객체를 리턴

 

- 다시 Service 로 돌아가야한다

- ProductServiceImpl.java (중복)

package sample13;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class ProductServiceImpl implements ProductService {
	@Autowired
	private ProductDao pd;

	public Product getProduct() {
		return pd.getProduct("짜장면");
	}
}

- 받은 DTO 객체를 그대로 main() 으로 돌려줌

 

- Ex01.java (중복)

package sample13;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample13/beans13.xml");
		ProductService ps = ac.getBean(ProductService.class);
		Product product = ps.getProduct();
		System.out.println(product);
	}
}

- DTO 객체를 돌려받음

- toString() 은 써도 안써도 자동 실행됨


 

나중에 할 예제의 흐름

- Controller 클래스에서 Service 객체를 생성할때 @Autowired 사용해서 생성

- Controller -> Service로 넘어가는 방법 = Service 객체 생성


Web Application 에서의 어노테이션

- 잠시 Web Application 인 springtest 프로젝트를 보자

 

어노테이션으로 Controller -> Service 로 가는 방법

- 조건에 맞는지 보자

1) servlet-context.xml 에서 base-package 를 설정하고 있다

- com.myhome.springtest 란 패키지 안의 모든 클래스를 읽어옴, 그 안에는 Controller 클래스인 HomeController.java

- Spring Project 생성시 입력헀던 top-level 패키지이다, 이 패키지 com.myhome.springtest 는 src/main/java 폴더 하위에 생성된다

2) HomeController 클래스 위에 @Controller 어노테이션이 있다

3) Controller 클래스 HomeController 클래스 안에서 Service 객체 생성이 필요한 곳에서 @Autowired 를 붙임

 

- 1,2,3 조건 3개를 모두 만족하므로 Controller 에서 Service 객체를 생성할때 자동으로 생성해서 Controller 클래스의 프로퍼티에 주입해준다

 

Spring 환경설정 파일과 어노테이션

- Web Application 에서 Spring 의 환경설정 파일 중 root-context.xml 은 어노테이션 뿐 아니라 기존 처럼 bean 태그로 객체 생성하는 방법도 사용한다

- Web Application 에서 Spring 의 환경설정 파일 중 servlet-context.xml 은 어노테이션 기반이다, base-package 가 지정되어있음, 


 

Spring DI 예제 14 (어노테이션 기반, Sample13 과 비슷)

- src/main/java/sample14/ 안의 파일들

- 더이상 bean 을 만들지 않고, 어노테이션 기반으로 처리함

 

Spring DI 예제 14 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다

- BookDao.java : 인터페이스, 추상메소드 getBook()

- BookDaoImpl.java : BookDao를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- BookService.java : 인터페이스, 추상메소드 getBook()

- BookServiceImpl.java : BookService를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- Book.java : DTO 클래스, 값을 저장하고 돌려줄때 사용

 

예제 sample14 / sample15 의 흐름

- 현재는 Controller 클래스가 없으므로 main() 에서 그 역할을 한다

- main() 에서 Service 객체를 생성하는 것이 아니라 그냥 불러왔음

 

어노테이션

- Service 구현 클래스와 DAO 구현 클래스에만 어노테이션이 붙어있다

- Service 구현 클래스, DAO 구현 클래스위에 @Component 어노테이션이 붙어있음

 

- beans14.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
	<context:component-scan base-package="sample14"/>

</beans>

- 저 한줄이 첫번째 조건이다

 

- Ex01.java

package sample14;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new GenericXmlApplicationContext("/sample14/beans14.xml");
		BookService bs = ac.getBean(BookService.class);
		Book book = bs.getBook();
		System.out.println(book);
	}
}

- 지금은 Controller 클래스가 아니라 main 메소드이므로 어노테이션을 안쓰고 있음

- bean 을 만들지 않았으므로 Service 객체가 없다

- 그래서 getBean(BookService.class) 로 Service 클래스 객체를 구해온다

 

- BookSeviceImpl.java

package sample14;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class BookServiceImpl implements BookService {
	@Autowired
	private BookDao bd;

	public Book getBook() {
		return bd.getBook("바람과 함께 사라지다");
	}
}

- Service 클래스 위에 @Component 어노테이션

- DAO 객체를 @Autowired 로 프로퍼티 bd 에 주입

- DAO 객체를 구해왔으므로 DAO 의 getBook() 메소드 호출, 매개변수로 "바람과 함께 사라지다" 를 준다

 

- BookDaoImpl.java

package sample14;

import org.springframework.stereotype.Component;

@Component
public class BookDaoImpl implements BookDao {
	public Book getBook(String title) {
		return new Book(title, 25000);
	}
}

- 매개변수로 전달된 타이틀값과 가격으로 DTO Book 객체를 생성해서 객체를(주솟값을) 리턴

- DAO 클래스 위에 @Componet 어노테이션

 

- 다시 돌아가는 과정은 리턴만 하면 되므로 생략

 


Spring DI 예제 15 (어노테이션 기반, Sample14 과 비슷)

- src/main/java/sample15/ 안의 파일들

- 더이상 bean 을 만들지 않고, 어노테이션 기반으로 처리함

- 이제 어노테이션 @Componet 대신 Service 클래스 위에 @Service, DAO 클래스 위에 @Repository

 

Spring DI 예제 15 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다

- BookDao.java : 인터페이스, 추상메소드 getBook()

- BookDaoImpl.java : BookDao를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- BookService.java : 인터페이스, 추상메소드 getBook()

- BookServiceImpl.java : BookService를 상속하는 구현클래스, 추상메소드 getBook() 를 오버라이딩

- Book.java : DTO 클래스, 값을 저장하고 돌려줄때 사용

 

어노테이션

- 이제 어노테이션 @Componet 대신 Service 클래스 위에 @Service, DAO 클래스 위에 @Repository

- 인터페이스 위에는 어노테이션 쓰지 않음, 구현 클래스 위에만 어노테이션 쓴다

 

클래스와 해당 어노테이션 관계

- Controller 클래스 위엔 @Controller

- DAO 클래스 위엔 @Repository

- Service 클래스 위엔 @Service

- @Component 로 해도 동작은 한다

 

예제 sample14 / sample15 의 흐름

- 현재는 Controller 클래스가 없으므로 main() 에서 그 역할을 한다


+ 나중에 Web Application 할때 할 예제의 흐름 (중복, 중요)

Controller -> Service 로 가는 법

- Controller 클래스에서 Service 객체를 생성할때 @Autowired 사용해서 생성

- Controller -> Service로 넘어가는 방법 = Service 객체 생성, 이때 주입할 프로퍼티 위에 @Autowired 를 써준다

 

1) servlet-context.xml 에서 top-level 패키지안의 모든 클래스 읽기, 즉 Conroller 클래스를 읽어옴

2) Controller 클래스에서 클래스명 위에 @Controller 붙이기

3) Service 객체를 생성해서 주입할 프로퍼티 위에 @Autowired 를 써준다

 

- 이 조건을 모두 만족시켜서 Controller 클래스에서 Service 클래스의 객체를 만들 수 있다

- Controlelr 클래스에 Setter 메소드가 없어도 만들고 주입 가능함

 

+ 지금 예제는 일반 Application 이므로 servlet-context.xml 에서 Controller 패키지를 읽어오는 작업을 하지도 않았고 Controller 패키지가 없으므로 main() 에서 Service 객체를 .class 로 불러와서 main() -> Service 로 이동


- Ex01.java

package sample15;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new GenericXmlApplicationContext("/sample15/beans15.xml");
		BookService bs = ac.getBean(BookService.class);
		Book book = bs.getBook();
		System.out.println(book);
	}
}

- beans15.xml 파일을 읽어온다

- beans15.xml 에 bean 태그로 객체를 생성하지 않았으므로 BookService.class (경로설정) 로 Service 클래스의 객체를 생성함

- 생성한 Service 객체로 Service 의 getBook() 메소드 호출

 

- beans15.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
<!-- 
	1. sample15 패키지 하위 클래스를 스캔한다는 의미를 가진다.
    2. @Component, @Controller, @Service, @Repository 어노테이션이
             붙어있는 클래스는 @Autowired 어노테이션을 이용해서 필요한 빈 객체를  
       setter 메소드 없이 주입을 받는다.
 -->	
	
	<context:component-scan base-package="sample15"/>

</beans>

 

- BookServiceImpl.java

package sample15;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/*@Component*/
@Service
public class BookServiceImpl implements BookService {
	@Autowired
	private BookDao bd;

	public Book getBook() {
		return bd.getBook("바람과 함께 사라지다");
	}
}

- Service 클래스 위에는 @Service 를 주로 붙인다

- @Autowired 를 프로퍼티 bd 위에 쓰면 생성된 DAO 객체를 bd 에 주입해줌

- 이때 이 Service 클래스안에 setBd() 인 Setter 메소드가 없어도 자동으로 주입해준다

- 이때 생성 및 주입은 main() 에서 beans15.xml 을 읽어왔을때 이미 했음

+ 조건 1,2,3  을 만족하므로 자동으로 생성, 주입이 가능한 것임 (base-package="sample15", @Service, @Autowired)

 

- BookDaoImpl.java

package sample15;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

/*@Component*/
@Repository
public class BookDaoImpl implements BookDao {
	public Book getBook(String title) {//title="바람과 함께 사라지다"
		return new Book(title, 25000);
	}
}

- DAO 클래스 위에는 @Repository 를 주로 붙인다

- 현재는 DAO 에서는 아무 객체도 생성하고 있지 않지만 나중에 SQL문 실행을 위해서 SqlSession 객체를 @Autowired 로 생성 및 주입해야함

 - DTO 인 Book 객체를 만들어서 받은 "바람과 함께 사라지다" 와 25000 을 저장해서 객체를 돌려줌

 

+ Book.java (DTO) : Object 의 toString() 메소드를 오버라이딩 했다, DTO 리턴시 자동으로 실행됨

- 이제 다시 돌아가야한다

- BookServiceImpl.java : 받은 객체를 그대로 리턴한다

- Ex01.java : Service 클래스로부터 DTO 객체를 받아서 책제목과 가격을 리턴하는 toString() 을 실행


Sample 16 & Sample 17

- 콘솔로 만든 회원관리 프로그램

- 같은 내용이다

- Sample 16 은 bean 객체를 생성해서 만들고

- Sample 17 은 어노테이션 기반으로 만들었다


Spring DI 예제 16 (bean 객체 직접 생성, 회원관리 콘솔 프로그램)

- src/main/java/sample16/ 안의 파일들

- bean 을 직접 생성하여 만든 콘솔 회원관리 프로그램

 

Spring DI 예제 16 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다, 명령어에 따라 회원정보 검색, 등록, 삭제, 수정 가능

- MemberService.java : 인터페이스, 추상메소드들이 많이 있다 ex) delete(), insert(), update(), select(), list()

- MemberServiceImpl.java : MemberService 를 상속, MemberService 의 추상메소드들을 모두 오버라이딩

- MemberDao.java : 인터페이스, 추상메소드들이 많이 있다 ex) delete(), insert(), update(), selectByEmail(), list()

- MemberDaoImpl.java : MemberDao를 상속, MemberDao 의 추상메소드들을 모두 오버라이딩

- Member.java : DTO, 필드 id,pass,email,name,reg_date 와 각각의 getter/setter 메소드, toString() 메소드

- RegisterMember.java : DTO, 필드 pass, confirmpass, name, email 와 각각의 getter/setter 메소드, passCheck() 메소드

- DTO 객체가 2개이다

- RegisterMember.java DTO 클래스는 가입할때만 사용된다

 

Service 클래스와 DAO 클래스의 메소드 명이 같은 게 많다

- 같게 설정해서 어느 메소드에서 어느 메소드를 호출할지 알기 쉽게 해둔 것이다

ex) Service의 delete() 메소드에서 DAO 의 delete() 메소드를 호출

 

인터페이스를 사용하는 이유

- 예전에는 통일성있는 클래스를 작성하기 위해 인터페이스를 사용

- 지금은 메소드가 많아서 그걸 관리하기 위해 인터페이스를 사용한다 (어떤 메소드가 있는지 색인처럼 확인 가능)

 

- beans16.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">

	<bean id="md" class="sample16.MemberDaoImpl" />
	<bean id="ms" class="sample16.MemberServiceImpl">
		<property name="md" ref="md" />
	</bean>

</beans>

- bean 을 써서 Setter DI 를 직접 하고 있다

- Sample17에서는 같은 내용을 어노테이션으로 처리

- Service 객체 "ms" 와 DAO 객체 "md"가 생성됨

- 그리고 Service 객체 "ms" 의 프로퍼티 md 에 위에서 만들어진 DAO 객체 "md" 가 주입되었다

Service 클래스의 프로퍼티 md

- 이때 Service 클래스에는 Setter 메소드가 있어야만 함

 

- Ex01.java

package sample16;

import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	static MemberService ms = null;

	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample16/beans16.xml");
		ms = (MemberService) ac.getBean("ms");
		Scanner sc = new Scanner(System.in);
		while (true) {
			System.out.println("명령어를 입력하세요?");
			String command = sc.nextLine();
			if (command.equals("x")) {
				System.out.println("프로그램 종료");
				break;
			//startsWith() 메소드는 캐릭터 라인이, 지정된 접두사로 시작 여부를 감지하는 데 사용
			// public boolean startsWith(String prefix)
			} else if (command.startsWith("new")) {  // 입력한 문자가 new로 시작하면 true 리턴함
				insert(command.split(" "));
				continue;
			} else if (command.startsWith("select")) {
				select(command.split(" "));
				continue;
			} else if (command.equals("list")) {
				list();
				continue;
			} else if (command.startsWith("delete")) {
				delete(command.split(" "));
				continue;
			} else if (command.startsWith("update")) {
				update(command.split(" "));
				continue;
			}
			help();
		}
		sc.close();
	}

	public static void insert(String[] str) {
		if (str.length != 5) {
			help();
			return;
		}
		RegisterMember rm = new RegisterMember();
		rm.setEmail(str[1]);
		rm.setName(str[2]);
		rm.setPass(str[3]);
		rm.setConfirmPass(str[4]);
		
		if (!rm.passCheck()) {
			System.out.println("똑바로 암호 입력해");
		} else {
			int result = ms.insert(rm);
			if (result > 0)
				System.out.println("입력 성공");
		}
	}

	public static void select(String[] str) {
		if (str.length != 2) {
			help();
			return;
		}
		Member member = ms.select(str[1]);
		if (member != null)
			System.out.println(member);
		else
			System.out.println("없는 데이터 입니다");
	}

	public static void list() {
		Collection<Member> list = ms.list();
		if (list != null) {
			for (Member member : list) {
				System.out.println(member);
			}
		}
	}

	public static void delete(String[] str) {
		if (str.length != 2) {
			help();
			return;
		}
		int result = ms.delete(str[1]);
		if (result > 0)
			System.out.println("삭제 성공");
	}

	public static void update(String[] str) {
		if (str.length != 5) {
			help();
			return;
		}
		RegisterMember rm = new RegisterMember();
		rm.setPass(str[3]);
		rm.setConfirmPass(str[4]);
		rm.setEmail(str[1]);
		rm.setName(str[2]);
		if (!rm.passCheck()) {
			System.out.println("똑바로 암호 입력해");
		} else {
			int result = ms.update(rm);
			if (result > 0)
				System.out.println("수정 성공");
		}
	}

	public static void help() {
		System.out.println("잘못 입력했습니다");
		System.out.println("명령어 사용법:");
		System.out.println("new 이메일 이름 암호 암호확인");
		System.out.println("update 이메일 이름 암호 암호확인");
		System.out.println("delete 이메일");
		System.out.println("select 이메일");
		System.out.println();
	}
}

 

Service 객체 구하기 (Ex01.java 부분)

		ms = (MemberService) ac.getBean("ms");

 

Scanner 객체 구하기 (Ex01.java 부분)

		Scanner sc = new Scanner(System.in);

- 키보드로 회원정보를 입력받거나 목록을 수정/삭제 하기 위해 Scanner 객체 생성

 

- While문 안에서 계속해서 입력을 받을 수 있도록 무한 루프가 돈다, x 입력시 빠져나감

 

While문 안 명령 한 줄 받기 (Ex01.java 부분)

			String command = sc.nextLine();

 

입력에 따라 분기 (Ex01.java 부분)

if (command.equals("x")) {
				System.out.println("프로그램 종료");
				break;
			//startsWith() 메소드는 캐릭터 라인이, 지정된 접두사로 시작 여부를 감지하는 데 사용
			// public boolean startsWith(String prefix)
			} else if (command.startsWith("new")) {  // 입력한 문자가 new로 시작하면 true 리턴함
				insert(command.split(" "));
				continue;
			} else if (command.startsWith("select")) {
				select(command.split(" "));
				continue;
			} else if (command.equals("list")) {
				list();
				continue;
			} else if (command.startsWith("delete")) {
				delete(command.split(" "));
				continue;
			} else if (command.startsWith("update")) {
				update(command.split(" "));
				continue;
			}
			help();

- 이때의 입력은 이렇게 입력해야함 (아래)

	public static void help() {
		System.out.println("잘못 입력했습니다");
		System.out.println("명령어 사용법:");
		System.out.println("new 이메일 이름 암호 암호확인");
		System.out.println("update 이메일 이름 암호 암호확인");
		System.out.println("delete 이메일");
		System.out.println("select 이메일");
		System.out.println();
	}

 

- 분기 문 중 "new" 로 시작한걸 보자

사용자가 new 를 입력했다면 (Ex01.java 부분)

			} else if (command.startsWith("new")) {  // 입력한 문자가 new로 시작하면 true 리턴함
				insert(command.split(" "));
				continue;

- startWith() 는 어떤 특정한 문자로 시작하는지 확인 가능하다

- command 가 "new" 로 시작하면 comand.startWith("new") 는 True 를 반환

- True 면 insert() 메소드가 실행됨, 이 메소드는 Ex01 의 main() 아래에 정의되어있다

- 매개변수로는 command.split(" ") 으로 자른 배열을 주고 있다 (split() 의 리턴값은 String 형 배열이다)

- "new lay99@g.com lay 1999 1999" 를 입력했다면 공백을 기준으로 잘라서 배열에 저장해서 insert() 매개변수 인자로 준다

- 이 배열을 insert() 호출시 전달

 

- Ex01.java 의 insert()  (Ex01.java 부분)

- insert() 는 main() 바깥 아래에 있다

	public static void insert(String[] str) {
		if (str.length != 5) {
			help();
			return;
		}
		RegisterMember rm = new RegisterMember();
		rm.setEmail(str[1]);
		rm.setName(str[2]);
		rm.setPass(str[3]);
		rm.setConfirmPass(str[4]);
		
		if (!rm.passCheck()) {
			System.out.println("똑바로 암호 입력해");
		} else {
			int result = ms.insert(rm);
			if (result > 0)
				System.out.println("입력 성공");
		}
	}

0. 매개변수 String 배열이 넘어왔다

- str[0] = new, str[1] = lay99@g.com, str[2] = lay, str[3] = 1999, str[4] = 1999 가 넘어왔다

- 명령어new, 이메일, 이름, 암호, 암호확인 이 배열 str에 순서대로 들어가 있다

1. 이때 형식이 맞는지를 먼저 확인한다, str.len 이 5개여야하므로 5개가 아니면 help() 호출 후 insert() 를 나간다

- insert() 를 나가서 main() 으로 돌아가서 continue 문에 의해 다시 while 문에 의해 입력을 받음

2. DTO RegisterMember 객체 rm을 생성해서 그 안에 이메일, 이름, 암호, 암호확인을 객체에 저장

3. RegisterMember 의 passCheck() 메소드로 암호와 암호확인이 일치한지 확인

- 불일치시 "똑바로 암호 입력해", 일치시 main() 에서 받아온 Service 객체 ms (전역변수임) 로 ms.insert() 호출

- ms.insert() 호출시 매개변수로 RegisterMember(DTO) 객체 rm 을 전달

- ms.insert() 에서 리턴된 값이 0 보다 크면 "입력 성공" 출력

 

- Service 클래스의 메소드를 호출했으므로 Service 로 넘어갔다

 

- MemberServiceImpl.java

package sample16;

import java.util.Collection;
import java.util.Date;
import java.util.List;

public class MemberServiceImpl implements MemberService {
	private MemberDao md;

	public void setMd(MemberDao md) {
		this.md = md;
	}

	public int insert(RegisterMember rm) {
		int result = 0;
		Member member = md.selectByEmail(rm.getEmail()); //email주소로 1명의 정보를 구해옴
		if (member == null) {	// 동일한 email 주소가 없으면 회원가입함
			member = new Member(rm.getPass(), rm.getEmail(), rm.getName(), new Date());
			md.insert(member);
			result = 1;
		} else {
			System.out.println("이미 데이터가 있습니다");
		}
		return result;
	}

	public Member select(String email) {
		return md.selectByEmail(email);
	}

	public Collection<Member> list() {
		return md.list();
	}

	public int delete(String email) {
		int result = 0; // 데이터가 이미 있는지 확인
		Member member = md.selectByEmail(email);
		if (member != null) {
			md.delete(email);
			result = 1;
		} else {
			System.out.println("없는네 우찌 삭제하니");
		}
		return result;
	}

	public int update(RegisterMember rm) {
		int result = 0; // 데이터가 이미 있는지 확인
		Member member = md.selectByEmail(rm.getEmail());
		if (member != null) {
			member.setPass(rm.getPass());  // 비번과 이름 수정
			member.setName(rm.getName());
			md.update(member);
			result = 1;
		} else {
			System.out.println("없는네 우찌 고치니 ? 헐");
		}
		return result;
	}
}

- DAO 객체를 주입할 필드 md, Setter 메소드 setMd() 가 있다

- 이 필드 md 에는 이미 DAO 객체가 주입되었다 (main 에서 beans16.xml 을 읽었을때 주입되었음)

 

insert() 부분만 (MemberServiceImpl.java 부분)

	public int insert(RegisterMember rm) {
		int result = 0;
		Member member = md.selectByEmail(rm.getEmail()); //email주소로 1명의 정보를 구해옴
		if (member == null) {	// 동일한 email 주소가 없으면 회원가입함
			member = new Member(rm.getPass(), rm.getEmail(), rm.getName(), new Date());
			md.insert(member);
			result = 1;
		} else {
			System.out.println("이미 데이터가 있습니다");
		}
		return result;
	}

- DAO 객체를 받은 md 로 md.selectByEmail() 로 이메일 주소 중복검사를 한다, 여기서 DAO 로 이동한다

 

- DAO 의 selectByEmail() 를 호출했으므로 DAO 로 갔다

- MemberDaoImpl.java

package sample16;

import java.util.*;

public class MemberDaoImpl implements MemberDao {
	private Map<String, Member> map = new HashMap<String, Member>();
	private static int nextId = 0;

	public void insert(Member member) {
		member.setId(++nextId);
		map.put(member.getEmail(), member);
	}

	public Member selectByEmail(String email) {
		return map.get(email);
	}

	public Collection<Member> list() {
		return (Collection<Member>) map.values();
	}

	public void delete(String email) {
		map.remove(email);
	}

	public void update(Member member) {
		map.put(member.getEmail(), member);
	}
}

- DB 연동을 안하므로, Map 자료구조에 회원 데이터를 저장할 것임, 여기 DAO 의 Map 객체 map 에 저장할 것

- Map 자료구조에서 키값은 String, 밸류값은 Member(DTO) 가 자료형이다

- 키값이 Email 주소이고, 밸류값은 해당 회원의 정보를 담게 할 것임

- nextId 는 몇명이 등록되어있는지 저장하는 역할, insert() 가 되면 이 값을 증가시켜서 setId() 메소드로 넘어온 DTO 객체 member 에 저장 * 아래까지 모두 보고와야 이해됨

<selectbyemail() 메소드>

- map.get() 으로 넘어온 이메일을 키로, 그 키의 밸류인 DTO 객체를 돌려주고 있다

- 해당 키(매개변수로 넘어온) 에 해당하는 밸류가 있으면 밸류를 리턴, 없으면 null 을 리턴

 

- Service 로 돌아가자

- Service 의 insert() 부분만

	public int insert(RegisterMember rm) {
		int result = 0;
		Member member = md.selectByEmail(rm.getEmail()); //email주소로 1명의 정보를 구해옴, *
		if (member == null) {	// 동일한 email 주소가 없으면 회원가입함
			member = new Member(rm.getPass(), rm.getEmail(), rm.getName(), new Date());
			md.insert(member);
			result = 1;
		} else {
			System.out.println("이미 데이터가 있습니다");
		}
		return result;
	}

<DAO selectByEmail() 에서 돌아온 뒤>

- * 가 돌아온 곳의 줄이다

- selectByEmail() 로 부터 돌아와서 Member 객체 member 로 가져온 객체를 저장한다

- select 문에서 돌려주는 값이 없어야 동일한 Email 이 없는 것임, 그래서 member == null 일때 삽입시켜야함

- 만약 객체를 제대로 가져왔다면 이메일 중복이 아니므로 이 회원을 등록(삽입) 할 것임

- 즉, 중복이 아닐때는 DAO 객체를 받은 md 로 md.insert() 해서 DAO 의 insert() 호출

<회원 등록>
- Member DTO 객체 member 에 매개변수로 넘어온 RegisterMember DTO 객체 rm 에서 암호, 이메일, 이름을 꺼내서 세팅

- 그 후 Service 객체 md 로 insert() 호출, 넘겨주는 값은 객체 member 이다

 

- 그럼 다시 DAO 로 간다

- DAO의 insert() 부분만

	public void insert(Member member) {
		member.setId(++nextId);
		map.put(member.getEmail(), member);
	}

- 매개변수로 넘어온 객체 member 에 setId() 로 id(아이디 비번할떄 id 가 아니라 인식을 위한 id) 를 세팅

- 현재는 DB 가 아닌 메모리에 저장한다, 그래서 map 자료구조에 put() 으로 키를 이메일, 밸류를 상세정보를 저장한 member 로 준다

+ map 은 키값 중복 안됨

- DAO 의 insert() 에서 반환은 하지 않음

 

- 다시 Service로 돌아옴

- Service 클래스 insert() 부분만

	public int insert(RegisterMember rm) {
		int result = 0;
		Member member = md.selectByEmail(rm.getEmail()); //email주소로 1명의 정보를 구해옴
		if (member == null) {	// 동일한 email 주소가 없으면 회원가입함
			member = new Member(rm.getPass(), rm.getEmail(), rm.getName(), new Date());
			md.insert(member); // * 돌아온 곳 
			result = 1;
		} else {
			System.out.println("이미 데이터가 있습니다");
		}
		return result;
	}

* 돌아온 곳

- DAO의 insert 에서 반환은 하지 않는다, 그냥 삽입만 한 것

- 여기선 DB연동이 아니므로 DAO까지 갔다 온 후 내가 임의로 result 에 1 을 넣고 그 result 를 반환

 

- 그러면 다시 Ex01.java 로 간다

- Ex01.java 에서 insert() 부분만

	public static void insert(String[] str) {
		if (str.length != 5) {
			help();
			return;
		}
		RegisterMember rm = new RegisterMember();
		rm.setEmail(str[1]);
		rm.setName(str[2]);
		rm.setPass(str[3]);
		rm.setConfirmPass(str[4]);
		
		if (!rm.passCheck()) {
			System.out.println("똑바로 암호 입력해");
		} else {
			int result = ms.insert(rm); // * 돌아온 곳
			if (result > 0)
				System.out.println("입력 성공");
		}
	}

* 돌아온 곳

- result 에 1이 반환되었으므로 아래의 "입력 성공" 을 출력

 

- 실제 입력을 해보자


- 이제 검색을 해보자

- 검색할때는 이메일 주소로만 검색 가능

 

Ex01.java

- main() 에서의 명령에 따른 분기문은 아까와 같은 원리이므로 설명 생략, select() 로 간다

 

Ex01.java 의 select()

	public static void select(String[] str) {
		if (str.length != 2) {
			help();
			return;
		}
		Member member = ms.select(str[1]);
		if (member != null)
			System.out.println(member);
		else
			System.out.println("없는 데이터 입니다");
	}

- 형식이 "select 이메일" 이므로 length 가 2인지 확인

- Service 객체 ms 로 ms.select() 하고, 매개변수로는 이메일을 넘김

 

- Service 클래스 MemberServiceImpl.java 의 select() 부분만

	public Member select(String email) {
		return md.selectByEmail(email);
	}

- DAO 객체 md 로 DAO 의 selectByEmail() 을 호출, 매개변수로는 넘어온 email 을 그대로 넘겨줌

 

- DAO 클래스 MemberDaoImpl.java의 selectByEmail() 부분만

	public Member selectByEmail(String email) {
		return map.get(email);
	}

- 회원이 저장되어있는 Map 객체 map 에서 get(키) 로 밸류인 Member 객체를 구해옴

 

- 이제 돌아간다

- Service 클래스 MemberServiceImpl.java 의 select() 부분만

	public Member select(String email) {
		return md.selectByEmail(email);
	}

- 그대로 리턴함, Ex01.java 로 간다

 

- Ex01.java 의 select() 부분만

	public static void select(String[] str) {
		if (str.length != 2) {
			help();
			return;
		}
		Member member = ms.select(str[1]);
		if (member != null)
			System.out.println(member);
		else
			System.out.println("없는 데이터 입니다");
	}

- ms.select() 에서 만약 있는 회원이면 객체를 반환해서 member 에 저장, 없으면 null 값이 member 로 들어감

- 그래서 member != null 일땐 member 출력 (Member DTO 클래스에 toString() 이 있으므로 그걸로 회원 정보 모두 출력)

 

- 검색을 실제로 해보면

- 검색된다


- 이제 목록 출력을 해보자

 

- 목록 출력시에는 "list" 라고만 입력하면된다

 

Ex01.java

- main() 에서의 명령에 따른 분기문은 아까와 같은 원리이므로 설명 생략, list() 로 간다

- Ex01.java 의 list() 부분만

	public static void list() {
		Collection<Member> list = ms.list();
		if (list != null) {
			for (Member member : list) {
				System.out.println(member);
			}
		}
	}

- Service 객체 ms 로 ms.list() 호출하고 Collection<Member>가 자료형인 list 로 반환

+ Collection : 인터페이스, 객체의 모음을 저장, List, Set, Map 이 이 Collection 을 상속함

 

- Service 클래스 MemberServiceImpl.java 의 list() 부분만

	public Collection<Member> list() {
		return md.list();
	}

- DAO 객체 md 로 md.list() 를 호출하고 있다

 

- DAO 클래스 MemberDaoImpl.java 의 list() 부분만

	public Collection<Member> list() {
		return (Collection<Member>) map.values();
	}

- 회원 데이터를 저장하는 Map 객체 map 으로 map.values() 로 map 에 저장된 모든 value 값들을 가져와서 리턴함

- map.values() 는 모든 회원 정보, 즉 DTO 객체들의 모음이다

- map.values() 는 리턴자료형이 Collection 이다!

- 그럼 Service 클래스로 다시 돌아가자

 

- Service 클래스 MemberServiceImpl.java 의 list() 부분만

	public Collection<Member> list() {
		return md.list();
	}

- 그대로 Ex01 로 리턴함

 

- Ex01.java 의 list() 부분만

	public static void list() {
		Collection<Member> list = ms.list();
		if (list != null) {
			for (Member member : list) {
				System.out.println(member);
			}
		}
	}

- 돌아와서 데이터들을 받아서 for문을 통해서 하나씩 출력하고 있다 (toString() 사용됨)

 

- update / delete 는 간략히만 설명

 

update

- update는 기존에 있는 이메일을 써야하고 이름과 비번을 수정 가능하다

<Ex01.java>

- update 는 RegisterMember의 passCheck() 를 먼저 호출해서 암호와 암호확인이 일치하는지 확인 후 일치시 Service 의 update() 호출

<Service>

- Service 의 update() 안에서 DAO의 selectByEmail() 를 호출해서 있는 이메일인지 확인

- 중복이 아닌 이메일이면 DAO의 update() 를 호출해서 수정

<DAO의 update()>

- update 는 키값이 중복되면 마지막의 키값만 사용할 수 있다는 점을 이용

- 즉 정보수정을 원하는 회원의 키값과 똑같은 키값으로 새로운 데이터(수정용 데이터)를 저장

 

delete

- map 에서 remove() 메소드로 키값 email 로 해당 데이터를 삭제


Spring DI 예제 16 (어노테이션 기반, 회원관리 콘솔 프로그램)

- src/main/java/sample17/ 안의 파일들

- 어노테이션 기반으로 만든 콘솔 회원관리 프로그램

 

Spring DI 예제 17 구조 & 흐름

- Web Application 에서는 갈땐 Controller-> Service -> DAO, 올땐 DAO -> Service -> Controller

- 현재는 Web Application 이 아닌  Application 이므로 Controller 클래스는 없다, 그 역할을 메인메소드가 하고 있는거임

- 갈때 : main() -> Service -> dao , 돌아올땐 반대로 dao -> Service -> main() 으로 돌아오자

 

- Ex01.java : 메인메소드를 가진 클래스이다, Controller 클래스의 역할을 대신한다, 명령어에 따라 회원정보 검색, 등록, 삭제, 수정 가능

- MemberService.java : 인터페이스, 추상메소드들이 많이 있다 ex) delete(), insert(), update(), select(), list()

- MemberServiceImpl.java : MemberService 를 상속, MemberService 의 추상메소드들을 모두 오버라이딩

- MemberDao.java : 인터페이스, 추상메소드들이 많이 있다 ex) delete(), insert(), update(), selectByEmail(), list()

- MemberDaoImpl.java : MemberDao를 상속, MemberDao 의 추상메소드들을 모두 오버라이딩

- Member.java : DTO, 필드 id,pass,email,name,reg_date 와 각각의 getter/setter 메소드, toString() 메소드

- RegisterMember.java : DTO, 필드 pass, confirmpass, name, email 와 각각의 getter/setter 메소드, passCheck() 메소드

- DTO 객체가 2개이다

- RegisterMember.java DTO 클래스는 가입할때만 사용된다

 

Service 클래스와 DAO 클래스의 메소드 명이 같은 게 많다

- 같게 설정해서 어느 메소드에서 어느 메소드를 호출할지 알기 쉽게 해둔 것이다

ex) Service의 delete() 메소드에서 DAO 의 delete() 메소드를 호출

 

인터페이스를 사용하는 이유

- 예전에는 통일성있는 클래스를 작성하기 위해 인터페이스를 사용

- 지금은 메소드가 많아서 그걸 관리하기 위해 인터페이스를 사용한다 (어떤 메소드가 있는지 색인처럼 확인 가능)

 

- beans17.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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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-4.1.xsd">
	<context:component-scan base-package="sample17"/>
</beans>

- 조건 3가지 중 첫번재를 만족한다, sample17 패키지를 읽어온다

 

- MemberServiceImpl.java

package sample17;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("ms")
public class MemberServiceImpl implements MemberService {
	@Autowired
	private MemberDao md;

	public int insert(RegisterMember rm) {
		int result = 0;
		Member member = md.selectByEmail(rm.getEmail());
		if (member == null) {
			member = new Member(rm.getPass(), rm.getEmail(), rm.getName(), new Date());
			md.insert(member);
			result = 1;
		} else {
			System.out.println("이미 데이터가 있습니다");
		}
		return result;
	}

	public Member select(String email) {
		return md.selectByEmail(email);
	}

	public Collection<Member> list() {
		return md.list();
	}

	public int delete(String email) {
		int result = 0; // 데이터가 이미 있는지 확인
		Member member = md.selectByEmail(email);
		if (member != null) {
			md.delete(email);
			result = 1;
		} else {
			System.out.println("없는네 우찌 삭제하니");
		}
		return result;
	}

	public int update(RegisterMember rm) {
		int result = 0; // 데이터가 이미 있는지 확인
		Member member = md.selectByEmail(rm.getEmail());
		if (member != null) {
			member.setPass(rm.getPass());
			member.setName(rm.getName());
			md.update(member);
			result = 1;
		} else {
			System.out.println("없는네 우찌 고치니 ? 헐");
		}
		return result;
	}
}

 

@Service 어노테이션 (MemberServiceImpl.java 부분)

- 조건 3개 중 두번째 조건이다

- @Service 어노테이션 안에서 Service 객체명의 이름을 지정할 수 있다

- sample16 에서는 getBean() 으로 구해왔다

- 여기선 bean 을 직접 만들지 않았으므로 sample15 처럼 getBean(서비스클래스명.class) 로 구해와야한다

- 여기선 @Service 어노테이션 안에 Service 객체명(객체 id값)으로 "ms" 를 지정했으므로 main() 에서 Service 객체를 구해올때 getBean("ms") 로 구해올 수 있다

 

+ Ex01.java 의 main() 부분

- 만약 @Service("ms") 가 아닌 @Service 만 했다면 ac.getBean(MemberService.class) 로 Service 객체를 구해야한다

- 하지만 Service 클래스 상단에서 @Service("ms") 로 그 객체에 이름을 줬으므로 이름으로 getBean("ms") 불러옴 

 

@Autowired 어노테이션 (MemberServiceImpl.java 부분)

- 조건 3개 중 세번째 조건이다

- DAO 객체를 주입받아야하는 프로퍼티 md 위에 @Autowired 를 작성

- 그럼 DAO 객체 생성과 md 로 주입까지 완료된다

 

- MemberDaoImpl.java

package sample17;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;

@Repository
public class MemberDaoImpl implements MemberDao {
	private Map<String, Member> map = new HashMap<String, Member>();
	private static int nextId = 0;

	public void insert(Member member) {
		member.setId(++nextId);
		map.put(member.getEmail(), member);
	}

	public Member selectByEmail(String email) {
		return map.get(email);
	}

	public Collection<Member> list() {
		return (Collection<Member>) map.values();
	}

	public void delete(String email) {
		map.remove(email);
	}

	public void update(Member member) {
		map.put(member.getEmail(), member);
	}
}

 

@Reopsitory 어노테이션 (MemberDaoImpl.java 부분)

- 지금은 DAO 클래스에서 아무 객체도 주입받고 있지 않으므로 @Repository 가 없어도 된다

- 나중에 DAO 클래스에서 SqlSession 객체를 생성 해서 DAO의 프로퍼티로 주입할때는 @Repository 를 위에 붙여야한다

 


스스로 질문 & 스스로 답변

Q. Service 클래스 상단에서 @Service 만으로 썻으면 main() 에서 Service 객체 구해올때 getBean(Service클래스명.class) 로 구해왔었다, 근데 나중에 Web Application 를 해서 Controller -> Service 로 갈떄도 getBean(Service클래스명.class) 를 쓰는가?

 

A. 아니다, 지금 Application 에서는 main() 에서 Service 객체를 구해와야하므로 getBean() 을 쓰지만 나중에 Controller -> Service 로 갈때는 Controller 클래스에서 @Autowired 로 Service 객체를 바로 프로퍼티에 주입시키므로 그냥 바로 쓰면 된다, getBean() 으로 직접 구해올 필요 없이 이미 Spring 환경설정 파일을 읽어올때 다 생성되고 주입되었음, 주입된 Service객체를 바로 쓰면 됨

 

- 이렇게 @Autowired 로 Controller 의 프로퍼티 service 에 바로 Service 객체가 주입됨 

앞으로 학습할 것

Spring

- 환경 설정이 어렵다

- 환경 설정 파일이 맞물려서 돌아가므로 하나가 잘못되면 아파치 톰캣이 동작 안함

- 어노테이션 기반으로 처리되므로 값의 전달, 리턴이 보이지 않음

 

Springboot

- 환경 설정이 비교적 쉽다

- 출력 방법으로 JSTL 외에도 타임리프 표기법이 내장

 

타임리프

- 타임리프 사용시 View 페이지를 jsp 파일로 만들지 않고 html 파일로 만든다, 기존 jsp 태그 사용 불가

- EL / JSTL 과 같은 역할

 

- zip 으로 압축한 프로젝트를 바탕화면에 복사, 압축 해제 후 import 

- import 하는 방법은 Maven 프로젝트와 같다


Spring Framework

Spring framework 창시

- 2000 년 초반 경 등장

- 로드 존슨(Rod Johnson)이 자신의 JAVA 개발 경험과 노하우를 기반으로 출판한 책 (Expert One-oneOne J2EE Design and Development)에 샘플 프레임워크를 만들어서 저서에 실었는데, 

- 이것이 차후 스프링이란 자바 표준 프레임워크로 발전하게 되었다.

- 엔터프라이즈 어플리케이션 개발의 복잡성을 줄여주기 위한 목적으로 개발 되었다.

- 대용량 프로젝트 개발에 사용하던 복잡한 구조의 EJB 사용으로 수행되었던 모든 기능을 쉽게 경량화하여 일반 POJO(Plain Old Java Object) 를 사용해서 가능하게 하였다 (Java 를 써서 개발한다는 의미)

 

Spring framework 특징

1. "경량 컨테이너"(크기와 부하의 측면)로서 자바 객체를 직접 관리

- 즉 자바를 이용한 Spring 프레임워크

 

2. 제어 역행(IoC : Inversion of Control)

- 애플리케이션의 느슨한 결합을 도모.

- 기존 방식으로 객체를 생성해서 처리하는게 아니라 xml 파일로 처리

 

3. 의존성 주입(DI : Dependency Injection)

- 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.

 

4. 관점지향 프로그래밍(AOP : Aspect-Oriented Programming)

- 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.

- 트랜잭션이나 로깅, 보안과 같이 모든 개발자들이 공통적으로 처리해야하는 작업 (공통모듈) 을 따로 처리 가능

 

5. 모델-뷰-컨트롤러 패턴

- 웹 프로그램밍 개발 시 거의 표준적인 방식인 "Spring MVC"라 불리는 모델-뷰-컨트롤러(MVC) 패턴을 사용한다.

- 모델, 뷰, 컨트롤러로 분리시켜 개발하는 방법, Model 2 방식과 유사

 

Spring framework 환경 구축 2가지 방법

1. Eclipse에 STS프로그램을 plugin으로 추가해서 사용하는 방법
- 우리가 쓰는 이클립스 내에는 Spring 프로젝트를 만들 수 있는 메뉴가 없으므로 plugin 추가

- 기존 프로젝트가 잘 동작하지 않거나 sql 파일이 열리지 않는 등의 문제가 많이 발생한다

- 기존 Eclipse 에 [Help] - MarketPlace 에서 "sts" 검색 후 Spring Tools 3 설치

2. STS 프로그램 다운로드 받아서 사용하는 방법

- 이 방법을 사용할 것


Spring 환경 구축 (STS 프로그램 다운)

- STS 는 기존 이클립스와 유사

- 기존 이클립스와 다른 점 : Spring 이나 Springboot Project 를 만들 수 있는 메뉴 내장

+ 이제 이클립스는 실습시에 쓰지 않을 것, 나머지 수업은 모두 STS 로 진행

+ STS 에는 Data Source Management (DB접속) 기능이 빠져있다

 

- 2가지 방법 중 STS 프로그램 다운로드 받는 방법 사용

 

STS 버전

- STS 는 3.x 와 4.x 버전이 있다,

- 4.x 는 Spring boot project 만 만들 수 있는 메뉴만 나타나고 Spring project 만드는 메뉴 나타나지 않음

- 3.x 설치시 Spring project, Spring boot project 모두 만들 수 있는 메뉴 나타남

- 실습을 위해 3점대를 설치할 것, 자바를 낮은 버전을 쓰고 있으므로 STS 3.9.11 (3.9 중에서 가장 낮은 버전) 을 설치한다

 

STS (Spring Tool Suite) 4.x 다운로드

- https://spring.io/tools 접속

 

STS (Spring Tool Suite) 3.x 다운로드

- 이걸 다운받을 것

- https://github.com/spring-projects/toolsuite-distribution/wiki/Spring-Tool-Suite-3 접속

- 이 다운받은 zip 파일을 C 드라이브에 저장시켰다

- 압축 푼 후 sts-bundle/sts-3.9.11.RELEASE 로 가서 sts.exe 라는 실행 파일 실행시키면 STS 가 구동됨

- 해당 실행 파일을 단축 아이콘 만들 것 (오른쪽 마우스 -> 보내기 -> 바탕 화면에 바로 가기 만들기)

- 해당 실행 파일 실행시키기

- Workspace 를 지정, 난 기본 Workspace 그대로 사용

- 기본 Workspace : C:\Users\admin\Documents\workspace-sts-3.9.11.RELEASE

- 이클립스와 비슷함

- 이클립스와 다른 점 : Spring Project, Springboot Project 를 만들 수 있는 메뉴가 추가되어있다

- STS 에는 Apache Tomcat 말고도 Pivotal 서버가 기본 내장되어있다, 우리는 Apache Tomcat 만 사용할 것이므로 Pivotal 서버는 삭제하자

서버를 삭제시 2군데서 지워야한다

1. Servers 탭에서 삭제

2. [Window] -> Preferences -> Server -> Runtime Environment 에서 선택 후 Remove

 

Dynamic Web Project 를 생성

- 아파치 톰캣의 버전과 위치를 설정하자, 최초 1번은 연결시켜야함

- 프로젝트명은 jsptest

- 위의 Defalt output folder 는 바이트코드가 저장되는 위치임

- 이때 index 파일 생성 전에 Encoding 을 먼저 설정하자

- 프로젝트 생성 시 가장 먼저 Encoding 부터 설정해야함, 모두 UTF-8 로 변환

 

인코딩 설정

[Window] -> Preferences -> "encoding" 이라 검색

- Workspace, CSS Files, HTML Files, JSP Files 의 Encoding 을 모두 "UTF-8" 로 변환 후 적용

- XML Files 는 기본으로 UTF-8 로 설정되어있으므로 변경하지 않음

 

index 파일 생성

- jsptest 프로젝트 WebContent 폴더 하위에 index.jsp 생성

- 실행시 jsptest~!! 가 잘 출력됨

 

포트번호 확인

- 아래의 Tomcat v9.0 을 더블클릭

- 이클립스에서 쓰는 포트와 다른 포트를 써야함

- 이클립스에서 80 을 쓰고 있지 않으므로 sts 에서 실행됐던 것

- 변경을 원할땐 Servers 에서 server.xml 을 열어서 수정

 

폰트 크기 설정

[Window] - Preference - Appearance - Colors and Fonts

 

Spring MVC Project 생성

- [File] - New - Project

- 이 Templates 를 어떤걸 선택하냐에 따라 구조가 달라진다

- Spring MVC Project 를 만들때 top-level package 명을 3단계로 입력해야한다

- 도메인명 역순으로 3단계를 입력, com.myhome.springtest 로 하자

 

- Spring MVC Project 만들면 java, resources, webapp 3가지 폴더가 만들어짐

- 그 중 java 폴더 하위에 com.myhome.springtest 패키지 하위에 Controller 클래스가 자동 생성되게 된다

 

+ Maven 으로 라이브러리 관리하는게 기본이므로 Spring MVC Project 를 생성하면 기존에 없던 내용들을 로컬 저장소로 다운한다

 

+ 프로젝트 왼쪽 아이콘

s : spring project

m : maven project

 

- Spring 은 기본적으로 Maven 으로 라이브러리를 관리한다

- Maven 환경설정 파일인 pom.xml 을 열어보자

- pom.xml (초기)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>springtest</artifactId>
	<name>springtest</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>3.1.1.RELEASE</org.springframework-version>
		<org.aspectj-version>1.6.10</org.aspectj-version>
		<org.slf4j-version>1.6.6</org.slf4j-version>
	</properties>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
				
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	
		
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
			</exclusions>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
				
		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
	
		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>        
	</dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

- Maven 프로젝트와 달리 Spring 프로젝트는 pom.xml 에 60% 이상의 기본적인 내용들이 모두 들어가 있음

- 많은 Dependency 들이 이미 추가되어있음 (inject, java servlet, jsp, jstl 등)

- groupId 와 ArtifactId 에 top-level package 등록한게 여기 나와있다

- <java-version> 을 현재 사용하고 있는 1.8 버전으로 나중에 수정하자 

- 현재 Spring 버전은 3.1.1 으로 되어있을을 확인 가능, 나중에 필요하면 올리면 된다

 

 

Spring MVC Project 구조

- main 폴더 아래에는 Maven 프로젝트처럼 java , resources, webapp 폴더가 있다

- java 폴더 안에 com/myhome/springtest 라는 top-level 프로젝트가 만들어져 있다, 그 안에 sample 로 만들어진 Controller 클래스 HomeController.java 가 있음

- test 폴더는 테스트 용도

- WEB-INF/views 폴더에는 View 파일들이 들어감

- WEB-INF 안에는 환경설정 파일 xml 파일들이 많이 있다

- webapp 폴더 안에 하위 폴더들이 여러개 있다

- webapp 폴더 안에 web.xml 파일도 있다

 

- HomeController.java

package com.myhome.springtest;

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;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
}

- return "home" 으로 되어있다, home.jsp 파일을 의미

 

Model 2 에서 클라이언트가 요청시 Controller 로 갈때 2가지 방법

1) 어노테이션

2) web.xml 파일 안에 Controller 클래스 이름, 위치가 등록되어있고 매핑으로 찾아감

- DispatcherServlet 을 FrontController 클래스라 부름

- web.xml 파일 안에 이 DispatcherServlet 클래스로 찾아가도록 매핑을 잡는 내용들이 있다

- 현재는 web.xml 파일 안에서 매핑을 잡을때 <url-pattern> 이 / 이므로 어떤 패턴으로 요청해도 찾아간다

* web.xml 코드는 아래에

 

Spring MVC 흐름도 (간략)

- 클라이언트가 요청시 가장 먼저 앞에 있는 Controller 클래스라는 의미의 Front Controller 클래스(= Dispatcher Servlet) 로 찾아간다

- Dispatcher Servlet Class = Front Controller Class

- Dispatcher Servlet Class를 찾아갈때는 web.xml 에 매핑을 통해 찾아간다

 

- HomeController 클래스는 샘플로 만들어진 클래스이며 FrontController 클래스와 다름, 사진에서 오른쪽(뒤쪽) 에 있는 Controller 클래스이다

 

 

Spring Project 실행하는 방법

- springtest 프로젝트에서 오른쪽 마우스 -> Run As -> Run on Server

- 한글 인코딩이 되어있지 않아서 한글값이 깨져서 나온다

- 한글값이 깨지지 않게 설정해보자

 

- /webapp/WEB-INF/views/home.jsp 파일의 1라인에 아래의 코드를 추가후에 다시 실행

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

- 현재 문서의 한글값을 UTF-8 로 인코딩하는 코드이다

- home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	Hello world!  
</h1>

<P>  The time on the server is ${serverTime}. </P>
</body>
</html>

- 인코딩이 되었다


Spring MVC 흐름 설명

1. 먼저 Client -> Dispatcher Servlet 으로 간다

- web.xml 을 통해 설정을 해주면 자동으로 간다

- Dispatcher Servlet 클래스는 내부적인 처리이고 우리가 볼 수 없음

2. Dispatcher Servlet -> Controller 로 이동

- Client 에서 요청한 요청이름명에 해당하는 Controller @RequestMappign 으로 간다

3. Controller 클래스에서 Service, DAO 와 작업을 함

- @RequestMapping 어노테이션 사용해서 나누기

- 이 부분은 나중에 설명

4.Controller 클래스에서 ModelAndView 에 값을 저장 해서 Dispatcher Servlet 으로 돌아감

- return 으로 지정된 곳의 View 페이지로 가게됨

5. Dispatcher Servlet 에서 ViewResolver 의 prefix, suffix 를 붙여서 해당 View 로 간다

6. View 에서 EL 등으로 출력

 

Spring MVC 흐름도

- 값이 전달되는 흐름을 보여준다

1. 먼저 Client -> Dispatcher Servlet 으로 간다

- Model 2 와 다른점 : Spring 에는 Dispatcher Servlet 클래스를 Front Controller 클래스라고 부른다

- 가장 앞에 있는 Controller 클래스 = Front Controller 클래스 = Dispatcher Servlet

- 요청이 오면 Front Controller 클래스로 가장 먼저 찾아가야함

- Dispatcher Servlet 클래스는 Spring 지원 라이브러리에 있다

 

Front Controller 클래스

- 모든 클라이언트의 요청은 가장 먼저 여기로 온다 , 흐름을 제어함

- web.xml 에서 매핑을 잡아오면 여기까지는 자동으로 온다

- 내부적으로 찾아가므로 url pattern 값만 수정하고, 여기까지 찾아가는 것은 크게 신경쓰지 않아도 됨

- 기본적으로 제공되는 Dispatcher Servlet 클래스 사용해서 만듬

- web.xml 에서 DIspatcher Servlet 클래스의 위치 또한 등록되어있다

 

Front Controller 클래스로 찾아가는 방법 2가지 중 web.xml 방법

- web.xml 은 Apache Tomcat 구동시 가장 먼저 읽어오는 파일이다

+ Dynamic Web Project 의 web.xml 에는 파일을 찾는 순서가 들어있다

- web.xml 에는 Dispatcher Servlet 클래스의 이름과 위치가 등록되어있으므로 이 클래스로 찾아가기 위한 Servlet 매핑을 잡는 내용이 web.xml 에 들어가 있다

 

- Dispatcher Servlet 까지는 자동으로 찾아가므로 크게 신경 쓰지 않아도 된다

- 우리는 패턴만 바꾸면 됨

- 그림은 뒤의 Service 와 DAO 가 나타나 있지 않음, Controller 클래스까지 가는 흐름만 그림에 표시

 

- web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

- <servlet> : <servlet-class> 에 Front Controller 클래스 위치가 등록되어 있다

- <servlet-mapping> : 어떤 확장자로 요청할때만 찾아갈지 설정하는곳, URL 패턴 지정

- <servlet> 과 <servlet-mapping> 안에 있는 <servlet-name> 값은 일치해야함

- <servlet-class> : Front Controller 클래스는 DispatcherServlet 클래스로 생성하므로 DispatcherServlet 클래스의 위치가 등록되어있음

- <url-pattern> 에 / 로 되어있으므로 모든 요청을 받음, 아무렇게 요청해도 반드시 DispatcherServlet 으로 가게된다

- 이게 WebServlet 어노테이션이 하는 역할과 같은 역할

 

2. Dispatcher Servlet -> Controller 로 이동

Controller 클래스 ( Front Controller 클래스 아닌 뒤의 Controller 클래스)

- 프로젝트 생성시 Sample 로 HomeController 클래스가 만들어진다, 나중엔 이걸 지우고 직접 Controller 클래스들 생성

- 이 HomeController 클래스가 Front 가 아닌 뒤의 Controller 클래스임

- Controller 클래스는 여러개 만들어진다 (프로그램마다 하나씩)

 

- Controller 클래스는 상단에 @Controller 어노테이션을 붙인다, 그럼 Controller 클래스 기능을 함

+ Spring 버전이 낮았을땐 클라이언트가 어떤 패턴으로 찾아왔을때 어디로 갈지 설정해주는 Handler Mapping 을 사용해서 Controller 클래스로 찾아갔다

- 지금은 Spring 버전이 높아져서 어노테이션 기반으로 바뀌면서 Handler Mapping 대신 @RequestMapping 어노테이션으로 요청을 받는다

	@RequestMapping(value = "/", method = RequestMethod.GET)

- value 에 "/" 에 있다, 아무거나 요청해도 모든 요청을 받겠다는 의미, 나중에 수정할 것

- value 에 해당하는 요청으로 오면 아래의 메소드 home() 은 자동으로 실행해줌

+ 나중엔 이 @RequestMapping 이 여러개 들어감

+ 나중엔 @RequestMapping에 구체적으로 어느 이름으로 온 요청을 받을지 따로 따로 들어감 

 

- Sample 인 Controller 클래스를 보자

- HomeController.java

package com.myhome.springtest;

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;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/**
	 * Simply selects the home view to render by returning its name.
	 */
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
}

- home() 의 리턴자료형이 String 으로 되어있다, 나중에 String 을 리턴

- home() 안의 내용에서 Date 객체를 생성하고 DateFormat 클래스를 써서 DateFormat 을 LONG 형으로 길게 설정, 그 객체로 format() 해서 날짜를 포매팅해서 formattedDate 에 저장,

- 이걸 Model 2 에서는 공유설정했다, Spring 에서는 Model 객체에 값을 addAttribute () 메소드로 키 밸류 형태로 저장함

- Spring 에서는 Model model 을 매개변수에 추가하면 자동으로 Model 객체가 생성됨'

public String home(Locale locale, Model model){}

- LONG 형으로 길게 설정된 포매팅된 날짜 formattedDate 를 value 로 저장

- 이 값을 JSP 파일에서 EL 로 ${serverTime} 을 쓰면 포매팅된 날짜 출력됨 

- return "home" 하고 있다 -> home.jsp 파일에서 EL로 그 날짜를 꺼내서 출력함

- Model 객체는 request 객체와 비슷한 역할

- Controller -> Dispatcher Servlet -> View 로 이동함 , 바로 View인 home.jsp 로 가는게 아님

- Dispatcher Servlet 은 내부적인 처리이므로 크게 신경쓰지 않아도 된다

 

3. Controller 클래스에서 Service, DAO 와 작업을 함

4.Controller 클래스에서 ModelAndView 에 값을 저장 해서 Dispatcher Servlet 으로 돌아감

- DB연동 끝나고 돌아와서 값 가져갈땐 Model 객체 이나 ModelandView 객체에 값을 저장해서 View페이지로 이동

- 이제 Client -> Dispatcher Servlet -> Controller 클래스까지 왔다

5. Dispatcher Servlet 에서 ViewResolver 의 prefix, suffix 를 붙여서 해당 View 로 간다

 

ViewResolver

- 여기선 View파일들이 저장될 위치를 저장해야함

- 이 ViewResolver 로 View파일들이 어디에 저장될지 위치를 설정함

- 환경설정 파일 WEB-INF/spring/appServlet/servlet-contest.xml 을 열어보면 위치가 나와있다 "/WEB-INF/views/" 로 설정되어있음

- 즉 home.jsp 가 어디에 저장되어있는지 설정하는거

 

- servlet-contest.xml

- HomeController 클래스의 home() 에서 return "home" 을 썻던 이유

1) 여기서 prefix로 설정된 /WEB-INF/views/ 를 생략한것임

2) suffix로 설정된 확장자인 .jsp 도 생략해서 "home" 이라 썼던 것

- 그걸 생략해야만 jsp 파일로 찾아간다

 

- Controller 에서 리턴시 Dispatcher Servlet 으로 갔다가 ViewResolver 로 가서 prefix 와 suffix 를 붙여서 home.jsp 인 View로 찾아가는것임!!

 

6. View 에서 EL 등으로 출력

- home.jsp 에서 "serverTime" 을 EL로 출력하는 것임

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	Hello world!  
</h1>

<P>  The time on the server is ${serverTime}. </P>
</body>
</html>

 

흐름 정리

- 여기까지 Client -> Front Controller -> Controller(Service/DAO와 작업) -> Front Controller -> ViewResolver -> View 이다

 

Spring 환경 설정 파일 2가지

1. servlet-context.xml

2.  root-context.xml : 오라클과 연동시 DB연동과 관련된 내용이 들어감, Bean 객체가 여기 들어감

- 둘 다 자동으로 실행되지 않는다, web.xml 에 등록해서 사용해야함

- web.xml 은 Apache Tomcat 구동시 자동으로 가장 먼저 실행됨, 두 환경 설정 파일을 연쇄적으로 실행되도록 하는 것

 

- web.xml

 

web.xml 에 들어가는 주요 3가지 내용

1. DispatcherServlet 위치 설정 : <servlet-class> 태그 안에 있다

2. 환경설정 파일 2개 불러오기 : root-context.xml 과 servlet-context.xml 파일

3. 한글 인코딩 (아직 안들어가있다)


Spring DI (Dependency Injection)

- 번역 : 의존성 주입

- Spring 에서 가장 많이 사용되고 중요한 개념

 

IoC (Inversion of Control)

- 번역 : 제어의 역행

- DI 와 함께 중요한 개념

- 기존에 개발자들이 빈 객체를 관리해 오던 개념에서 빈 관리를 컨테이너에서 처리한다는 의미

 

- 클라우드의 spring/소스/ch01 zip 파일을 압축 해제 후 import

- Maven Project는 모두 이렇게 import

- import 한 프로젝트 ch01 는 현재 Web Application 이 아닌 Application 이라서 webapp 폴더가 없다

- 일반적인 자바개발을 하는 프로젝트 처럼 되어있음

- pom.xml 파일을 열어보자

- 현재 Spring 4.3.6 버전을 사용하고 있다

 

Spring DI 예제 1 (기존 시스템, 기존 자바 방식)

- src/main/java/sample01/ 안의 파일들

- Ex01.java : 메인메소드를 가진 클래스이다, MessageBeanKr 클래스로 객체 mb 생성, mb.sayHello("Spring") 호출

- MessageBeanEn.java : 매개변수로 전달된 값을 출력하는 메소드 sayHello() 가 있다

- MessageBeanKr.java : 매개변수로 전달된 값을 출력하는 메소드 sayHello() 가 있다

- 특정 클래스의 메소드를 호출하려면 가장 먼저 그 클래스로 객체를 생성해야함

- 기존 시스템, 기존 자바에서 사용하는 방식임

- MessageBeanKr 의 sayhello() 메소드 호출하고 싶으면 Ex01 클래스에서 MessageBeanKr 클래스로 객체를 생성 후 메소드 호출해야함

 

- Spring 에서는 이런방식을 쓰지 않는다

- 이 방식은 Application 에서 직접 MessageBeanKr 클래스 객체를 생성해서 메소드를 사용하므로 두 클래스 사이에 의존성이 강하다는 표현을 함

- 직접 클래스로 객체를 생성하는 것보다 의존관계를 낮출 수 있는 방법을 예제 2에서 설명

 

Spring DI 예제 2 (의존도를 조금 낮추는 방식)

- src/main/java/sample02/ 안의 파일들

- Ex01.java : 메인메소드를 가진 클래스이다, 가장 먼저 MessageBeanKr() 클래스로 객체 생성하고 왼쪽은 부모 인터페이스로 받음, 즉 업캐스팅하여 객체 mb 생성 후 mb.sayHello() 호출

- MessagBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanEn.java : MessageBean 인터페이스를 상속하고 sayHello() 를 오버라이딩, "Hello" 출력

- MessageBeanKr.java : MessageBean 인터페이스를 상속하고 sayHello() 를 오버라이딩, "안녕하세요" 출력

- MessageBean 인터페이스를 MessageBeanKr, MessageBeanEn 클래스에서 상속하고 추상메소드 sayHello() 를 메소드 오버라이딩

- 부모 인터페이스를 만들어서 추상클래스를 갖도록 하고 나머지 구현 클래스들은 그 인터페이스를 상속받고 메소드를 오버라이딩

- 이런 경우엔 결합력이 낮아지지만 여전히 결합력이 높다

- 이런 방식도 Spring 에서 쓰지 않음, 좀 더 유연한 방식을 쓰려고 한다

 

Spring DI 예제 3 (Spring 에서 사용하는 방식, IoC 제어의 역행)

- src/main/java/sample03/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- MessagBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanEn.java : MessageBean 인터페이스를 상속하는 구현클래스, sayHello() 를 오버라이딩, "Hello" 출력

- MessageBeanKr.java : MessageBean 인터페이스를  상속하는 구현클래스, sayHello() 를 오버라이딩, "안녕하세요" 출력

 

- Ex01.java

package sample03;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

public class Ex01 {
	public static void main(String[] args) {
		 BeanFactory bf = new XmlBeanFactory(new FileSystemResource("beans01.xml"));
//		ApplicationContext bf = new FileSystemXmlApplicationContext("beans01.xml");
		// MessageBean mb = bf.getBean("mb", MessageBean.class);
		MessageBean mb = (MessageBean) bf.getBean("mb");
		// MessageBean mb = bf.getBean(MessageBean.class);
		// MessageBean mb = (MessageBean)bf.getBean("a");
		mb.sayHello("Spring");
	}
}

- 의존도를 낮추기 위해 직접 Java 클래스끼리 객체를 생성하지 않는다

- 대신 beans01.xml 파일에서 객체를 생성하고 있다

1. "bean01.xml" 파일을 읽어와서 BeanFactory 객체 bf 를 만듬 (혹은 ApplicationContext 객체)

2. bf.getBean("mb") 로 beans01.xml 에서 "mb" 라는 id를 가진 객체를 가져와서 mb 로 리턴받음 (+ 다운캐스팅 필수)

3. mb.sayHello("Spring") 으로 출력함

 

- xml 파일들 저장되는 위치 크게 3곳인데 현재 beans01.xml 은 프로젝트 하위에 저장되어있음

- beans01.xml : Spring 의 환경설정 파일이다,

- 이 파일 또한 자동으로 실행되지 않는 파일이므로 Ex01.java 의 메인메소드에서 불러와야 사용 가능한것,

- 그래서 Ex01 의 메인 메소드에서 이 beans01.xml 을 불렀다

- beans01.xml 이 현재 프로젝트 하위에 있다 -> Ex01.java 에서 그냥 이름만으로 읽어오면 됨

		 BeanFactory bf = new XmlBeanFactory(new FileSystemResource("beans01.xml"));

 

- beans01.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">
	
	<!-- MessageBeanKr mb = new MessageBeanKr() -->
	<bean id="mb" class="sample03.MessageBeanKr" name="a"></bean>
	
	<!--  Constructor DI -->
	<bean id="mb2" class="sample04.MessageBeanImpl">
		<constructor-arg value="도깨비">
			<!-- <value>박보검</value> -->
		</constructor-arg>
		<constructor-arg value="안뇽">
			<!-- <value>Hello</value> -->
		</constructor-arg>
	</bean>
	
</beans>

- Spring 환경설정 파일

- beans 가 루트 엘리먼트

- 안에 bean 객체를 생성하는 코드가 있다, 마치 JSP 의 useBean action tag 로 객체 생성하는 것 처럼 생성

- 필요할때마다 beans 안에 bean 을 추가한다

- 이 파일의 동작 시점 : 자동 실행되지 않고, 메인메소드에서 beans01.xml 을 읽어올때 실행됨

 

객체 생성 1 (beans01.xml 부분)

	<!-- MessageBeanKr mb = new MessageBeanKr() -->
	<bean id="mb" class="sample03.MessageBeanKr" name="a"></bean>

- bean 태그에서 객체 생성

- id 속성에 객체명 작성, 이 메인메소드에서 id 값으로 이 객체를 불러온다

- class 속성에 패키지부터 클래스까지의 경로를 설정 ex) sample03.MessageBeanKr

- 위의 주석은 bean 태그와 같은 코드를 Java 로 쓴 것이다

- 이 beans01.xml을 sample03 Ex01.java의 메인메소드에서 불러와서 여기서 생성한 객체를 받으므로 sample03 Ex01.xml의 메인메소드에서 객체를 쓸 수 있다

- 즉, 객체를 생성해서 받았으므로 Ex01.java 에서 sample03.MessageBeanKr 의 메소드 사용 가능

 

흐름 설명

- Ex01 클래스 메인 메소드에서 beans01.xml 파일을 불러온다

+ beans01.xml 은 자동으로 실행되는 파일이 아니므로 메인 메소드에서 읽어오는 것

- 이 파일 beans.xml 을 읽어서 MessageBeanKr 객체를 생성함, 즉 메모리상에 새로운 기억공간을 생성됨

- Ex01 클래스에서 getBean() 메소드로 bean 의 id 값으로 해당 객체를 가져와서 받음

- 그럼 Ex01 클래스 메인메소드에서 MessageBeanKr 객체 사용 가능, 메소드 sayHello() 실행 가능

 

+ 직접 New 연산자로 객체 생성 후 메소드로 호출하는게 아니라, 객체 생성을 Spring 의 환경설정 파일에서 한다

+ xml 파일에서 먼저 처리,즉 제어가 이전과는 반대이다, 제어의 역행(IoC)이라는 말을 씀

 

- 아직 DI 가 아니다, 이게 더 발전한게 DI 이다

- 클래스의 필드값은 생성자 또는 setter 메소드로 설정한다

- Injection : 생성자로 값을 할당하는 것을 Injection 이라고 함 

- 이게 DI와 연관이 있다


DI (Dependency Injection)

- 빈 간의 의존 관계를 컨테이너 에서 설정하고 관리 한다는 개념

- Constructor DI(Dependency Injection) : 빈 간의 의존 관계를 설정하기 위해 생성자를 이용

- Setter DI(Dependency Injection) : 빈 간의 의존 관계를 설정하기 위해 setter 메소드를 이용

 

DI 쉽게 설명

- 하나의 클래스에는 필드가 있다, 필드의 접근제어자가 Private 일때 값을 설정하는 방법은 2가지 뿐

1) 생성자의 매개변수로 필드값을 초기화

- 일반적으로 객체가 생성될때 생성자가 호출됨

- 생성자를 호출해서 매개변수로 전달되는 값을 해당 필드로 할당한다, 이 개념이 Spring 에서 DI 개념으로 확장됨

- 자바에서는 할당,  Spring 에서는 주입(Injection) 이라 함

- 생성자로 주입하는 것을 Construct DI 라고 함

2) setter 메소드

- setter 메소드의 매개변수로 값을 할당(주입) 하는 것을 Setter DI 라고 함

- Spring 환경설정 파일에서 빈 객체를 생성하면서 환경설정 파일에서 Construct DI, Setter DI 처리를 함

+ 객체가 생성될때 생성자도 호출되므로

 

Spring DI 예제 4 (IoC 를 확장시킨 DI 개념)

- src/main/java/sample04/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- MessagBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanImpl.java : MessageBean 인터페이스를  상속하는 구현클래스, sayHello() 메소드를 오버라이딩함, 아래에서 코드 설명

 

- MessageBeanImpl.java

package sample04;

public class MessageBeanImpl implements MessageBean {
	private String name;
	private String greet;

	public MessageBeanImpl(String name, String greet) {
		this.name = name;		// 도깨비
		this.greet = greet;		// 안뇽
	}

	public void sayHello() {
		System.out.println(name + " ! " + greet);
	}
}

이 클래스 구성요소

1) 필드가 있다

- 접근제어자 private 이므로 객체 생성 후 필드 값 설정을 생성자의 매개변수로 할당(주입)해야함

- 이 예제는 Setter 대신 생성자로 매개변수 할당하는 예제

2) 생성자 : 객체 생성시 호출되며 매개변수로 필드값 초기화

3) 메소드 : 필드를 출력하는 역할

- 할당(주입) 하는걸 이제 Java 클래스에서 하지 않고, Spring 의 환경설정 파일에서 객체 생성과 주입까지 담당

 

- Ex01.java

package sample04;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new FileSystemXmlApplicationContext("beans01.xml");
		MessageBean mb = (MessageBean) ac.getBean("mb2");
		mb.sayHello();
	}
}

- Spring 의 환경설정 파일 beans01.xml 은 여기서 읽어줘야 실행됨

- id 가 "mb2" 인 객체를 불러서 MessageBean 객체 mb 로 받는다

 

- beans01.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">
	
	<!-- MessageBeanKr mb = new MessageBeanKr() -->
	<bean id="mb" class="sample03.MessageBeanKr" name="a"></bean>
	
	<!--  Constructor DI -->
	<bean id="mb2" class="sample04.MessageBeanImpl">
		<constructor-arg value="도깨비">
			<!-- <value>박보검</value> -->
		</constructor-arg>
		<constructor-arg value="안뇽">
			<!-- <value>Hello</value> -->
		</constructor-arg>
	</bean>
	
</beans>

- 두번째 bean 객체를 보자

 

Cunstructor DI 부분 (beans01.xml 부분)

	<!--  Constructor DI -->
	<bean id="mb2" class="sample04.MessageBeanImpl">
		<constructor-arg value="도깨비">
			<!-- <value>박보검</value> -->
		</constructor-arg>
		<constructor-arg value="안뇽">
			<!-- <value>Hello</value> -->
		</constructor-arg>
	</bean>

- sample04 의 MessageBeanImpl 클래스로 bean 객체 mb2 를 생성

- 객체 생성시 생성자가 호출된다, 생성자의 매개변수를 통해서 값을 주입하는 것임

- 생성자 매개변수의 첫번째 매개변수 자리 name 이란 필드값으로  value 의 "도깨비" 값이 할당(생성자 의존성 주입) 된다

- 생성자 매개변수 두번째에 매개변수 자리 greet 란 필드값으로 value 의 "안녕" 이란 값이 할당(생성자 의존성 주입) 된다

- 이 순서는 MessageBeanImpl.java 클래스에 작성된 생성자의 매개변수 순서이다, 그 생성자는 반드시 만들어져 있어야함 

이전의 Java vs Spring

- Java 에선 생성자의 매개변수로 값을 설정했지만 이젠 여기 Spring 의 환경설정 파일에선 <constructor-arg> 사용

- Java 에선 생성자에 매개변수를 2개 이상 전달가능했지만, 여기선 따로 따로 설정해야함, 순서대로 값이 전달된다

 

- 객체 생성 및 생성자 의존성 주입(할당) 시점 : Ex01 클래스의 메인메소드에서 beans01.xml 을 읽어왔을때

 

+ 지금은 어노테이션이 안되어있으므로 일일히 bean 객체 생성하고 생성자 의존 주입하고 있다

+ 나중에는 이렇게 bean 객체 생성하지 않고 어노테이션으로 바뀌면 코드가 간결해짐

 


Spring DI 예제 5 (예제 4와 비슷)

- src/main/java/sample05/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- Vehicle.java : 부모 인터페이스, 추상메소드 ride()

- AirPlain.java : Vhicle 인터페이스를  상속하는 구현클래스, ride() 메소드를 오버라이딩함, ride() 에서 매개변수로 전달된거 출력

- Car.java : Vhicle 인터페이스를  상속하는 구현클래스, ride() 메소드를 오버라이딩함, ride() 에서 매개변수로 전달된거 출력

 

- Vehicle.java

package sample05;

public interface Vehicle {
	void ride(String name);
}

 

- Car.java

package sample05;

public class Car implements Vehicle {
	public void ride(String name) {
		System.out.println(name + "(이)가 자동차를 탄다");
	}
}

 

- Ex01.java

package sample05;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new FileSystemXmlApplicationContext("beans02.xml");
		Vehicle vh = (Vehicle) ac.getBean("vh");
		vh.ride("철수");
	}
}

- beans02.xml 파일을 읽어오고 있다

- Spring 환경설정 파일이 어디에 있는지에 따라 읽어오는 방법이 다르다 * 아래에 설명

- getBean() 메소드로 id 가 "vh" 인 Car 클래스의 객체 읽어와서 Vehicle 객체 vh 로 받고 ride() 호출시 Car 의 오버라이딩 메소드 ride() 가 실행됨

+ Car 클래스로 다운캐스팅해서 Car 클래스 객체로 받아도 되지만 결합도 낮추기 위해 인터페이스 객체로 받았다

Spring 환경설정 파일 위치에 따른 읽어오는 법

- 지금은 bean02.xml 이 현재 프로젝트 바로 하위에 있으므로 이름만으로 읽어와도 된다

- 같은 패키지안에 저장시에는 패키지명을 앞에 붙임

- resorces 디렉토리 안에 저장시에는 앞에 classpath: 를 붙여서 읽어옴

- Web Application 에서는 주로 resources 디렉토리 안에 저장되는 경우가 많음, 지금 ch01 프로젝트는 그냥 Application 임

 

- beans02.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">
	
	<bean id="vh" class="sample05.Car"/>
	<!-- <bean id="vh" class="sample05.Airplane"/> -->
	
	<!-- Constructor DI -->
	<bean id="vh2" class="sample06.VehicleImpl">
		<constructor-arg value="철수"></constructor-arg>
		<constructor-arg value="자전거"></constructor-arg>
	</bean>
</beans>

 

- 첫번째 bean 객체는 sample05 의 Car 클래스로 객체 vh 를 생성

- 생성된 bean 은 싱글톤으로 만들어진다, 싱글톤으로 만들어졌이므로 공유가 됨, 다른 bean 안에서 이 객체 vh 불러서 쓸 수 있다


Spring DI 예제 6 (예제 4,5와 비슷)

- src/main/java/sample06/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- Vehicle.java : 부모 인터페이스, 추상메소드 ride()

- VehicleImpl.java : Vhicle 인터페이스를 상속하는 구현클래스, ride() 메소드를 오버라이딩함, ride() 에서 매개변수로 전달된거 출력,

 

- VehicleImple.java

package sample06;

public class VehicleImpl implements Vehicle {
	private String name;
	private String rider;

	public VehicleImpl(String name, String rider) {
		this.name = name;		// 철수
		this.rider = rider;		// 자전거
	}

	public void ride() {
		System.out.println(name + "(이)가 " + rider + "(을)를 탄다");
	}
}

- VehicleImple.java 엔 필드, 생성자, ride() 메소드가 있다

- 여기에 생성자를 생성해둬야 Spring 환경설정 파일에서 할당(DI) 가능

 

- Ex01.java

package sample06;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new FileSystemXmlApplicationContext("beans02.xml");
		Vehicle vh = (Vehicle) ac.getBean("vh2");
		vh.ride();
	}
}

- 여기서 FileSystemXmlApplicationContext() 로 beans02.xml 을 읽어올때 객체가 생성되고 생성자가 호출됨

- getBean() 메소드로 id 가 "vh2" 인 Car 클래스의 객체 읽어와서 Vehicle 객체 vh 로 받고 ride() 호출시 VehicleImpl 의 오버라이딩 메소드 ride() 가 실행됨

- 즉 DI 개념은 Java 클래스인 Ex01.java에서 생성자의 매개변수를 통해 값을 주는게 아니라 값도 beans02.xml 에서 할당(의존성 주입)하는 것임

- Ex01.java 실행시

 

- beans02.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">
	
	<bean id="vh" class="sample05.Car"/>
	<!-- <bean id="vh" class="sample05.Airplane"/> -->
	
	<!-- Constructor DI -->
	<bean id="vh2" class="sample06.VehicleImpl">
		<constructor-arg value="철수"></constructor-arg>
		<constructor-arg value="자전거"></constructor-arg>
	</bean>
</beans>

- 아래쪽 코드이다, 두번째 bean 객체

- simple06 의 Car 클래스로 bean 객체 "vh2" 를 만들고 있다

- 객체 vh2 생성시 생성자가 호출되는데, 생성자의 매개변수 순서대로 name 에 "철수", rider 에 "자전거" 값을 할당(주입)

- value 속성값이 주입되는 값이다

- 객체 생성 및 생성자 의존성 주입(할당) 시점 : Ex01 클래스의 메인메소드에서 beans02.xml 을 읽어왔을때

+ 어노테이션 처리시 일일히 bean 객체 생성하지 않음

 


Spring DI 예제 7 (예제 6와 비슷, Setter DI 사용)

- src/main/java/sample07/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함

- 이번에는 생성자가 아니라 setter 메소드로 값을 할당할 것

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- messageBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanImpl.java : messageBean인터페이스를 상속하는 구현클래스, sayHello() 메소드를 오버라이딩함, ride() 에서 매개변수로 전달된거 출력,

 

- MessageBeanImpl.java

package sample07;

public class MesageBeanImpl implements MessageBean {
	private String name;		// property
	private String greet;

	public void setName(String name) {
		this.name = name;		// 길동
	}

	public void setGreet(String greet) {
		this.greet = greet;		// 안녕
	}

	public void sayHello() {
		System.out.println(name + " !! " + greet);
	}
}

- 이번에는 MessageBeanImpl 클래스에 필드, setter 메소드, 일반 메소드 sayHello() 가 있다

- Setter 메소드의 매개변수로 값을 할당할 것

- 여기 Setter 메소드가 만들어져 있어야 Spring 환경설정 파일에서 Setter DI 가능

 

- Ex01.java

package sample07;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac =
				 new ClassPathXmlApplicationContext("/sample07/beans07.xml");
//				 new GenericXmlApplicationContext("/sample07/beans07.xml");
		MessageBean mb = (MessageBean) ac.getBean("mb");
		mb.sayHello();
	}
}

- 객체 생성 및 생성자 의존성 주입(할당) 시점 : Ex01 클래스의 메인메소드에서 beans07.xml 을 읽어올때 메모리에 공간 생성

- Spring 환경설정 파일이 같은 sample07 패키지에 들어있으므로 beans07.xml 앞에 패키지명을 붙여줘야 읽어올 수 있다!

ex) "/sample07/beans07.xml"

- mb 객체가 구해졌으므로 메소드 오버라이딩 되어있는 MessageBeanImpl 의 sayHello() 실행 가능

 

- beans07.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">
	
	<!-- Setter DI -->
	<bean id="mb" class="sample07.MesageBeanImpl">
		<property name="name">
			<value>길동</value>
		</property>
		<property name="greet" value="안녕"/>
	</bean>
	
</beans>

- sample07 의 MessageBeanImpl 클래스로 객체 mb 를 만든다

- setter 메소드(MessageBeanImplsetName(), setGreet() 를 호출해서)로 객체의 필드값을 설정할 것

- property 태그 name 속성으로 "값이 주입될 프로퍼티명(필드명)"을 쓴다

- property 태그 value 속성값으로 또는 property 태그 안의 value 태그 안에 "주입할 값"을 쓴다

ex) name 속성에 "name" 을 쓰면 필드 name 에 value 로 설정된 "안녕" 이 할당됨

ex) name 속성에 "greet" 을 쓰면 필드 greet 에 value 로 설정된 "안녕" 이 할당됨

- Setter DI = Setter 의존성 주입 = Setter 메소드의 매개변수로 주입하는 것

+ 필드 = 프로퍼티

 

Setter DI 사용하는 곳

+ 90% 이상이 Setter DI 사용, 대부분의 환경설정 파일에서 Setter DI 를 한다

- MyBatis 환경설정파일에서 property 태그를 썼다 * 아래 사진

- servlet-context.xml 에서도 property 태그 사용함 * 아래 사진

 

정리

- Constructor DI : Constructor 로 프로퍼티를 할당하는 것 , constructor-arg 사용

- Setter DI : Setter 메소드의 매개변수로 프로퍼티를 할당, property 사용

- 둘 다 Spring 환경설정 파일에서 하는 작업이다

- 이 작업은 나중에 어노테이션 기반으로 바뀌므로 사용방법이 조금 달라지게 됨


Spring DI 예제 8 (Spring 환경설정 파일이 resources 폴더 안에 있음, Setter DI 와 Constructor DI 둘 다 사용)

- src/main/java/sample08/ 안의 파일들

- 이번에는 beans08.xml 파일이 resorces 폴더 안에 있다

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함, setter DI 와 Constructor DI 둘 다 사용

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- messageBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanImpl.java : messageBean인터페이스를 상속하는 구현클래스, sayHello() 메소드를 오버라이딩함, sayHello() 에서 매개변수로 전달된거 출력,

 

- MessageBeanImpl.java

package sample08;

public class MessageBeanImpl implements MessageBean {
	private String name;
	private String greet;

	public MessageBeanImpl(String name) {
		this.name = name;	// 돌쇠
	}

	public void setGreet(String greet) {
		this.greet = greet;	// 안뇽
	}

	public void syaHello() {
		System.out.println(name + " ! " + greet);
	}
}

- 생성자도 있고 setter 메소드도 있다

- 즉 Contstructor DI 와 Setter DI 를 혼용해서 사용할 것

- 필드 name 은 생성자로 값 설정하고, 필드 greate 는 setter 메소드로 값 설정할 것

 

- Ex01.java

package sample08;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:beans08.xml");
		MessageBean mb = (MessageBean) ac.getBean("mb");
		mb.syaHello();
	}
}

-  bean08.xml 파일을 읽어서 객체 생성 및 DI 후 그 객체를 id "mb" 로 받아서 오버라이딩 된 메소드 호출

- 이번에는 beans08.xml 파일이 resorces 폴더 안에 있다

- 이때 파일명 앞에 classpath: 를 붙여줘야 읽어 올 수 있다

+ Web Application 에서는 Spring 환경설정 파일이 주로 resources 디렉토리 안에 저장되어있음

 

Spring 환경설정 파일 위치에 따른 읽어오는 법 (중복)

1. 현재 프로젝트 바로 하위에 있다면 이름만으로 읽어와도 된다

2. 같은 패키지안에 저장되어 있다면 패키지명을 앞에 붙임

3. resorces 디렉토리 안에 저장시에는 앞에 classpath: 를 붙여서 읽어옴

- Web Application 에서는 주로 resources 디렉토리 안에 저장되는 경우가 많음, 지금 ch01 프로젝트는 그냥 Application 임

 

- beans08.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">
	
	<bean id="mb" class="sample08.MessageBeanImpl">
		<constructor-arg value="돌쇠"></constructor-arg>
		<property name="greet" value="안뇽"></property>
	</bean>
	
</beans>

- 프로퍼티 name 은 생성자로 값 "돌쇠" 설정, MessageBeanImpl 클래스의 생성자의 매개변수는 하나이므로 이 constructor-arg 가 그 하나의 매개변수로 들어감

- 프로퍼티 greet 은 setter 메소드로 값 "안뇽" 설정, 프로퍼티 greet의 setter 메소드인 setGreet() 가 MessageBeanImpl 클래스에 있으므로 여기서 property 태그로 name 속성에 필드값 "greet" 를 써서 값 설정

 


Spring DI 예제 9 (어려움)

- src/main/java/sample09/ 안의 파일들

- new 연산자로 객체를 직접 생성하지 않음, Spring 의 환경설정 파일에서 객체를 생성함

- 또한 Spring 의 환경설정 파일에서 필드값을 초기화함

- Ex01.java : 메인메소드를 가진 클래스이다, 아래에서 코드 설명

- messageBean.java : 부모 인터페이스, 추상메소드 sayHello()

- MessageBeanImpl.java : messageBean 인터페이스를 상속하는 구현 클래스, sayHello() 메소드를 오버라이딩함, sayHello() 에서 매개변수로 전달된거 출력

- Outputer.java : 인터페이스, 추상메소드 output()

- FileOutputer.java : Outputer 인터페이스를 상속하는 구현 클래스, output() 메소드를 오버라이딩함

- 이 구현클래스 2개로 bean 객체 따로 만듬

 

- MessageBeanImpl.java

package sample09;

public class MessageBeanImpl implements MessageBean {
	private String name;
	private String greet;
	private Outputer output;

	public void setName(String name) {
		this.name = name;					// name="홍길동"
	}

	public void setGreet(String greet) {
		this.greet = greet;					// greet="Hello !"
	}
	// Outputer output = new FileOutputer()
	public void setOutput(Outputer output) {
		this.output = output;				// output=out
	}

	public void sayHello() {
		String msg = name + "님 " + greet;
		System.out.println(msg);
		output.output(msg);
	}
}

- 필드, setter 메소드, 메소드 로 구성되어있다.

- 프로퍼티에 값을 할당하는 방법 중 Setter 메소드를 사용할 것

- 인터페이스 Outputer 객체 output 의 setter 메소드 setOutput() , 매개변수는 인터페이스 Outputer 형

- setOut() 의 매개변수는 부모 인터페이스인 Output 형이고, 여기 매개변수에 주입되는 값은 bean09.xml 에서 만들어진 자식인 FileOutputer 객체가 주입됨

+ 인터페이스로는 객체 생성을 못하므로 자식 구현 클래스로 객체를 생성해서 업캐스팅 해야함

- 그럼 프로퍼티 out 은 FileOutpter 객체를 받은 것임

- MessageBean 의 sayHello() 메소드를 여기서 오버라이딩

- sayHello() 안에서 초기화된 필드값을 출력하고, FileOutputer 객체 output 으로 FileOupter 클래스에서 오버라이딩된 메소드 output() 호출하자

 

* 잘 이해가 안되면 아래를 읽고 다시 여기 돌아와서 읽어보기

 

- FileOutputer.java

package sample09;

import java.io.FileWriter;
import java.io.IOException;


public class FileOutputer implements Outputer {
	private String fileName;

	public void setFileName(String fileName) {
		this.fileName = fileName;	// fileName="test.txt"
	}

	public void output(String msg) {
		try {
			FileWriter fw = new FileWriter(fileName);
			fw.write(msg);
			fw.close();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}

- 상속받은 인터페이스 Outputer 의 추상메소드 output() 을 오버라이딩 해야한다

- FileOutputer 클래스에서는 필드가 있고, setFileName() 이라는 setter 메소드가 있고 output() 을 오버라이딩하고 있다

- output() 에서 File 객체를 만들어서 파일을 생성하고 있다

 

- Ex01.java 실행시 "홍길동님 Hello !" 출력 후 test.txt 파일이 만들어진다

- 그 파일안에 "홍길동님 Hello !" 가 들어있음

 

- beans09.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">
	
	<bean id="mb" class="sample09.MessageBeanImpl">
		<property name="name" value="홍길동"></property>
		<property name="greet" value="Hello !"></property>
		<property name="output" ref="out"></property>
	</bean>
	<bean id="out" class="sample09.FileOutputer">
		<property name="fileName" value="test.txt"></property>
	</bean>
	
</beans>

<첫번째 객체>

- 첫번째 bean 객체는 MessageBeanImpl 로 만들어진 객체 mb

+ MessageBeanImple 에는 필드 name, greet, output 이 있다, 이때 output 의 자료형은 Output

- 프로퍼티 name, greet, output 에 값 "홍길동", "Hello !", 그리고 FileOutputer 객체 out 을 Setter 메소드로 주입하고 있다

- 아래에서 자식 구현클래스로 만든 FileOutputer 의 객체 out 을 setOutput() 의 매개변수에 주입하는 것이다

- 다른 bean 의 id 값을 참조할 떄는 value 대신 ref 를 사용한다

+ ref 는 다른 bean 의 id 값만 들어감

 

<두번째 객체> : 여기 설명을 첫번째 객체 설명보다 먼저 보기

- 두번째 bean 객체는 FileOutputer 로 만들어진 객체 out

- FileOutputer 클래스로 만든 객체 out 의 프로퍼티 fileName 의 값을 "test.txt" 로 주입하는데 이때 Setter DI 사용하고 있다

 

- beans09.xml 을 메인메소드에서 불러올떄 beans09.xml 의 모든 객체가 생성되고 주입된다 (순서 상관X)

- 그러므로 첫번째 객체를 생성할때 두번째 객체 out 을 참조(불러올수)할 수 있는 것이다!

 

 

 

실행되는 순서대로 따라가보자

- Ex01.java (메인메소드)

package sample09;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Ex01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("/sample09/beans09.xml");
		MessageBean mb = (MessageBean) ac.getBean("mb");
		mb.sayHello();
	}
}

- sample09 패키지의 beans09.xml 을 읽어와서 ApplicationContext 객체 ac를 구함

- ac.getBean("mb") 로 beans09.xml 에서 MessageBeanImpl 클래스로 생성된 mb 객체를 구해옴

- 그럼 MessageBeanImpl 클래스의 오버라이딩 된 sayHello() 메소드 실행 가능

 

- MesageBeanImpl.java

package sample09;

public class MessageBeanImpl implements MessageBean {
	private String name;
	private String greet;
	private Outputer output;

	public void setName(String name) {
		this.name = name;					// name="홍길동"
	}

	public void setGreet(String greet) {
		this.greet = greet;					// greet="Hello !"
	}
	// Outputer output = new FileOutputer()
	public void setOutput(Outputer output) {
		this.output = output;				// output=out
	}

	public void sayHello() {
		String msg = name + "님 " + greet;
		System.out.println(msg);
		output.output(msg);
	}
}

- 객체 생성시 호출되었으므로 필드값도 초기화해야함, 초기화도 beans09.xml 에서 Setter 메소드로 해준다

- 그럼 필드가 모두 초기값을 갖게 된다

- Ex01.java 의 메인메소드에서 sayHello() 를 호출하므로 거기서 필드 name ,greet 값을 출력하고 out 객체로 output() 메소드를 실행한다

 

- 객체 out 은 Output 인터페이스를 상속받은 FileOutputer 객체를 받은 것이므로 FileOutputer 에서 오버라이딩 된 output() 메소드를 호출하는 것임

- output 메소드를 호출하며 메세지를 매개변수로 가져감

 

- FileOutputer.java

package sample09;

import java.io.FileWriter;
import java.io.IOException;


public class FileOutputer implements Outputer {
	private String fileName;

	public void setFileName(String fileName) {
		this.fileName = fileName;	// fileName="test.txt"
	}

	public void output(String msg) {
		try {
			FileWriter fw = new FileWriter(fileName);
			fw.write(msg);
			fw.close();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}

- FileWriter 클래스는 파일을 생성할때 사용하는 클래스

- 생성자 안에는 생성할 파일명을 넣어야함, 필드 fileName 은 Setter 에 의해 이미 주입된 상태이므로 fileName 을 넣으면 된다,  (fileName 은 beans09.xml 에서 "test.txt" 로 주입되었음)

- output() 의 매개변수로 전달된 메세지를 사용해서 파일을 만들고 메세지를 그 파일에 작성

- test.txt 파일이 생성되었고 test.txt 를 열어보면 메세지가 적혀있다


나중에 할 일

- 지금 하고 있는 ch01 프로젝트는 일반 Application 프로젝트이다, 그래서 webapp 폴더가 없음

- 나중에 Web Application 을 만들 땐 모든 Spring 환경설정 파일의 객체들을 한번에 root-context.xml 에서 생성함

- root-context.xml 에서 DB 관련된 내용이 들어가는데 이때 여기서 bean 들을 만들고 주입 (DI)

ex) SqlSession 객체를 구해올때도 root-context.xml 에서 bean 객체를 만들고 Setter DI 사용

- 그래서 나중엔 직접 bean 객체 생성하는 일은 하지 않음, 어노테이션으로 처리한다

- 이 root-context.xml 은 web.xml 에서 불러줘서 실행된다

- web.xml 은 서버 구동시 자동으로 실행됨

 

- 나중에 Web Application Spring 환경설정 파일은 resources 폴더안에 주로 지정된다

- beans09.xml 같은 객체를 만드는 Spring 환경설정 파일이 주로 resources 폴더 안에 들어감

- MyBatis 환경설정 파일, Mapper 파일도 resources 폴더에 저장됨

- 그래서 두 파일을 불러올 때 classpath: 를 앞에 붙여서 불러와야한다

 

- servlet-context.xml 에서는 DI 개념을 어노테이션으로 바꿔서 처리할 것 * 맛보기

<context:component-scan base-package="sample15"/>

[문항1]  

JSP와 오라클 데이터베이스를 JDBC(Java Database Connectivity) 방식으로 연동하는 코드를  작성하시오. [10점]


데이터베이스 이름 : xe
port 번호 : 1521
계정명 : scott
비밀번호 : tiger

 

// JDBC 방식
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "scott", "tiger");

 

 

 

[문항2]  

JSP의 내장객체 중 영역에 관련된 객체(request, session)를 이용하여 회원의 id를 각각 공유 설정하고, 공유된 id를 구해오는 코드를 각각 작성하시오.  [10점]


회원의 id : toto

	String id = "toto";
	request.setAttribute("rid", id);
	session.setAttribute("sid", id);
	
	String r_id = (String)request.getAttribute("rid");
	String s_id = (String)session.getAttribute("sid");

 

 

 

[문항3]  

사용자 폼 태그에서 method=post 방식으로 넘어온 한글 자료를 JSP에서 받을 때 한글을 깨지지 않게 하는 코드를 작성하시오. [10점]

 

		request.setCharacterEncoding("utf-8");

 

 

 

[문항4]  

다음 Java코드와 같은 역할을 하는 내용을 보여주는 JSP 표준액션 코드를 작성 하시오. [10점]


Car sonata = new Car();

<jsp:useBean id="sonata" class="Car"/>

 

 

 

[문항5]  

체크박스는 복수 개를 선택할 수 있다. 복수개가 선택된 값을 JSP에서 String 배열로 받으려면 request객체 하위의 어떠한 메서드를  사용해야 하는가? [10점]

 

답 : request 객체의 getParameterValues() 메소드를 사용하면 된다

 

 

 

[문항6]  

자바 서블릿 코드에서 출력 스트림 객체를 만드는 방법은? [10점]

 

PrintWriter out = response.getWriter();

 

 

 

[문항7]  

다음 보기는 JDBC를 활용하여 테이블에 데이터를 입력하는 프로그램이다. 괄호 (1) (2) (3) (4)를 채워서 완성하시오. [10점]
Class.forName((1));
Connection con=DriverManager.getConnection((2), "scott","tiger");
PreparedStatement pstmt = con.prepareStatement((3));
pstmt.setInt(1, deptno);
pstmt.setString(2, dname);
pstmt.setString(3, loc);
int result = pstmt.(4);
pstmt.close();
con.close();

 

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "scott","tiger");
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setInt(1, deptno);
pstmt.setString(2, dname);
pstmt.setString(3, loc);
int result = pstmt.executeUpdate();
pstmt.close();
con.close();

# 실수했다, sql 대신 SQL문이 들어가야함

# insert into 테이블명 values(?,?,?)

 

 

 

[문항8]  다음과 같은 조건에 맞는 게시판 양식을 작성하세요? [10점]


1) 값 전달 방식은 post 방식으로 한다.
2) 값이 전달될 파일명은 board.jsp 로 작성한다.
3) 입력 양식의 name값은 위에서부터 title, name, password, content 순서로 한다.
4) 확인 버튼은 submit으로 작성한다.
5) 게시판 양식 : boardform.html
    폼에서 넘어온 값을 받는 파일 : board.jsp
    자바빈 클래스 : board – BoardBean.java
boardform.txt 파일로 작성해서 제출하세요.

 

- boardform.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판</title>
</head>
<body>

<table border=1 align=center>
<form method="post" action="board.jsp">
	<caption>게시판</caption>
	<tr><th>제목</th>
		<td><input type=text name="title"></td>
	</tr>
	<tr><th>작성자</th>
		<td><input type=text name="name"></td>
	</tr>
	<tr><th>비밀번호</th>
		<td><input type=password size=50 name="password"></td>
	</tr>
	<tr><th>내용</th>
		<td><textarea cols=60 rows=10 name="content"></textarea></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="확인">
		</td>
	</tr>
</form>
</table>

</body>
</html>

 

 

 

[문항9]  위 8번 문제의 양식에서 넘어온 값을 저장하기 위한 자바빈(DTO) 클래스를 작성하세요? [10점]


1) 입력 양식의 name값은 위에서부터 title, name, password, content 순서로 한다.
2) 자바빈 클래스의 package는 board로 작성한다.
3) 자바빈 클래스는 BoardBean 으로 작성한다.
4) 게시판 양식 : boardform.html 
    자바빈 클래스 : board – BoardBean.java
boardbean.txt 파일로 작성해서 제출하세요.

 

- board/BoardBean.java

package board;

public class BoardBean {
	private String title;
	private String name;
	private String password;
	private String content;
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
}

 

 

[문항10]  위 8번 문제의 게시판 폼에서 넘어온 값을 액션태그를 사용하여 자바빈 클래스(DTO)에 저장하고 출력하는 프로그램을 작성하세요. [10점]


1) 한글값이 깨지지 않도록 처리한다.
2) 게시판 폼에서 넘어온 값을 액션태그를 이용해서 자바빈 클래스에 저장하고, 저장된 값을 출력하시오.
3) 게시판 양식 : boardform.html
    폼에서 넘어온 값을 받는 파일 : board.jsp
    자바빈 클래스 : board – BoardBean.java
board.txt 파일로 작성해서 제출하세요.

 

- board.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="board" class="board.BoardBean"/>
<jsp:setProperty property="*" name="board"/>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

제목 : <jsp:getProperty name="board"  property="title" /> <br>
작성자 : <jsp:getProperty name="board" property="name" /> <br>
비밀번호 : <jsp:getProperty name="board" property="password" /> <br>
내용 : <jsp:getProperty name="board" property="content" />

</body>
</html>

 

 

점수

98 / 100

 

 

 

복습

ORM (Object Relational Mapping)프레임워크

- MyBatis, JPA

 

JSP Model 2 - MyBatis 게시판 프로그램 

- 이전에 만들었던 JSP Model 2 게시판 프로그램에서 DB 연동을 Connection Pool 대신 MyBatis 와 연동한 것이다

- Connection Pool 사용한 JSP Model 2 게시판 프로그램  : https://laker99.tistory.com/129

 

컬럼 설명

-- 모델2 게시판
select * from tab;
select * from seq;
select * from model22;

create table model22(
	board_num number,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50),
	board_re_ref number,
	board_re_lev number,
	board_re_seq number,
	board_readcount number,
	board_date timestamp,
	primary key(board_num)
);

create sequence model22_seq
start with 1
increment by 1
nocache;
출처: https://laker99.tistory.com/140 [레이커 갓생일기:티스토리]

- 번호값이 들어가는 컬럼이 주로 Primary Key 가 된다

- 원문, 댓글 상관없이 board_num 은 sequence 가 들어간다

 

글 개수 (board.xml 부분)

	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>

- 돌려줄 값의 자료형이 int 이므로 returnType 에 int 를 써야 한다


JSP Model 2 - MyBatis 게시판 프로그램 : 원문 글 작성 기능

- index.jsp 또는 프로젝트를 실행하면 바로 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청한다

- index.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>

 모델2 게시판
 
<%
	response.sendRedirect("./BoardListAction.do");
%> 

<script>
//	location.href="./BoardListAction.do";
</script>

</body>
</html>

- location.href 로 목록을 가져오기 위한 "/BoardListAction.do" 요청을 해도 되고, sedRedirect 로 "/BoardListAction.do" 로 포워딩해서 요청해도 된다.

- Controller -> Service -> DAO -> Service -> Controller View 파일로 가면 목록이 나타나고 공유된 값들을 출력시켜줌

 

- 목록 페이지에서 '글쓰기' 를 누르면 "/BoardForm.do" 로 요청한다

- qna_board_list.jsp 부분

<a href="./BoardForm.do">글쓰기</a> <br>

 

- Controller 클래스로 간다

- Controller 클래스에서 "/BoardForm.do" 부분만

		// 글작성 폼	
		}else if(command.equals("/BoardForm.do")) {	
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_write.jsp");
		}

 

- 바로 View 페이지 qna_board_write.jsp 로 이동한다

- qna_board_write.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>MVC 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">MVC 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">파일 첨부</div>
		</td>
		<td>
			<input name="board_file" type="file"/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="등록">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- 첨부파일이 있을때는 form 에서 반드시 post 방식으로 전송, enctype 코드를 써야한다

- 입력하고 '등록' 을 누르면 "/BoardAddAction.do" 로 전송된다

+ 그럼 이 첨부파일을 원문 글 작성 Service 클래스인 BoardAddAction.java 에서 MultipartRequest 로 받아야함

 - "/BoardAddAction.do" 로 요청되었음을 확인 가능

 

- Controller 클래스에서 "/BoardAddAction.do" 부분만

		// 글작성
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 원문 글 작성 Service 클래스인 BoardAddAction.java 로 이동

- BoardAddAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import dao.BoardDAO;
import model.BoardBean;

public class BoardAddAction implements Action{
	
	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardAddAction");
		
		String path = request.getRealPath("boardupload");
		System.out.println("path:"+path);
		
		int size = 1024 * 1024; // 1MB
		
		MultipartRequest multi = 
			new MultipartRequest(request,
								 path,
					             size,
					             "utf-8",
						         new DefaultFileRenamePolicy());
		
		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result==1) {
			System.out.println("insert");
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(true);
		forward.setPath("./BoardListAction.do");
		
		return forward;
	}

}

첨부파일을 업로드 하기 위해 할 일

1) 첨부파일이 실제 저장되는 경로인 "boardupload" 의 진짜 path를 구한다

2) 업로드할 첨부파일 최대 크기를 정한다

3) MultipartRequest 객체를 생성하면서 첨부파일을 서버로 업로드시킴

- 매개변수가 5개인 생성자 사용하고 있다

- 여기서 한글값 인코딩을 하므로 request.setCharacterEncoding() 을 쓰지 않아도 됨

 

- DTO 객체 board 를 생성하고 그 객체에 앞의 원문 글 작성폼으로부터 넘어온 값들 (첨부파일명 포함) 을 MultipartRequest 객체 multi.getParameter() 로 받아서 저장시킨다

- 실제 서버에 저장된 첨부파일명을 multi.getFilesystemName() 으로 구해와서 DTO 객체 board 에 저장

+ 클라이언트가 업로드한 파일명을 구하는 메소드는 따로 있다

- DAO 객체 생성 후 DAO 의 insert() 메소드를 호출함, 매개변수로는 삽입할 데이터를 저장한 객체 board 를 넘김

<DAO insert() 에서 돌아온 후>

- ActionForward 객체 forward 를 생성하고 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청

+ 바로 목록 페이지 qna_board_list 로 가면 아무것도 없다, 먼저 목록을 가져오는 요청을 해야함

 

+ 첨부파일이 업로드되는 경로가 콘솔창에 찍혀 나온다

 

- MemberDAO.java 에서 SqlSession 객체를 구해오는 getSession() 부분

	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}

- MyBatis의 환경설정 파일을 읽어오면서 DB연동을 수행한다

- Builder 객체로 read 객체를 읽어서 SqlSessionFactory 객체 sf 를 생성

- sf 객체로 openSession(true) 로 세션을 구해옴, 이때 자동 커밋한다

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	//글작성(원문작성)
	public int insert(BoardBean board) throws Exception {
		int result=0;
		SqlSession session = getSession();
		result = session.insert("board_insert", board);		
		System.out.println("result:"+result);
		
		return result;
	}

- DAO 클래스에서 SQL문을 실행하는 것은 맞다! Mapper 파일에서 SQL문을 가져와서 여기서 실행함

- 메소드 insert() 는 SqlSession 에서 지원되는 메소드

+ SqlSession 은 MyBatis 에서 지원되는 인터페이스이다

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_insert)

	<!-- 글작성(원문) -->
	<insert id="board_insert" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},model22_seq.nextval,0,0,0,sysdate)
	</insert>

- paramteterType 속성으로 값을 받는다, 받는 값의 자료형을 써야하므로 DTO 의 alias 값인 "board" 가 들어감

- board_num 자리에는 model22_seq.nextval 로 sequence 로 값을 넣는다

- #{board_name} 은 넘어온 객체 board 에서 이름 컬럼을 가져오는 board.getBoard_Name() 의 의미이다

- 원문 글이므로 board_re_ref 컬럼 자리에는 sequence 로 값이 입력된다

- 원문 글 작성이므로 board_re_lev, board_re_seq,board_readcount 자리에는 0 을 입력

 

첨부파일명 컬럼 (board.xml 에서 id 가 "board_insert"인 태그의 부분)

#{board_file,jdbcType=VARCHAR}

- 첨부파일은 사용자가 첨부를 선택할 수도 있고, 하지 않을 수도 있다

- 첨부파일을 선택하지 않으면 객체 board의 board_file 에 null 이 들어감, 그럼 DB에 insert 할때 board_file 에 null 값이 들어감

- MyBatis는 null 값이 들어가는 것을 허용하지 않는다

- ,jdbcType=VARCHAR 를 하면 board_file 컬럼에 null 값을 허용도록 만들어주는 코드이다

- 이 jdbcType=VARCHAR 가 없으면 첨부파일을 선택하지 않고 글 작성시 오류가 발생한다

 

- 원문 글 작성 성공시 콘솔창

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 목록 가져오기 기능 / 목록 페이지

- 원문 글 작성 후 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩하며 요청함

- Controller 클래스로 이동한다

 

- Controller 클래스에서 "/BoardListAction.do" 부분만

		// 글목록	
		}else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 목록 가져오기 Service 클래스인 BoardListAction.java 로 이동

- BoardListAction.java

package service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;

public class BoardListAction implements Action{
	
	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardListAction");
		
		int page=1;
		int limit=10;
		
		if(request.getParameter("page")!=null) {
			page = Integer.parseInt(request.getParameter("page"));					
		}
		
		int startRow = (page-1) * limit + 1;
		int endRow = page * limit;
		
		List boardlist = null;
		BoardDAO dao = BoardDAO.getInstance();		
		int listcount = dao.getCount();
//		boardlist = dao.getList(startRow, endRow);
//		boardlist = dao.getList(page);

//      Map 처리	----------------------------------	
		Map map = new HashMap();
		map.put("start", startRow);
		map.put("end", endRow);
		
//		boardlist = dao.getList(map);		
		boardlist = dao.getList(page);		
//-------------------------------------------------		
		System.out.println("listcount:"+listcount);
		System.out.println("boardlist:"+boardlist);		
		
		int pageCount = listcount/limit + ((listcount%limit==0) ? 0:1);
		
		int startPage = ((page-1)/10) * limit + 1;
		int endPage = startPage + 10 - 1;
		
		if(endPage > pageCount) endPage = pageCount;
		
		request.setAttribute("page", page);
		request.setAttribute("listcount", listcount);
		request.setAttribute("boardlist", boardlist);
		request.setAttribute("pageCount", pageCount);
		request.setAttribute("startPage", startPage);
		request.setAttribute("endPage", endPage);		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_list.jsp");
		
		return forward;
	}

}

- 기본변수 3개, 파생된 변수 5개를 여기 글 목록 가져오기 Service 클래스에서 구해와야한다

+ 출력하는 파일은 따로 있다

<기본변수, 파생변수 구하기>

- 현재 페이지 번호 page의 기본값은 1 페이지로 지정, 한 화면에 10개씩 출력하겠다는 의미로 limit 는 10

- 만약 이 Service 클래스로 올때 페이지번호를 가져왔다면 그 번호가 page 의 값이 된다

- startRow, endRow 는 page, limit 에 의해 구해진다

- listcount 변수는 총 데이터 개수를 저장하고 있다, getCount() 로 부터 돌려받은 값

<총 데이터 개수 구하기>

- DAO 객체 생성 후 총 데이터 개수를 구하는 DAO 의 메소드 getCount() 메소드 호출해서 총 데이터 개수를 돌려받음

<목록 구하기>

- 실제 목록을 잘라서 목록을 구해오는 DAO 의 메소드 getList() 메소드를 호출, 매개변수로는 페이지번호 page 를 전달

* 목록을 가져올때 문제점 아래에 설명

- getList() 는 리스트로 리턴하고, 그 결과는 List 객체 boardlist 로 리턴받음

<DAO getList() 에서 돌아온 후>

- 총 페이지수가 저장될 파생변수 pageCount 를 listcount 와 limit 으로 구한다

+ 나눗셈을하면 정수형으로 값이 구해짐을 주의

- 각 블럭을 시작하는 페이지인 startPage 와 각 블럭의 마지막 페이지인 endPage 를 page 와 limit 로 구한다

- View 페이지에서 페이지 메뉴바에서 페이지 번호를 출력할때 무조건 10번 루프를 돌리므로 실제 존재하지 않는 페이지가 나타나는것을 막기 위해 마지막 페이지일땐 endPage 를 pageCount 로 설정해준다

<공유설정>

- 기본 변수 및 파생 변수인 page, listcount, boardlist, pageCount, startPage, endPage 를 request 객체로 공유설정한다

- View 페이지에서 출력할때 boardlist 는 리스트이므로 forEach 의 items 태그에 들어감, 나머지는 기본변수이므로 ${page} 처럼 출력함

<포워딩 방식, 페이지 지정>

- qna_board_list.jsp 로 포워딩, 거기서 목록을 출력할 것

- request 공유설정했으므로 Dispatcher 방식으로 포워딩해야한다

 

+ Model 2 게시판 프로그램 글 목록 구하기 변수들 설명 : https://laker99.tistory.com/129?category=1080281


목록을 가져올때 문제점

- MyBatis 연동 전에는 startRow 와 endRow 를 getList() 의 매개변수로 전달했었다, select 문의 where 절에 사용했음

- MyBatis 와 연동시 값이든 주소든 단 하나만 SqlSession 지원 메소드 5개의 매개변수로 전달 가능하다

+ Service 클래스에서 DAO 메소드로는 여러개의 값을 매개변수로 전달 가능하지만, SqlSession 지원 메소드의 매개변수로는 첫번째 매개변수에 id 를 넣고, 두번째 매개변수에 값을 전달할 수 있다, 단 하나의 값만 전달 가능

 

해결방법

- 해결방법이 여러개 있지만 여기선 이 방법을 사용하고 있다

- startRow 와 endRow 를 전달하는 대신 페이지 번호인 page 를 전달한다

- 이 page 번호에 따라 startRow 와 endRow 가 정해지므로, limit 는 정해진 값이므로 그 자리에 10을 쓰면 된다

- SQL문 상에서 page 번호로 startRow 와 endRow를 계산하기


또다른 해결방법

- 이 부분에서만 여러 파일의 내용을 변경할 것(주석 풀기 등), 이 부분을 나가면 다시 원래대로

- Map 객체 map 을 생성하고 put() 으로 startRow, endRow 를 저장 후 Map 객체 map 을 전달하면 됨

 

//      Map 처리	----------------------------------	
		Map map = new HashMap();
		map.put("start", startRow);
		map.put("end", endRow);

- Map 객체 map 에 key , value 값으로 startRow 와 endRow 를 모두 저장시켰다

ex) 사용자가 2 페이지 선택시 11 과 20 을 Map 객체에 저장하는 것

- key 값으로 value 를 가져와서 사용할 것

- 그리고 Map 객체를 DAO의 getList() 로 전달

		boardlist = dao.getList(map);

- MemberDAO.java 에서 getList() 메소드 부분에서 주석을 바꿈

	// 데이터 목록
//	public List getList(int page) throws Exception {
	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", map); 
		
		return list;
	}

- map 객체를 selectList() 의 매개변수로 전달한다

- Mapper 파일인 board.xml 로 가자

 

	<!-- Map 전달 -->
	<select id="board_list" parameterType="Map" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= #{start} and rnum &lt;= #{end}
	</select>

- parameterType 을 Map 으로 써야한다, HashMap 이어도 괜찮다

- #{start} 와 #{end} 의 "start" 와 "end" 는 map 에 저장한 key 값이다

- #{start} 처럼 key 값을 이용해서 value 값을 구해올 수 있다

 

+ 세번째 방법

- DTO 클래스 안에 startRow 와 endRow 를 저장할 수 있는 필드를 만들고 그 DTO 객체에 startRow, endRow 를 저장해서 넘겨주는 방법도 있다

- 가져올땐 getter 메소드 사용

 

어떤 방법을 써야할까?

- 우리가 쓰고 있는 페이지번호 page 를 넘기고 SQL문 상에서 startRow, endRow 를 계산하는 방법은 쓰지 못할 때가 있다

- 검색기능을 수행시에 '작성자명', '제목' 등으로 검색한다, 그 검색어도 SQL상으로 전달되어야함, 넘어가는 값이 많아질땐 페이지번호를 넘기는 방법 쓰지 못하게 됨

- 이땐 DTO 객체를 쓰거나, Map 객체안에 저장해서 전달해야함

- 주로 DTO 객체를 넘기는 경우가 많다, Map 은 DTO보다 처리속도가 느림


- MemberDAO.java 에서 getCount() 메소드 부분만

	// 총데이터 갯수 구하기
	public int getCount() throws Exception{
		int result=0;
		SqlSession session=getSession();
		result = (Integer)session.selectOne("board_count");
		
		return result;
	}

- group 함수로 구하기때문에 검색하는 데이터는 1개이므로 selectOne() 메소드 사용

- session.selectOne() 에서 리턴자료형은 Object 이므로 다운캐스팅이 원칙이다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_count)

	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>

- 돌려주는 데이터는 글 개수 이므로 resultType 은 "int" 이다

+ 주의 : returnType 은 무조건 DTO 가 아니다, 돌려주는 데이터의 자료형임

 

- MemberDAO.java 에서 getList() 메소드 부분만

	// 데이터 목록
	public List getList(int page) throws Exception {
//	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", page); 
		
		return list;
	}

- 메소드 selectList() 에서도 다른 SqlSession 지원 메소드처럼 단 하나의 값만 매개변수로 전달 가능하다

+ 첫번째 매개변수는 id 값, 두번쨰 매개변수에만 값을 전달 가능

- page 번호만 있으면 startRow, endRow를 계산할 수 있으므로 페이지번호 page 만 전달한다

+ Map 객체로 넘어올때는 주석처리 되어있음

 

- board.xml 에서 목록을 자르고 목록을 가져오는 SQL문 부분 (id 가 board_list)

	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select>

- selectList() 메소드를 실행하고 결과를 List 로 돌려줌, 이때 결과를 DTO 객체들을 저장한 List 로 전달한다고 해도 resultType 에는 DTO 인 member 를 써야한다

- 리스트에 알아서 순차적으로 저장해서 돌려준다

- 전달받은 값은 페이지번호 이므로 parameterType 은 int 로 작성

<SQL문>

- 첫번째 서브쿼리는 rownum 컬럼에 대한 별칭 rnum 을 준다

- 두번째 서브쿼리는 필요한 내용을 검색하고 정렬한다, 댓글게시판이므로 board_re_ref 로 최근글이 위로 가도록 정렬하고, 부모글과 자식글은 board_re_ref 가 동일하므로 한번 더 정렬조건으로 board_re_seq 를 준다

- 두번쨰 서브쿼리에 대해서 board 라는 별칭을 주고 첫번째 서브쿼리에서 board 의 모든 컬럼을 검색하고 있다

- startRow, endRow 를 전달받은 page 번호로 계산한다

- startRow = (#{page} -1) * 10 + 1 이다, 10 은 limit 을 의미

- endRow = #{page} * 10 이다, 10은 limit 을 의미

- xml 파일에서는 < 와 > 를 잘 인식하지 못함, > 대신 특수문자 &gt; 를 사용, < 대신 특수문자 &li; 을 사용

+ <와 > 를 대신하는 다른 방법도 있다, 여기서는 &gt; 과 &lt; 을 사용

 

- 목록을 성공적으로 가져올때 콘솔창

 

- 이제 목록을 가져오는 작업은 끝났다, Controller 클래스로 갔다가 Dispatcher 방식으로 qna_board_list.jsp 로 포워딩됨

- qna_board_list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   

<a href="./BoardForm.do">글쓰기</a> <br>
데이터 갯수 : ${listcount} 개

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<th>번호</th>
		<th>제목</th>
		<th>작성자</th>
		<th>날짜</th>
		<th>조회수</th>
	</tr>
		
	<c:set var="num" value="${listcount - (page-1) * 10 }"/>	
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td>
			${num}
			<c:set var="num" value="${num-1}"/>
		</td>	
		<td>
		<c:if test="${b.board_re_lev > 0}">
			<c:forEach var="i" begin="0" end="${b.board_re_lev}">
				&nbsp;
			</c:forEach>
		</c:if>
		<c:if test="${b.board_re_lev > 0}">
			Re.
		</c:if>
		<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
			 ${b.board_subject}
		</a>
		
		</td>	
		<td>${b.board_name}</td>	
		<td>
		
		<fmt:formatDate value="${b.board_date}" pattern="yyyy-MM-dd H:mm"/>
		
		</td>	
		<td>${b.board_readcount}</td>	
	</tr>
	</c:forEach>

</table><br>


<center>
<c:if test="${listcount > 0}">

	<a href="./BoardListAction.do?page=1" style="text-decoration:none"> < </a>

	<c:if test="${startPage > 10}">
		<a href="./BoardListAction.do?page=${startPage - 10}">[이전]</a>
	</c:if>

	<c:forEach var="i" begin="${startPage}" end="${endPage}">
		<c:if test="${i == page}">
			[${i}]
		</c:if>
		<c:if test="${i != page}">
			<a href="./BoardListAction.do?page=${i}">[${i}]</a>
		</c:if>
	</c:forEach>

	<c:if test="${endPage < pageCount}">
		<a href="./BoardListAction.do?page=${startPage + 10}">[다음]</a>
	</c:if>
	
	<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> > </a>	

</c:if>
</center>

- JSTL 태그들을 쓰기 위해 코어 라이브러리와 국제화 라이브러리를 불러온다

- '글쓰기' 를 누르면 원문 글 작성 폼으로 가는 요청인 "/BoardForm.do" 으로 요청

- 글개수 출력은 Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 listcount 를 바로 구해서 출력

- 그 페이지 화면출력번호 num 을 구함 * 아래에 설명

<데이터 뿌리기>

- Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 값들을 EL 태그로 구해서 출력하고 있다

- forEach 태그를 통해서 각 데이터(행)를 출력, forEach 태그의 items 에 목록을 가지고 있는 공유된 리스트 boardlist 가 온다, 그럼 각 데이터 하나가 변수 b 에 저장됨

- 제목을 출력할때 ${b.board_subject} 로 출력, 표기법만 이럴뿐 실제 의미는 b.getBoard_Subject() 를 출력하는 것이다

- 국제화 라이브러리 formatDate 태그로 패턴을 지정해서 날짜 ${b.board_date} 를 원하는 포맷으로 출력

<댓글 관련>

- if 태그를 써서 ${b.board_re_lev} > 0 이면 즉, 댓글이면 깊이만큼 제목 앞에 공백 출력, Re. 출력

<상세페이지로 가는 링크>

- 제목에 링크를 걸어서 글 번호와 원래 페이지번호를 가져가고 있다

<페이지 메뉴바>

- forEach 태그의 begin 과 end 속성으로 공유된 값 startPage 와 endPage 를 사용함, startPage ~ endPage 까지를 페이지 메뉴바에 출력

- 현재 페이지가 아닌 경우 링크를 "/BoardListAction.do" 로 걸고 페이지번호 page 를 get 방식으로 전달, 이렇게 하면 해당 페이지의 목록을 가져와서 다시 출력함

- 이렇게 전달시 BoardListAction.java 의 이 코드에서 페이지번호 page 를 받음 (BoardListAction.java 부분)

		if(request.getParameter("page")!=null) {
			page = Integer.parseInt(request.getParameter("page"));					
		}

- < 클릭시 page 번호를 1 을 넘겨주며 "/BoardListAction.do" 로 간다

- > 클릭시 page 번호를 마지막 페이지(= 총 페이지 수) 를 넘겨주며 "/BoardListAction.do" 로 간다 (총 페이지수는 공유된 pageCount값을 가져온 것이다)

- [이전] 메뉴는 startPage > 10 인 경우, 즉 첫번째 페이지가 아닌 경우에만 출력, 링크는 현재 블럭의 startPage 에서 10 을 빼서 이전 블럭의 가장 작은 페이지로 이동

- [다음] 메뉴는 endPage < pageCount 인 경우 출력, 링크는 현재 블럭의 startPage 에서 10 을 더해서 다음 블럭의 가장 작은 페이지로 이동

ex) 1page 일떄 endPage 는 10, pageCount 는 13 이므로 [다음] 메뉴 나타남

- 11page일때 endPage 는 13(보정했었다), pageCount 도 13 이므로 만족하지 않음, [다음] 메뉴 나타나지 않는다

 

+ Model 2 게시판 프로그램 글 목록 출력 설명 : https://laker99.tistory.com/129?category=1080281


화면 출력 번호

- Service 클래스 BoardListAction.java 에서 구하지 않은 마지막 파생변수인 화면 출력 번호를 여기서 정의

	<c:set var="num" value="${listcount - (page-1) * 10 }"/>

- 3개의 기본 변수값을 사용해서 화면 출력 번호 num 을 생성, 10 은 limit 값이다

- limit 값은 고정된 값 (10) 이므로 공유설정하지 않았음

- forEach 로 각각의 행을 출력할때마다 1이 감소됨


- 페이징 처리를 보기 위해 글을 여러개 작성해서 많은 데이터를 넣자
+ BoardAddAction.java 에서 포워딩할때 Dispatcher 방식으로 바꾸고, 포워딩 페이지 설정에서 . 을 빼주면 글 작성 후 새로고침 시 같은 데이터가 다시 들어감

- 여기서 새로고침 눌러서 데이터 많이 넣기

 

- 이렇게 페이지 메뉴바에서 5 를 클릭시

- 페이지번호에 따라 startRow, endRow 가 결정되므로 가져올 글들이 달라짐


JSP Model 2 - MyBatis 게시판 프로그램 : 상세 페이지 기능

- qna_board_list.jsp 에서 제목을 클릭하면 "/BoardDetailAction.do" 로 요청하며 글 번호와 페이지 번호를 가져간다

		<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
			 ${b.board_subject}
		</a>

- 여기가 출발점임, 바로 상세페이지를 실행하면 안된다

- 상세페이지로 가거나, 수정하고 오거나, 삭제하거나 할때 원래 페이지로 돌아가기 위해서 페이지 번호를 가져감

- 하나의 글에 대한 상세 정보를 구해와야 하므로 글 번호가 필요함

 

- Controller 클래스에서 "/BoardDetailAction.do" 부분만

		// 상세 페이지	
		}else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 상세 페이지 Service 클래스인 BoardDetailAction.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDetailAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardDetailAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
//		BoardBean board = dao.updateContent(board_num);
		
		//조회수 증가
		dao.updateCount(board_num);
		BoardBean board = dao.getContent(board_num); // 상세정보 구하기
		
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_view.jsp");
		
		return forward;
	}

}

- 목록페이지에서 넘어온 글 번호와 페이지 번호를 받아서 변수 board_num 와 page 에 저장

- 여기서는 2가지 작업을 해야함, 2개의 DAO 메소드를 호출해야한다

1) updateCount() : 조회수 1 증가

2) getContent() : 상세 정보 구해오기

- 이때 글번호 board_num 을 전달해야 그 글의 조회수를 증가시킬 수 있고, 상세 정보를 구할 수 있다

<DAO updateCount() 와 getContent() 에서 돌아온 후>

- 가져온 상세정보를 DTO 객체 board 로 받음

- 그 객체 board 와 페이지번호 page 를 공유설정함

+ 글 번호는 객체 board 안에 board_num 필드안에 저장되어있으므로 공유설정 따로 하지 않음

+ DTO 객체 board 가 공유되면 View 에서 ${board.필드명} 으로 결과를 출력함

<포워딩>

- 상세정보를 보여줄 View 페이지인 qna_board_view.jsp 로 포워딩 페이지 설정

+ 실제 포워딩은 Controller 로 가서 포워딩한다

 

- MemberDAO.java 에서 updateCount() 메소드 부분만

	// 조회수 증가
	public void updateCount(int board_num) throws Exception{
		SqlSession session = getSession();
		session.update("board_updatecount", board_num);
	}

- 전달받은 글 번호 board_num 을 session.update() 의 두번째 매개변수로 전달함

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_updatecontent)

	<!-- 조회수 증가 -->
	<update id="board_updatecount" parameterType="int">
     update model22 set board_readcount=board_readcount+1 where board_num = #{board_num}	
	</update>

- 글 번호를 전달받았고, parameterType 에는 전달받은 값의 자료형을 써야하므로 "int"

- 조회수 값을 1 증가시켜주는 update 를 한다, where 절에 #{board_num} 으로 넘어온 글 번호를 쓴다

 

- MemberDAO.java 에서 getContent() 메소드 부분만

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

- 1개 글에 대한 상세정보를 구해서 돌려줘야하므로 selectOne() 메소드 호출

- 두번째 매개변수로 글 번호 board_num 을 넘겨준다

- 검색되는 결과가 1개이므로 DTO 객체가 리턴자료형으로 왔다, 검색되는 결과가 여러개일땐 List 가 온다

+ 무조건 DTO 가 온다는건 Mapper 파일의 SQL문 부분에 해당함, DAO에서는 검색되는 결과가 여러개일땐 List 가 온다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

- #{board_num} 번호값에 만족하는 데이터를 검색해서 데이터 1개에 대한 상세정보를 자동으로 DTO 객체에 매핑해서 그 DTO 객체를 리턴함

+ 테이블 컬럼명과 DTO 프로퍼티명이 일치할때만 자동 매핑 가능

- resultType 은 DTO alias 인 board

 

- 다음은 이 상세정보를 출력하는 View 페이지인 qna_board_view.jsp

- qna_board_view.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 
    
<table border=1 width=600 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>작성자</td>
		<td>${board.board_name}</td>
	</tr>
	<tr>
		<td>조회수</td>
		<td>${board.board_readcount}</td>
	</tr>
	<tr>
		<td>날짜</td>
		<td>
			<fmt:formatDate value="${board.board_date}"
							pattern="yyyy-MM-dd H:mm"/>		
		</td>
	</tr>
	<tr>
		<td>제목</td>
		<td>${board.board_subject}</td>
	</tr>
	<tr>
		<td>내용</td>
		<td><pre>${board.board_content}</pre></td>
	</tr>
	<tr>
		<td>첨부 파일</td>
		<td>
			<c:if test="${board.board_file !=  null }">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">${board.board_file}</a>
			</c:if>
		</td>
	</tr>
	<tr>
		<td colspan=2 align=center>
			<input type="button" value="댓글" 
				   onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">
			<input type="button" value="수정" 
				   onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">
			<input type="button" value="삭제" 
				   onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">
			<input type="button" value="목록" 
			       onClick="location.href='./BoardListAction.do?page=${page}'">
		</td>
	</tr>
</table>

- Service 클래스 BoardDetailAction.java 에서 공유설정한 객체 board 와 페이지번호 page 를 EL 로 가져와서 활용

ex) 조회수는 ${board.board_readcount} 로 가져와서 출력하고 있다

- if 태그에 ${board.board_file != null} 로, 만약 첨부파일이 있다면 첨부파일명을 출력

- 첨부파일명을 클리하면 file_down.jsp 로 이동하며 다운이 된다

- 내용은 pre 태그로 감싸서 출력해야 줄이 바뀌어서 출력됨

- 상세페이지에 아래에 출력될 버튼을 만들고 링크를 걸었다

'목록' 버튼 클릭시

- 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청, 이때 페이지번호 page 를 전달

+ 페이지 번호 page는 BoardDetailAction.java 에서 공유설정했던 값을 가져왔다

'댓글' 버튼 클릭시

- 댓글을 다는 폼으로 가기 위해 "/BoardReplyAction.do" 로 요청, 이때 5가지의 값을 가져가고 있다

- 부모글이 될 현재 글의 글 번호, 현재 페이지 번호 뿐 아니라 부모글이 될 현재 글의 ref, lev, seq 값도 가져간다

- 만약 글 번호, 페이지 번호만 넘길때는 Service클래스로 가서 부모글에 대한 정보(ref,lev,seq)를 DB 에서 가져와야한다

- 여기선 5가지 값을 다 가져가므로 Service 클래스로 갈 필요 없이 바로 댓글 작성 폼 View 페이지로 가도 된다 

- '수정' 버튼 / '삭제' 버튼 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 댓글 작성 기능

- qna_board_view 에서 출력된 버튼 중 '댓글' 버튼을 클릭시 "/BoardReplyAction.do" 로 요청

<input type="button" value="댓글" 
		onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">

- "/BoardReplyAction.do" 로 가면서 글번호, 페이지번호 뿐 아니라 부모글에 대한 정보인 board_re_ref, board_re_lev, board_re_seq 도 전달

- 그러므로 Service 클래스로 가서 이 정보를 가져오기 위해 DB 연동할 필요 없이 바로 댓글 작성 폼으로 이동 가능

- Service 클래스로 안가고 바로 댓글 작성 폼으로 이동한다

 

- Controller 클래스에서 "/BoardReplyAction.do" 부분만

		// 댓글 폼	
		}else if(command.equals("/BoardReplyAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_reply.jsp");
		}

- 즉 댓글 작성 폼인 View 페이지 qna_board_reply.jsp 로 이동한다

- Model 2 게시판 프로그램 작성시에는 댓글작성폼으로 갈때 2개의 값만 전달하고 나머지는 DB와 연동해서 부모글의 정보를 구하는 작업을 선행했었다, 검색어 (게시판 프로그램 : 댓글 작성폼 Service 클래스)

- 여기서는 부모글의 정보도 qna_board_view 에서 전달하므로 DB연동 필요없이 바로 View페이지로 가고 있다

 

- qna_board_reply.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>댓글 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">
<input type="hidden" name="board_re_ref" value="${param.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${param.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${param.board_re_seq}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">댓글 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="댓글">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_view 에서 get 방식으로 전달된 값을 param EL 내장객체로 가져오고 있다

ex) ${param.board_re_ref} 는 request.getParamter("board_re_ref") 를 의미

- get 방식으로 전달된 5개의 값을 qna_board_reply 의 form 에서 "/BoardReply.do" 로 요청하며 hidden 을 통해서 전달

- 댓글을 작성하고 '댓글' 버튼을 누르면 "/BoardReply.do"로 요청

 

- Controller 클래스에서 "/BoardReply.do" 부분만

		// 댓글	
		}else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

 

- 댓글 작성 Service 클래스인 BoardReply.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReply implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardReply");
		
		request.setCharacterEncoding("utf-8");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
		int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
		int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
		String page = request.getParameter("page");
		
		BoardBean board = new BoardBean();
		board.setBoard_re_ref(board_re_ref);
		board.setBoard_re_seq(board_re_seq);
		
		BoardDAO dao = BoardDAO.getInstance();
		dao.updateSeq(board); 	// board_re_seq값 증가 		
		
		board.setBoard_re_seq(board_re_seq+1);
		board.setBoard_re_lev(board_re_lev+1);		
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_pass(request.getParameter("board_pass"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
					
		dao.boardReply(board); // 댓글 작성 : insert SQL		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page); // 상세페이지로 이동
//		forward.setPath("/BoardListAction.do?page="+page); // 목록페이지로 이동
		
		return forward;
	}

}

- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩

- hidden 으로 넘어오는 5개의 값을 request.getParameter() 로 받아서 변수에 저장

- 또한 사용자가 직접 댓글 작성폼에 작성한 정보 4가지도 받는다

- updateSeq() 메소드에서 부모글의 board_re_ref, board_re_seq 값이 필요하므로 생성한 DTO 객체 board 에 부모글의 board_re_ref, board_re_seq  를 저장하고 그 객체 board 를 updateSeq() 의 매개변수로 전달

<DAO updateSeq() 메소드로부터 돌아온 뒤>

- 댓글을 작성해야하므로 insert 될 데이터를 DTO 객체 board 에 세팅

- 이때 board_re_seq 값과 board_re_lev 값은 부모보다 1 증가된 값으로 세팅

- 또한 사용자가 직접 입력한 4가지 정보도 세팅하고 DAO의 boardReply() 메소드 호출, 매개변수로는 객체 board 를 넘김

<DAO boardReply() 메소드로부터 돌아온 뒤>

- 댓글을 단 이후 상세페이지로 넘어갈땐, "/BoardDetailAction.do" 로 포워딩 페이지 지정하며 글번호와 페이지번호를 전달

+ BoardDetailAction.java 에서 글번호와 페이지번호를 getParameter() 로 받는 작업을 가장 먼저하므로 두 값이 필요

- 댓글을 단 이후 목록페이지로 넘어갈땐, "/BoardListAction.do" 로 포워딩 페이지 지정하며 페이지 번호를 전달

 

이 Service 클래스에서 2번의 DAO 메소드를 실행해야함

1) updateSeq() 메소드 : update SQL문

- update 문을 수행하기 위해 부모글의 ref 값과 seq 값을 전달

- 댓글의 ref 는 부모글의 ref 값과 같은값을 넣고, 부모글의 seq 보다 큰 글들의 seq 를 +1 함

2) boardReply() 메소드 : insert SQL문 

- 댓글을 DB에 insert 함

- Mapper 파일로 처리해야하므로 두 메소드의 SQL문을 하나의 메소드에서 실행 불가, 메소드를 따로 작성 해야함

 

- MemberDAO.java 에서 댓글 출력순서를 조절하기 위한 updateSeq() 부분

	// 댓글 출력 순서 (board_re_seq값 증가)
	public void updateSeq(BoardBean board) {
		SqlSession session = getSession();
		session.update("board_updateseq", board);
	}

- 부모글의 board_re_ref 와 board_re_seq 값을 저장한 객체 board 를 두번째 매개변수로 전달한다

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_updateseq)

	<!-- 댓글 출력순서 -->
	<update id="board_updateseq" parameterType="board">
	 update model22 set board_re_seq=board_re_seq+1 
	  where board_re_ref = #{board_re_ref} and board_re_seq &gt; #{board_re_seq}
	</update>

- 부모글과 board_re_ref 값이 같으면서 , 부모글보다 board_re_seq 값이 큰 글의 board_re_seq 값을 1 증가시켜줘야함

의미

1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가

2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가

 

- MemberDAO.java 에서 댓글을 삽입하는 boardReply() 부분

	// 댓글작성
	public void boardReply(BoardBean board) {
		SqlSession session = getSession();
		session.insert("board_reply", board);
	}

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_reply)

	<!-- 댓글 작성 -->
	<insert id="board_reply" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,sysdate)
	</insert>

- board_num 은 원문이든 댓글이든 모두 sequence 로 들어간다

- 댓글은 첨부파일을 올리지 못하므로 첨부파일명을 저장하는 컬럼 board_file 이 null 이 되어야함

- MyBatis 는 null 값을 허용하지 않는다

- jdbcType=VARCHAR 속성값을 추가하면 null 값을 허용하게 됨 

- 이미 BoardReply.java 에서 부모의 board_re_seq, board_re_lev 에서 1 증가시켜서 board 객체의 board_re_seq 와 board_re_lev에 세팅했으므로 여기선 그대로 가져와서 삽입

- 조회수 컬럼엔 0, 날짜 컬럼엔 sysdate 로 현재 날짜와 시간을 넣어줌

 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정폼 기능

- qna_board_view 에서 출력된 버튼 중 '수정' 버튼을 클릭시 "/BoardModifyAcion.do" 로 요청

<input type="button" value="수정" 
	onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">

- "/BoardModifyAcion.do" 로 가면서 글번호, 페이지번호 전달

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다

- 먼저 상세정보를 구해와서 그걸 수정폼에 뿌려야한다

 

- Controller 클래스에서 "/BoardModifyAcion.do" 부분만

		// 수정 폼	
		}else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 수정폼에 뿌릴 상세 정보를 가져오는 Service 클래스 BoardModifyAction.java

- BoardModifyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModifyAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardModifyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean board = dao.getContent(board_num);
				
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_modify.jsp");
		
		return forward;
	}

}

- 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장

- DAO의 getContent() 메소드로 글 하나에 대한 상세정보를 가져온다, 매개변수로는 글 번호를 넘겨준다

- getContent() 에서 구해온 상세정보를 board 객체에 저장 한다

- board 객체를 공유설정하고 페이지 번호인 page 변수도 request 객체로 공유설정함

+ View인 수정폼에서 가져와서 뿌려줄 수 있도록 공유설정해야한다, DTO 객체가 공유되었다면  View 에서 ${공유되는네임값.필드명} 으로 사용

- 포워딩 페이지는 qna_board_modify.jsp 로 지정한다

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

- 두번째 매개변수로 글 번호 board_num 전달

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

- DTO 객체에 상세정보를 매핑해서 리턴해줌

 

- 포워딩되는 페이지이자 수정폼 View 페이지인 qna_board_modify.jsp 를 보자

- qna_board_modify.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 수정</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 수정</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name }"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="${board.board_subject}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="수정">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

+ 첨부파일을 수정하는 기능은 빠져있다

- Service 클래스인 BoardModifyAction.java 에서 공유설정했던 DTO 객체 board 의 값을 ${board.필드명} 으로 가져와서 출력하고 있다

ex) ${board.subject} 으로 해당 글의 제목을 가져오고 있다

- 공유설정했던 DTO 객체 board에서 글 번호인 ${board.board_num} 을 hidden 으로 "/BoardModify.do" 로 넘겨준다

- 또한 수정이 끝난 후 원래 페이지로 돌아가기 위해 공유설정된 페이지번호 ${page} 를 hidden 으로 "/BoardModify.do" 로 넘겨준다

- 즉 글 번호와 페이지 번호를 글 수정을 하는 Service 클래스로 넘기고 있음

 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정 기능

- 수정폼 qna_board_modify.jsp 에서 '수정' 을 누르면 form 을 통해 "/BoardModify.do" 로 요청한다 

- hidden 으로 글 번호와 페이지 번호가 넘어왔음

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다

- 또한 수정폼에서 사용자가 입력한 값들도 넘어왔다

 

- Controller 클래스에서 "/BoardModify.do" 부분만

		// 수정	
		}else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 수정을 하는 Service 클래스인 BoardModify.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardModify");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String page = request.getParameter("page");
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String board_pass = request.getParameter("board_pass");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getContent(board_num);
		
		if(old.getBoard_pass().equals(board_pass)) {//비번 일치시
			dao.update(board);
			
		}else {	// 비번 불일치시
			
			out.println("<script>");
			out.println("alert('비밀번호가 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page);
		
		return forward;
	}

}

- hidden 으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장한다

- 수정을 하기 위해 필요한 정보들을 저장한 DTO 객체 board 에 세팅

- DAO의 getContent() 메소드를 호출해서 매개변수로 글번호를 전달, 데이터의 상세정보를 구해와서 객체 old 에 저장

- old.getBoard_pass() 인 DB속 비밀번호와 사용자가 수정폼에 입력한 비밀번호 board_pass 가 일치시 글을 수정하는 DAO의 update() 메소드를 호출해서 매개변수로 객체 board 를 넘겨줌

- 비밀번호가 일치하지 않았다면 이전페이지인 수정폼으로 돌아감

- 비밀번호 일치 후 수정을 성공적으로 완료했다면 상세페이지로 가도록 "/BoardDetailAction.do" 로 요청하며 글 번호, 페이지 번호를 넘긴다

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

 

- MemberDAO.java 에서 update() 메소드 부분만 

	// 글수정
	public void update(BoardBean board) throws Exception {
		SqlSession session=getSession();
		session.update("board_update", board);
	}

- session.update() 는 수정한 글 개수를 자동으로 반환해주지만 여기서는 리턴받지 않았다, select 가 아니면 리턴받을 필요 없음

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_update)

	<!-- 글수정 -->
	<update id="board_update" parameterType="board">
	 update model22 set board_name=#{board_name}, board_subject=#{board_subject},
	 board_content=#{board_content} where board_num=#{board_num}
	</update>

- 객체 board 가 넘어왔으므로 paramterType 은 DTO alias 인 "board"

- update SQL문을 사용해서 넘어온 DTO 객체의 값들을 #{필드명} 으로 가져옴

ex) #{board_name} 은 board.getBoard_Name() 을 의미

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 삭제폼 / 글 삭제 기능

- qna_board_view 에서 출력된 버튼 중 '삭제' 버튼을 클릭시 "/BoardDeleteAcion.do" 로 요청

<input type="button" value="삭제" 
	onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">

- "/BoardDeleteAcion.do" 로 가면서 글번호, 페이지번호 전달

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 삭제폼 -> 삭제 까지 넘어간다

- 먼저 삭제폼으로 가게 된다

 

- Controller 클래스에서 "/BoardDeleteAcion.do" 부분만

		// 삭제 폼	
		}else if(command.equals("/BoardDeleteAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_delete.jsp");
		}

- 삭제 폼에서는 DB 접속이 필요하지 않으므로 Service 클래스가 아닌 삭제폼 qna_board_delete.jsp 로 바로 포워딩 설정 후 Controller 클래스 하단에서 포워딩

 

- qna_board_delete.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 삭제</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardDelete.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">

<table width=300 cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 삭제</td>
	</tr>	
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="삭제">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- EL 내장객체 param 으로 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받음

- 받은 글 번호와 페이지번호를 바로 form의 hidden 을 통해 "/BoardDelete.do" 로 전달함

+ 원하는 글 1개를 삭제하기 위해 반드시 글 번호를 전달해야함, 그리고 삭제 후 원래 페이지로 돌아가기 위해 페이지 번호 전달

- 그리고 사용자가 삭제폼에 입력한 비밀번호도 "/BoardDelete.do" 전달

 

- Controller 클래스에서 "/BoardDelete.do" 부분만

		// 삭제	
		}else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 글 삭제 Service 클래스 BoardDelete.java

package service;

import java.io.File;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDelete implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardDelete");
		
		response.setContentType("text/html; charset=utf-8"); 
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String path = request.getRealPath("boardupload");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass  = request.getParameter("board_pass");
				
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getContent(board_num); // 상세정보 구하기
		
		if(old.getBoard_pass().equals(board_pass)) { //비번 일치시
			dao.delete(board_num); // delete SQL
			
			if(old.getBoard_file() != null) { // 첨부파일이 있으면
				
				File file = new File(path);
				file.mkdirs();
				
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();
					}
				}				
			}
			
		}else {	// 비번 불일치시
	
			out.println("<script>");
			out.println("alert('비밀번호가 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();

			return null;			
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page="+page);
		
		return forward;
	}

}

- qna_board_delete.jsp 에서 hidden 으로 넘어온 글 번호, 페이지 번호와 사용자가 삭제폼에 입력한 비밀번호를 받아서 변수에 저장한다

- DAO의 getContent() 메소드를 호출, 매개변수로 글 번호를 전달해서 해당 글의 상세정보를 구해온다

- 구해온 상세 정보에서 비밀번호를 가져와서 사용자가 삭제폼에 입력한 비밀번호와 비교

- 비번이 일치시 DAO의 delete() 메소드를 호출해서 글을 삭제함, 글 삭제 이후 첨부파일도 지워준다 * 아래에 설명

- 비번이 불일치시 위에서 생성한 out 객체로 alert 창을 출력하고 이전 페이지인 삭제폼으로 돌아감

- 비번이 일치하고 삭제 성공시 목록페이지로 가기 위해 포워딩 페이지를 목록을 가져오는 요청인 "/BoardListAction.do" 로 지정하고 페이지 번호 전달

 

이 Service 클래스에서 DAO의 메소드 2개를 호출해야한다

1) getContent() : 비번을 가져와서 비교하기 위해 호출

2) delete() : 글 삭제를 위해 호출

 

첨부파일 삭제 (BoardDelete.java 부분)

String path = request.getRealPath("boardupload");

if(old.getBoard_file() != null) { // 첨부파일이 있으면
				
				File file = new File(path);
				file.mkdirs();
				
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();
					}
				}				
			}

- "boardupload" 폴더의 경로로 File 객체 file 을 구한 후 listFiles() 로 File 배열 f 에 모든 파일을 저장

- 배열에 들은 파일명을 getName() 으로 하나씩 가져와서 DB에서 삭제한 글의 첨부파일명과 일치하면 그 첨부파일 삭제

 

- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 : https://laker99.tistory.com/131?category=1080281 

- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 검색어 : 첨부파일 지우기 (BoardDelete.java 부분)

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

 

- MemberDAO.java 에서 delete() 메소드 부분만 

	// 글삭제
	public void delete(int board_num) {
		SqlSession session=getSession();
		session.delete("board_delete", board_num);
	}

- 글 삭제를 위해 글 번호를 넘겨준다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_delete)

	<!-- 글삭제 -->
	<delete id="board_delete" parameterType="int">
	 delete from model22 where board_num=#{board_num}
	</delete>

 

- 글을 삭제해보자

 

- 삭제폼으로 갈땐 Service 클래스로 가지 않으므로 콘솔창에 Service 클래스로 간 흔적이 없다

- 삭제 되었음, 후에 목록을 가져오고 목록페이지로 간다

 

복습

Maven

- pom.xml 에 의존 라이브러리 추가시 원격 저장소에서 로컬 저장소로 다운받음

- Spring Project 에서도 로컬 저장소(레포지토리) 는 똑같은 폴더를 쓴다

 

+ Spring

- Spring Project 도 이와 비슷한 폴더 구조로 만들어진다

- Spring Project 에서는 생성 시 부터 pom.xml 에 기본적인 라이브러리들이 들어있다

 

MyBatis 환경설정 파일

- 마켓플레이스에서 플러그인 다운받으면 생성할 수 있었지만 현재는 불가능

- 한개의 파일만 있으면 된다

- 들어가는 내용

1) Alias 설정

2) DB 연동

3) Mapper 파일 불러오기

 

Mapper 파일

- DAO 클래스 안의 SQL문이 들어있는 파일

- 마켓플레이스에서 플러그인 다운받으면 생성 가능

- 검색되는 컬럼명과 DTO 필드명이 다를때는 resultMap 태그로 매핑을 직접 잡아준다

 


JSP Model 2 - MyBatis 연동 : 회원가입 프로그램

Model 2 과 MyBatis 연동

- 클라우드에서 Model 2 과 MyBatis 가 이미 연동이 완료된 회원가입 프로그램 mybatismember 프로젝트 import

- zip 파일을 압축 풀어서 import

- import 하는 프로젝트는 Maven 프로젝트이다

- Maven 프로젝트이므로 구조는 다음과 같다

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다 (Controller, Service, DAO, DTO)

+ Model 2 이므로 src/main/java 안에서도 DTO, DAO, Service, Controller 가 서로 다른 패키지 안에 들어가있음

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다

- src/main/webapp 안에 View 파일 등이 들어간다

 

- import한 mybatismember 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>mybatismember</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>mybatismember Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 복사 시작 -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>		

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-impl</artifactId>
			<version>1.2.5</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>mybatismember</finalName>
	</build>
</project>

- 프로젝트 시작 시 가장 먼저 라이브러리를 구하는 작업을 해야한다

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

 

Model 2 에 MyBatis 연동시 구조

- Model 2 에서 MyBatis 를 연동

- Controller -> Service -> DAO -> MyBatis 와 연동해서 DB 연동

 

- db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

- 여기 db.properties 에 적힌 이 계정으로 접근해서 테이블을 생성해야한다

 

- MyBatis 환경설정 파일인 mybatis-config.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="db.properties" />
	<typeAliases>
		<typeAlias type="model.MemberDTO" alias="member"></typeAlias>
	</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="member.xml" />
	</mappers>
</configuration>

 

- Mapper 파일 member.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="mymember">

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>
	
	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>
	
	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete> 

</mapper>

 

 


 

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 model2member.sql 에서 totoro 계정으로 연결한 후 member0609 테이블을 생성하자

- 커넥션 프로파일 설정

- model2member.sql

-- 회원관리
select * from tab;
select * from member0609;

create table member0609(
	id varchar2(20) primary key,
	passwd  varchar2(20) not null,
	name varchar2(20) not null,
	jumin1 varchar2(6) not null,
	jumin2 varchar2(7) not null,
	mailid varchar2(30), 
	domain varchar2(30), 
	tel1 varchar2(5),
	tel2 varchar2(5),
	tel3 varchar2(5),
	phone1 varchar2(5),
	phone2 varchar2(5),
	phone3 varchar2(5),
	post varchar2(10),
	address varchar2(200),
	gender varchar2(20),
	hobby varchar2(50),
	intro varchar2(2000),
	register timestamp );

- 테이블 member0609 를 생성하자

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야 매핑이 자동으로 일어난다


JSP Model 2 - MyBatis 회원관리 프로그램 : 기본 파일들

- index.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>

<!-- 모델2 회원관리 -->

<script>
	location.href="./LoginForm.do";
</script>

</body>
</html>

- index.jsp 실행이나 현재 프로젝트 Run As - Run on Server 로 실행시키자

- 로그인 폼이 나타난다

- '회원가입' 을 눌러 회원가입 폼으로 가서 가입을 시키자

- 이제 로그인 페이지로 넘어간다, 아이디, 비번이 일치하면 로그인 성공한다

- 정보수정을 해보자

- 비번이 일치할때 수정된다!

 

Model 2 흐름

Model 2 요청할때 흐름

- View 에서 Controller 로 찾아갈때 WebServlet 어노테이션의 패턴을 맞춰서 찾아간다

- Controller 에서 해당 Service 클래스로 넘긴다, Service 클래스에서 DAO 의 메소드를 불러서 DB와 연동

 

Model 2 돌아올때 흐름

- DAO 클래스에서 검색한 결과를 Service 로 넘기면 Service 에서 결과를 공유설정한다

- 다시 Controller 클래스로 돌아와서 View 페이지로 포워딩됨

 

- src/main/java 에서 model 패키지의 DTO 클래스인 MemberDTO.java 를 보자

// DTO(Data Transfer Object)

package model;

import java.sql.Timestamp;

public class MemberDTO {
	private String id;
	private String passwd;
	private String name;
	private String jumin1;
	private String jumin2;
	private String mailid;
	private String domain;
	private String tel1;
	private String tel2;
	private String tel3;
	private String phone1;
	private String phone2;
	private String phone3;
	private String post;
	private String address;
	private String gender;
	private String hobby;
	private String intro;
	private Timestamp register;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getJumin1() {
		return jumin1;
	}
	public void setJumin1(String jumin1) {
		this.jumin1 = jumin1;
	}
	public String getJumin2() {
		return jumin2;
	}
	public void setJumin2(String jumin2) {
		this.jumin2 = jumin2;
	}
	public String getMailid() {
		return mailid;
	}
	public void setMailid(String mailid) {
		this.mailid = mailid;
	}
	public String getDomain() {
		return domain;
	}
	public void setDomain(String domain) {
		this.domain = domain;
	}
	public String getTel1() {
		return tel1;
	}
	public void setTel1(String tel1) {
		this.tel1 = tel1;
	}
	public String getTel2() {
		return tel2;
	}
	public void setTel2(String tel2) {
		this.tel2 = tel2;
	}
	public String getTel3() {
		return tel3;
	}
	public void setTel3(String tel3) {
		this.tel3 = tel3;
	}
	public String getPhone1() {
		return phone1;
	}
	public void setPhone1(String phone1) {
		this.phone1 = phone1;
	}
	public String getPhone2() {
		return phone2;
	}
	public void setPhone2(String phone2) {
		this.phone2 = phone2;
	}
	public String getPhone3() {
		return phone3;
	}
	public void setPhone3(String phone3) {
		this.phone3 = phone3;
	}
	public String getPost() {
		return post;
	}
	public void setPost(String post) {
		this.post = post;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getHobby() {
		return hobby;
	}
	public void setHobby(String hobby) {
		this.hobby = hobby;
	}
	public String getIntro() {
		return intro;
	}
	public void setIntro(String intro) {
		this.intro = intro;
	}
	public Timestamp getRegister() {
		return register;
	}
	public void setRegister(Timestamp register) {
		this.register = register;
	}

}

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야 매핑이 자동으로 일어난다

- 프로퍼티명이 컬럼명과 동일하게 되어있다, 쉽게 매핑 가능해짐


- Controller 클래스 MemberController.java 를 보자

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
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 service.Action;
import service.ActionForward;
import service.Delete;
import service.Idcheck;
import service.Login;
import service.MemberInsert;
import service.Update;
import service.UpdateMember;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();               
		String command = requestURI.substring(contextPath.length());  
		
		System.out.println("requestURI:"+requestURI);    // requestURI: /model2member/MemberInsert.do
		System.out.println("contextPath:"+contextPath);  // contextPath: /model2member
		System.out.println("command:"+command);          // command: /MemberInsert.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원 가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 회원가입 폼	
		}else if(command.equals("/MemberForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./member/memberform.jsp");
			
		// ID중복 검사(ajax)	
		}else if(command.equals("/Idcheck.do")) {
			try {
				action = new Idcheck();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 로그인(회원인증)	
		}else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 로그인 폼	
		}else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/loginform.jsp");	
			
		// 로그 아웃	
		}else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/logout.jsp");
			
		// 회원정보 수정폼	
		}else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 회원정보 수정	
		}else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		
		// 회원탈퇴 폼	
		}else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/deleteform.jsp");
			
		// 회원탈퇴	
		}else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {		// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			}else {							// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}		
		
	}// doProcess() end	
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("get");
	
		doProcess(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("post");
		
		doProcess(request, response);		
	}

}

- 사용자가 get 방식 요청시 doGet() 이 자동실행되고, post 방식 요청시 doPost() 가 자동 실행됨

- 공통적인 작업을 하기 위한 doProcess() 메소드

 

Controller 클래스로 찾아가는 2가지 방법

1. WebServlet 어노테이션 패턴을 지정하고, 맞는 패턴으로 찾아가는 방법

- 현재 Model 2

2. Spring 에서는 web.xml 파일 안에서 Controller 클래스 이름, 위치가 등록되고, 매핑을 통해 찾아감

- Model 2 도 예전에 이방법을 사용했었다

 


- 다음은 DAO 클래스 MemberDAO.java 를 보자

// DAO(Data Access Object)

package dao;

import java.io.Reader;

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 model.MemberDTO;

public class MemberDAO {
	
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO getInstance() {
		return instance;
	}
	
	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}
		
		
	// 회원가입
	public int insert(MemberDTO member) throws Exception{
		int result=0;
		SqlSession session = getSession();
		result = session.insert("insert", member);		
		System.out.println("result:"+result);
		
		return result;
	}
	
	// ID중복검사
	public int idcheck(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {	// 중복 ID	
			result = 1;			
		}else {					// 사용가능한 ID
			result = -1;
		}
		
		return result;
	}
	
	// 로그인(회원인증)
	public int memberAuth(String id, String passwd) throws Exception {
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {		// 중복 ID	
			if(member.getPasswd().equals(passwd)) {
				result = 1;			// 회원인증 성공
			}else {
				result = -1;		// 회원인증 실패
			}
		}

		return result;
	}

	
	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}
	
	
	// 회원정보 수정
	public int update(MemberDTO member) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.update("update", member);

		return result;
	}

	// 회원 탈퇴
	public int delete(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.delete("delete", id);		
		
		return result;
	}	

}

- MyBatis 와 연동하면 DAO 이후부터가 달라지는 내용이다

- SQL문이 Mapper xml 파일로 빠지고, 그 중간에 MyBatis 환경설정 파일이 있음

 

SqlSession 객체를 구해주는 메소드 (MemberDAO.java 부분)

 

	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}

- MyBatis 환경설정 파일을 읽어와서 Reader 객체를 구하고, SqlSessionFactory 객체를 구하고, 세션을 openSession 으로 열어서 그 세션을 반환

- SqlSession 객체가 만들어지면, SqlSession 가 제공하는 메소드 5개를 사용할 수 있다

- Maven 에 추가했던 MyBatis 라이브러리의 클래스와 인터페이스들을 DAO 에서 import 해야한다


- MyBatis 환경설정 파일인 mybatis-config.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="db.properties" />
	<typeAliases>
		<typeAlias type="model.MemberDTO" alias="member"></typeAlias>
	</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="member.xml" />
	</mappers>
</configuration>

- property 태그 : 멤버변수를 property 라고 한다, 이 변수 "driver", "url" 등이 모두 프로퍼티 명임, Spring 의 DI 개념

- "driver" 변수에는 오라클과 연동하므로 오라클용 JDBC 드라이버 위치가 저장된 변수값을 사용

 

- ${jdbc.driverClassName} 등은 db.properties 파일에 있는 변수임

+ db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

 

- mappers 태그 안에서 Mapper 파일을 불러온다

- 같은 폴더 내에 있으므로 이름인 member.xml 만으로 불러온다

- 테이블이 늘어나면 Mapper 파일이 늘어나고, 이 mappers 태그 안의 코드도 늘어남

- 이 과정을 통해 MyBatis 환경 설정파일은 Mapper 파일을 불러오고, DAO 는 MyBatis 환경 설정 파일을 불러오므로 DAO 에서 Mapper 파일의 id 를 사용 가능하다


- 다음은 Mapper 파일인 member.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="mymember">

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>
	
	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>
	
	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete> 

</mapper>

- MyBatis 환경설정 파일에서 이 Mapper 파일을 불러올때 "mapper" 태그 안에서 불러왔으므로 이 Mapper 파일의 루트 엘리먼트도 "mapper" 이어야함

- namespace 값을 써서 다른 Mapper 파일 내에 id 중복 문제를 피한다

- 넘어오는 값의 자료형을 parameterType 에 쓰고, 돌려주는 값의 자료형을 returnType 에 쓴다


JSP Model 2 - MyBatis 연동 구조



JSP Model 2 - MyBatis 회원관리 프로그램 : 회원가입 기능

흐름 설명

1. index.jsp 에서 "/LoginForm.do" 로 요청

2. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- index.jsp 에서 location.href 로 Controller 로 찾아왔다, 폼이 아니므로 Get 방식 전송

3. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- loginForm.jsp 로 포워딩 됨

- DB 연동안하므로 Service 클래스가 아닌 폼으로 감

4. 로그인 폼인 loginForm.jsp 로 넘어와서 '회원가입' 버튼을 누르면 "/MemberForm.do" 로 요청, 즉 회원가입 폼으로 간다

5.어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- loginForm.jsp 에서 location.href 로 Controller 찾아왔다, 폼이 아니므로 Get 방식 전송

6. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- memberForm.jsp 로 포워딩 됨

7. 회원가입 폼인 memberForm.jsp 로 넘어와서 정보 입력 후 '회원가입' 버튼을 누르면 "/MemberInsert.do" 로 요청

- form에서 post 방식으로 전송

8. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감. doPost() -> doProcess() 실행됨

9. command 변수에 저장된 내용을 통해 회원가입을 처리해주는 Service 클래스인 MemberInsert.java 로 보내줌

10.MemberInsert.java 에서 DTO 객체생성 후 회원가입 폼에서 입력된 값들을 모두 DTO 객체에 저장

- Spring 에서는 직접 DTO 객체 생성하고,Setter 메소드로 일일히 저장하는 작업 하지 않는다, 어노테이션으로 처리함

11. DAO 객체를 생성 후 DAO 의 insert() 메소드 호출하고, 매개변수로는 값을 설정한 DTO 객체를 전달

12. DAO 의 insert() 메소드안에서 SqlSession 객체를 받고, session.insert("insert',member) 로 id 가 insert 인 Mapper 파일의 SQL문 호출

- 삽입할 데이터를 전달해줘야 하므로, 전달할 값인 DTO 객체 member 를 두번째 매개변수에 써서 전달

13. Mapper 파일 member.xml 에서 id 가 insert 인 태그 안 SQL문 실행 후 삽입된 데이터 개수 1개를 돌려줌

14. DAO 에서 result 로 그 삽입된 데이터 개수 1개를 받아서 Service 클래스로 리턴함

15. Service 에서 - Service 클래스 하단에서 포워딩 방식, 포워딩할 페이지 지정

- Dispatcher 방식, loginform.jsp 페이지로 포워딩할 것

16. Service 클래스를 호출한 Controller 클래스로 돌아간다

17. Controller 클래스에서 forward != null 이므로 설정된 곳으로 포워딩함

- Dispatcher 방식, loginform.jsp 페이지로 포워딩 한다

 

코드 및 설명

- Controller 클래스에서 "/MemberForm.do" 부분만

		// 회원가입 폼	
		}else if(command.equals("/MemberForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./member/memberform.jsp");
		}

- 회원가입 폼 memberform.jsp 는 길어서 코드 붙이지 않았다 * 나중에 ID중복검사 기능 부분 설명때 붙일 것

- Controller 클래스에서 "/MemberInsert.do" 부분만

		// 회원 가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 회원가입 Service 클래스인 MemberInsert.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class MemberInsert implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("MemberInsert");
		
		request.setCharacterEncoding("utf-8");
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String h = "";
		String[] h1 = request.getParameterValues("hobby");
		for(String h2 : h1) {
			h += h2+"-";			// 공부-게임-
		}
		member.setHobby(h);
		
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.insert(member);
		if(result == 1) {
			System.out.println("회원가입 성공");
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/member/loginform.jsp");
		
		return forward;
	}

}

 

- MemberDAO.java 에서 insert() 메소드 부분만

	// 회원가입
	public int insert(MemberDTO member) throws Exception{
		int result=0;
		SqlSession session = getSession();
		result = session.insert("insert", member);		
		System.out.println("result:"+result);
		
		return result;
	}

- Maven 에 추가했던 MyBatis 라이브러리의 클래스와 인터페이스들을 DAO 에서 import 해야한다

- session.insert("아이디", 전달할 값) 로 Mapper 파일의 SQL문을 불러오고 있다

- session.insert() 실행 후 자동으로 삽입 성공 데이터 개수 (1) 를 돌려준다, 그걸 result 변수에 받아서 리턴하고 있음

- DB 연동을 수행하므로 예외처리를 해야한다

- try - catch 로 직접 예외처리를 해도 되고, 메소드를 호출한 곳에 throws 로 예외를 던져도 된다

 

- member.xml 에서 회원 가입을 처리하는 SQL문 부분 (id 가 insert)

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>

- parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- #{mailid} 의 의미는 member.getMailid()

 

+ member.xml 의 namespace

- Mapper 파일은 테이블 수에 비례해서 늘어나므로 namespace 가 있어야 서로 다른 파일에서 id 중복이 되더라도 충돌이 일어나지 않음

 

- 회원가입 시도시 콘솔창

- 회원가입이 성공적으로 되었음


JSP Model 2 - MyBatis 회원관리 프로그램 : 아이디 중복검사 기능

코드와 흐름 같이 설명

- ajax 로 처리

- 회원가입 폼인 memberform.jsp 파일을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do"> 
<table border=1 width=500 align=center>
	<caption>회원 가입</caption>
	<tr><td>ID</td>
		<td><input type=text autofocus="autofocus" id="id" name="id">
			<input type=button value="ID중복검사" id="idcheck">
			<div id="myid"></div>
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid">@
		    <input type=text size=10 id="domain" name="domain">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4>-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4>-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4>
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4>-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4>
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address"></td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요"></textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원가입">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- ID 중복검사 부분의 버튼의 id 는 "idcheck", 메세지를 출력할 div 태그의 id 값은 "myid", ID 입력양식의 id 는 "id"

- member.jsp 파일을 외부 자바 스크립트 파일로 불러온다

 

- member.jsp 파일에서 ID중복검사 부분을 보자

$(document).ready(function(){	
	
	// ID 중복검사
	$("#idcheck").click(function(){
		if($("#id").val()==""){
			alert("ID를 입력하세요");
			$("#id").focus();
			return false;
		}else{
			
			var id = $("#id").val();	
			
			$.ajax({
				type:"post",
				url:"/mybatismember/Idcheck.do",
				data:{"id":id},
				datatype:"text",
				success:function(data){
//					alert(data);
					
					if(data==1){	// 중복 ID
						$("#myid").text("중복ID");
						$("#id").val("").focus();
					}else{			// 사용 가능한 ID
						$("#myid").text("사용 가능한 ID");
						$("#passwd").focus();
					}					
				}
			});			
		}		
	});
})

- jQuery 로 버튼을 가장 먼저 구해오고, click 함수처리 + click 대신 keyup 이벤트로 처리해도 된다

- 입력양식에 값이 입력되지 않아씅면 경고창 띄우기

- 값이 입력되었을때 그 값을 가져와서 변수 id 에 저장

- $.ajax() 를 통해 "Idcheck.do" 로 요청

- 패키지가 다르므로 패키지명도 앞에 써준다

- 값을 가져갈때는 json 형태로 값을 가져간다 "id" 속성명에 id 값을 가져감

- 콜백함수로 데이터를 돌려받음, 돌려받은 뒤 1이 리턴되면 중복 ID 로 처리함, 리턴되는 값이 1 이 아니면 사용가능한 ID로 처리

 

- Controller 클래스로 간다

- Controller 클래스에서 "/Idcheck.do" 부분만

		// ID중복 검사(ajax)	
		}else if(command.equals("/Idcheck.do")) {
			try {
				action = new Idcheck();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 여기서 Service 클래스인 Idcheck.java 파일로 보내준다

- Idcheck.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;

public class Idcheck implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Idcheck");
		
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String id = request.getParameter("id");
		System.out.println("id:"+ id);
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.idcheck(id);
		System.out.println("result:"+ result);	// 1 :  중복ID, -1 : 사용가능한 ID
		out.println(result);	// 브라우저에 출력되는 값이 callback함수로 리턴된다.
		
		return null;
	}

}

- member.js 에서 ajax 로 전달한 사용자가 입력한 id 값을 속성명 "id" 를 사용해서 request.getParameter("id") 로 받음

- DB와 연동하여 중복 ID 인지 아닌지 확인해야하므로 DAO 객체 생성 후 DAO의 idcheck() 메소드 호출

- idcheck() 메소드 호출 시 매개변수로 사용자가 입력한 id 값을 전달함

- idcheck() 에서 리턴된 값을 result 로 받음, 이때 중복 ID는 1, 사용가능한 ID는 -1 로 전달해주기로 함

- 콜백함수로 리턴되는 값은 브라우저에 출력되는 값이므로 out 객체로 result 값을 출력

+ System.out.println() 은 콘솔에 출력되는 값, out.println() 은 브라우저에 출력되는 값

 

- MemberDAO.java 에서 idcheck() 메소드 부분만

	// ID중복검사
	public int idcheck(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {	// 중복 ID	
			result = 1;			
		}else {					// 사용가능한 ID
			result = -1;
		}
		
		return result;
	}

- 매개변수로 사용자가 입력한 id 값을 전달받음

- SqlSession 객체를 변수 session 으로 받아오고 session.selectOne() 메소드를 호출하면서 Mapper 파일에서 id 가 "idcheck" 인 태그의 SQL문 호출

- 그 SQL문을 호출하면서 사용자가 입력한 id 를 넘김

- 사용자가 입력한 id 에 해당하는 검색되는 결과가 1개이므로 selectOne() 메소드로 검색해야함

- selectOne() 을 호출 후 DTO 객체 member 를 반환함, 그 객체가 있으면 DB에 이미 같은 id 인 데이터가 있다는 의미이므로 1 리턴

- 받아온 객체가 null 이면 같은 id 인 데이터가 없다는 의미이므로 사용가능한 id 라는 의미로 -1 리턴

 

  - member.xml 에서 ID중복검사를 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- 전달되는 값은 id 이므로, 전달되는 값의 자료형인 String 을 parameterType 으로 쓴다

+ parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- 돌려주는 데이터 타입은 DTO 이므로, resultType 에 member 를 쓴다

- SQL문에서 #{id} 는 전달되는 값의 변수명인 id 를 써준다

 

- 중복 아닌 ID 입력 후 'ID중복검사' 눌렀을때 콘솔 창

- 중복 ID 입력 후 'ID중복검사' 눌렀을때 콘솔 창


JSP Model 2 - MyBatis 회원관리 프로그램 : 로그인 기능

흐름 설명

1. index.jsp 에서 "/LoginForm.do" 로 요청

2. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- index.jsp 에서 location.href 로 Controller 로 찾아왔다, 폼이 아니므로 Get 방식 전송

3. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- loginForm.jsp 로 포워딩 됨

- DB 연동안하므로 Service 클래스가 아닌 폼으로 감

4. 로그인 폼인 loginForm.jsp 로 넘어와서 아이디, 비번 입력 후 '로그인' 클릭시 form 태그에서 "/Login.do" 로 요청

- 사용자가 입력한 아이디, 비번 값도 전달함

- post 방식으로 요청했다

5. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doPost() -> doProcess() 실행됨

- loginForm.jsp 에서 form 에서 post 방식으로 Controller 로 찾아왔다

+ 이땐 ActionForward 객체 forward 가 null 이므로 포워딩 코드에 접근하지 않음

6. command 변수에 저장된 내용을 통해 로그인(회원 인증)을 처리해주는 Service 클래스인 Login.java 로 보내줌

7. Login.java 에서 로그인 성공시 필요한 session 객체와 out 객체를 구한다

8. Login.java 에서 사용자가 로그인 폼에 입력한 아이디, 비밀번호를 받아서 저장

9. 회원 인증을 처리하기 위해 DB와 연동해야하고, DAO 객체를 생성하고 DAO의 memberAuth(id, passwd) 메소드 호출

- 매개변수로 사용자가 전달한 id, passwd 를 전달

10. DAO 클래스에서 memberAuth() 에서 SqlSession 객체 session 을 구하고 session.selectOne("idcheck",id) 호출하며 Mapper 파일에서 id 가 "idcheck" 인 태그의 SQL문 호출

- 1명에 대한 데이터를 가져오므로 selectOne() 사용

- 전달하는 값은 사용자가 입력한 id 이다

11. Mapper 파일 member.xml 에서 id 가 idcheck인 태그 안 SQL문 실행 후 id 에 해당하는 1명에 대한 상세정보를 DTO 객체로 리턴함

12. DAO 클래스에서 selectOne() 실행 후 돌려받은 값을 DTO 객체 member 에 저장한다

13. 그 객체 member 가 null 이 아니면 그 id 로 검색한 데이터가 DB에 존재한다는 뜻이므로 id가 일치한다는 의미

14. 다음엔 사용자가 입력한 비번과 member 에서 가져온 DB의 비번이 각각 일치시 회원 인증 성공으로 보고 1을 리턴

- 일치하지 않으면 회원 인증 실패로 보고 -1 리턴

15. Service 클래스로 돌아와서 memberAuth() 가 1을 리턴하면 성공으로 보고, 세션으로 id 값을 공유설정하고 포워딩 방식과 포워딩 페이지 설정 

- 여기서부터 SESSION 영역 시작

- 포워딩 할 페이지는 main.jsp 로 설정

+ session 공유시엔 상관없고, Service 클래스에서 request 방식 공유를 했을땐 Dispatcher 방식으로 포워딩 설정해야만 공유된 값 사용 가능

- 로그인 실패시 메세지를 뿌리고 이전 페이지로 돌아감

16. Service 클래스를 호출한 Controller 클래스로 돌아가서, 생성되고 값이 설정된 forward 객체를 리턴함

17. Controller 클래스에서 forward != null 이므로 Controller 클래스 하단에서 포워딩할 페이지, 방식을 가져와서 main.jsp 로 Dispatcher 방식 포워딩함

- Service 하단에서 설정했던 포워딩 페이지, 방식을 여기서 구해와서 실제 포워딩하는 것

18. main.jsp 에서 세션을 구해옴, 이때 세션이 있을때와 없을때 다른 화면을 보여줌

 

- MemberDAO.java 에서 memberAuth() 메소드 부분만

	// 로그인(회원인증)
	public int memberAuth(String id, String passwd) throws Exception {
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {		// 중복 ID	
			if(member.getPasswd().equals(passwd)) {
				result = 1;			// 회원인증 성공
			}else {
				result = -1;		// 회원인증 실패
			}
		}

		return result;
	}

 

 

- member.xml 에서 로그인(회원 인증) 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

+ ID 중복검사 할때와 같은 SQL문 사용

 

- 맞는 아이디, 비밀번호 입력 후 로그인 시

 

- 마지막으로 로그인 성공 후 포워딩되는 메인 페이지인 main.jsp 를 보자

- main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> --%> <!-- 안됨 -->


<!--  세션이 있으면 -->
<%-- <c:if test="${not empty sessionScope.id}"> --%>	<!-- 2개 모두 잘됨 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="<%=request.getContextPath() %>/UpdateMember.do">회원정보 수정</a>  <br><br>
	
	<a href="<%=request.getContextPath() %>/Logout.do">로그아웃</a>  <br><br>
	
	<a href="<%=request.getContextPath() %>/DeleteMember.do">회원탈퇴</a> <br><br>
</c:if>

<!-- 세션이 없으면 -->
<%-- <c:if test="${empty sessionScope.id}"> --%>
<c:if test="${sessionScope.id == null}">
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입</a> <br><br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인</a> <br><br>
</c:if>

- 가장 먼저 EL 의 ${sessionScope.id != null} 로 네임 "id" 로 세션에 설정된 id 값이 있는지 확인한다

+ 또는 ${not empty sessioNScope.id} 를 if 조건으로 써도 된다

- 세션 공유값이 있으면 ~님 환영합니다 화면이 드고, 세션 공유값이 없으면 회원가입, 로그인 메뉴만 띄움 

 

링크 설정, 요청 네임값

- '회원정보 수정' : "/UpdateMember.do"

- '로그아웃' : "/Logout.do"

- '회원탈퇴' : "/DeleteMember.do"

- '회원가입' : "/MemberForm.do"

- '로그인' : "/LoginForm.do"

 

- main.jsp 를 그냥 실행하면 세션값이 없기때문에 아래 화면이 나타남

- 로그인 후에 main.jsp 로 넘어오면 세션값이 있으므로 아래 화면이 나타남


JSP Model 2 - MyBatis 회원관리 프로그램 : 로그아웃 기능

코드와 흐름 같이 설명

- 메인 페이지 main.jsp 에서 '로그아웃'  클릭시 "/Logout.do" 로 요청함

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/Logout.do" 부분만

		// 로그 아웃	
		}else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/logout.jsp");
		}

- DB연동이 필요없으므로 Service 클래스로 가지 않음

- 바로 여기서 ActionForward 객체 foward 생성 후 포워딩 방식, 포워딩 페이지를 설정함

- logout.jsp 로 Dispatcher 방식으로 포워딩

 

- logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%	// 세션 삭제
	session.invalidate();
%>

<script>
	alert("로그아웃");
	location.href="./LoginForm.do";
</script>

logout.jsp 에서 해야할 일

1. 세션 삭제

2. 로그아웃 메세지 출력 후 다시 로그인 폼으로 돌아가기

- 로그인 폼으로 돌아가기 위한 요청인 "/LoginForm.do" 로 요청

 

- 그럼 다시 Controller 클래스에서 로그인 폼으로 포워딩 한다

 

로그아웃 시에는

- Controller 로 갔다가 Service 로 가지 않고 View로 바로 포워딩해서 세션을 끊고 다시 로그인 폼으로 가면 됨

 

- 로그아웃 시도

- '로그아웃' 눌렀을때 콘솔창

 


JSP Model 2 - MyBatis 회원관리 프로그램 : 수정폼 기능

코드와 흐름 같이 설명

+ 수정폼은 DB와 연동해서 상세정보를 구해와서 수정폼에 뿌려줘야함

- 메인 페이지 main.jsp 에서 '회원정보 수정'  클릭시 "/UpdateMember.do" 로 요청함

	<a href="<%=request.getContextPath() %>/UpdateMember.do">회원정보 수정</a>  <br><br>

- 이때 아무 값도 전달하지 않음, id 값은 로그인 성공 시 부터 공유설정되어있는 상태이다

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/UpdateMember.do" 부분만

		// 회원정보 수정폼	
		}else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

- Controller 클래스에서 해당 Service 클래스로 보내줌

 

- Controller 클래스 포워딩 처리 부분

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {		// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			}else {							// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Service 에서 돌아온 뒤 여기서 포워딩 처리됨

 

- 수정폼 Service 클래스인 UpdateMember.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class UpdateMember implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("UpdateMember");
		
		HttpSession session = request.getSession();
		String id = (String)session.getAttribute("id");
		System.out.println("id:"+id);
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO member = dao.getMember(id);
		System.out.println("member:"+member);
		
		String hobby = member.getHobby();	// "공부-게임-등산-"
		String[] h = hobby.split("-");
		
		request.setAttribute("member", member);				
		request.setAttribute("h", h);				
		
		ActionForward forward = new ActionForward();	
		forward.setRedirect(false);        // dispatcher 방식으로 포워딩
		forward.setPath("/member/updateform.jsp");
		
		return forward;
	}

}

<DAO 갔다오기 전>

- session 객체를 생성 후 세션값인 id 를 가장 먼저 구해온다

- getAttribuate() 로 세션값을 구해올때는 Object 형으로 리턴하므로 "id" 네임값에 들어있는 값의 자료형인 String 형으로 다운캐스팅

- DAO 객체를 생성하고 DAO의 getMember() 메소드를 호출해서 1명에 대한 상세정보를 구해온다

- getMember() 의 매개변수로는 세션으로 구해온 id 값을 전달한다

<DAO 갔다온 후>

- DAO의 getMemger(id) 로 구해온 상세정보를 DTO 객체 member 가 받는다

- 그 객체 member 의 주솟값을 찍어봄 * 아래에 캡처

- 받아온 객체 member 를 request 객체로 공유설정해야 View 페이지에서 공유값을 받아서 뿌릴 수 있다

- 문자열들을 잘라서 String 배열 h 에 저장하고 따로 공유 설정

- DTO 객체 member 는 ${member.필드명} 형태로 View 에서 출력, 배열 h 는 forEach 태그의 items 로 들어가서 View 에서 출력

- 메소드 execute() 에서도 DAO 로부터 넘어온 예외를 다시 던짐, 메소드를 호출한 Controller 클래스에서 예외를 처리한다

- ActionForward 객체 forward 생성 후 updateform.jsp 로 Dispatcher 방식으로 포워딩 할 것임을 설정

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- 수정폼뿐 아니라 수정, 삭제시에 비번을 꺼낼때도 사용됨

- 검색되는 데이터가 1개이므로 selectOne() 메소드를 사용하고 id 가 "idcheck" 인 태그의 SQL문 호출, 사용자의 id 를 전달

- 예외를 던지므로서 메소드를 호출한 곳(Service 클래스쪽) 에 양도함

+ Service 클래스의 메소드에서도 예외를 던짐, 메소드를 호출한 Controller 클래스에서 예외를 처리한다

 

- member.xml 에서 로그인(회원 인증) 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- 전달되는 값은 id 이므로 parameterType 을 String 으로 하고, 검색한 결과를 돌려줄때는 DTO 객체로 돌려주므로 resultType 은 member 로 한다

+ parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- #{id} : selectOne() 을 호출할떄 두번째 매개변수로 넘어온 값으로 변수 id 가 넘어왔으므로 #{id} 로 그 값을 가져옴

- 검색결과를 setter 메소드로 DTO 객체에 저장시킬필요 없이 자동으로 매핑되어 DTO 객체에 저장되서 리턴된다

 

- 다음은 View 부분인 updateform.jsp 를 보자

<%@ 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="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Update.do"> 
<input type="hidden" name="id" value="${member.id}">
<table border=1 width=500 align=center>
	<caption>회원 수정</caption>
	<tr><td>ID</td>
		<td>${member.id}</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name" value="${member.name}"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1" value="${member.jumin1}">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2" value="${member.jumin2}">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid" value="${member.mailid}">@
		    <input type=text size=10 id="domain" name="domain" value="${member.domain}">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4 value="${member.tel1 }">-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4 value="${member.tel2 }">-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4 value="${member.tel3 }">
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010" <c:if test="${member.phone1 == '010' }">${'selected'}</c:if> >010</option>
				<option value="011" <c:if test="${member.phone1 == '011' }">${'selected'}</c:if> >011</option>
				<option value="016" <c:if test="${member.phone1 == '016' }">${'selected'}</c:if> >016</option>
				<option value="018" <c:if test="${member.phone1 == '018' }">${'selected'}</c:if> >018</option>
				<option value="019" <c:if test="${member.phone1 == '019' }">${'selected'}</c:if> >019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4 value="${member.phone3 }">
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post" value="${member.post }">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address" value="${member.address }"></td>
	</tr>
	<tr><td>성별</td>
		<td>
		
		<c:if test="${member.gender == '남자' }">
			<input type=radio id="male" name="gender" value="남자" checked="checked">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</c:if>
		<c:if test="${member.gender == '여자' }">
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자" checked="checked">여자
		</c:if>			
			
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부
		
			<input type="checkbox" id="h2" name="hobby" value="게임"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='게임'}">${'checked'}</c:if>
		</c:forEach> >게임
			
			<input type="checkbox" id="h3" name="hobby" value="등산"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='등산'}">${'checked'}</c:if>
		</c:forEach> >등산
			
			<input type="checkbox" id="h4" name="hobby" value="낚시"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='낚시'}">${'checked'}</c:if>
		</c:forEach> >낚시
			
			<input type="checkbox" id="h5" name="hobby" value="쇼핑"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='쇼핑'}">${'checked'}</c:if>
		</c:forEach> >쇼핑
			
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요">${member.intro }</textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원수정">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- 공유설정된 값을 받아서 수정폼에 뿌려준다

- 공유된 객체 member 안의 필드값들은 ${member.필드명} 으로 가져온다 (updateform.jsp 부분)

	<tr><td>성명</td>
		<td><input type=text id="name" name="name" value="${member.name}"></td>
	</tr>

- 공유된 배열 h 는 forEach 태그의 items 안에 "${h}" 가 들어감 (updateform.jsp 부분)

			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부

- id 값은 수정폼에서 입력양식이 아닌 출력만 하고 있으므로 따로 hidden 객체로 id 값을 넘겨줌 

+ 세션이 있지만 바로 전달하는게 좋다

- 수정폼에서 입력 후 '회원수정' 을 누르면 action 값으로 지정된 "/Update.do" 로 요청

 

- 메인 페이지 main.jsp 에서 '회원정보 수정' 을 눌러 수정폼 으로 들어가보자

- 상세정보를 구해와서 값들을 수정폼에 잘 뿌리고 있다

 

- '회원정보 수정' 을 눌렀을때 콘솔창

- DAO 에서 검색해서 돌려받은 객체 member 가 출력됨 (데이터가 저장된 곳의 주소)


수정폼에서 처리하기 힘든 부분 (updateform.jsp 부분)

- jQuery 를 쓰지 않으면 처리하기 힘든 부분들이다

- 수정폼에서 처기하기 힘든 부분들을 jQuery 를 쓰지 않고 어떻게 처리했는지 보자

 

1. 핸드폰 첫번째 자리

	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010" <c:if test="${member.phone1 == '010' }">${'selected'}</c:if> >010</option>
				<option value="011" <c:if test="${member.phone1 == '011' }">${'selected'}</c:if> >011</option>
				<option value="016" <c:if test="${member.phone1 == '016' }">${'selected'}</c:if> >016</option>
				<option value="018" <c:if test="${member.phone1 == '018' }">${'selected'}</c:if> >018</option>
				<option value="019" <c:if test="${member.phone1 == '019' }">${'selected'}</c:if> >019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4 value="${member.phone3 }">
		</td>
	</tr>

- option 태그 안에 JSTL if 태그를 넣어서 특정 숫자일때 ${'selected'} 를 출력해서 선택되게 만듬

 

2. 성별 라디오 버튼

	<tr><td>성별</td>
		<td>
		
		<c:if test="${member.gender == '남자' }">
			<input type=radio id="male" name="gender" value="남자" checked="checked">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</c:if>
		<c:if test="${member.gender == '여자' }">
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자" checked="checked">여자
		</c:if>			
			
		</td>
	</tr>

- JSTL if 태그를 넣어서 member.gender 값이 '남자' 일때 남자 라디오버튼에 checked 를 만들어서 선택되게 함

- if 태그 안에 라디오 버튼 을 넣어서 조건문을 만족할때만 나타남

 

3. 취미 체크박스

	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부
		
			<input type="checkbox" id="h2" name="hobby" value="게임"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='게임'}">${'checked'}</c:if>
		</c:forEach> >게임
			
			<input type="checkbox" id="h3" name="hobby" value="등산"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='등산'}">${'checked'}</c:if>
		</c:forEach> >등산
			
			<input type="checkbox" id="h4" name="hobby" value="낚시"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='낚시'}">${'checked'}</c:if>
		</c:forEach> >낚시
			
			<input type="checkbox" id="h5" name="hobby" value="쇼핑"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='쇼핑'}">${'checked'}</c:if>
		</c:forEach> >쇼핑
			
		</td>
	</tr>

- input 태그 안에 JSTL forEach 태그를 넣어서 배열 h 에서 값을 하나씩 가져와서 차레로 변수 i 에 저장

- 변수 i 가 '공부' 이면 공부 체크박스에 ${'checked'} 로 체크가 되게 함 

ex) h[0] = "공부", h[1] = "게임"

- 모든 체크박스마다 forEach 태그를 써서 일일히 찾아야한다

 

- jQuery 를 쓰지 않고 핸드폰 첫번째자리, 성별 라디오버튼, 취미 체크박스에 값을 뿌렸다



JSP Model 2 - MyBatis 회원관리 프로그램 : 회원 정보 수정 기능

코드와 흐름 같이 설명

- updateform.jsp 에서 '회원수정' 버튼을 누르면 "/Update.do" 로 요청함

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/Update.do" 부분만

		// 회원정보 수정	
		}else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 정보 수정 Service 클래스인 Update.java 로 이동

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class Update implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Update");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String h="";
		String[] h1 = request.getParameterValues("hobby");
		for(String h2 : h1) {
			h += h2+"-";			// h = "공부-게임-등산-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(member.getId());
		
		// 비번 비교
		if(old.getPasswd().equals(member.getPasswd())) { //비번 일치시
			int result = dao.update(member);
			if(result==1) System.out.println("회원수정 성공");
			
		}else {											 //비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}	
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/member/main.jsp");
		
		return forward;
	}

}

- 수정폼에서 입력값들이 넘어오므로 한글 인코딩

- id 도 hidden 객체로 넘어왔고, 사용자가 입력한 비번도 넘어왔다

- DTO 객체 member 를 생성해서 setter 메소드로 id 와 사용자가 수정폼에 입력한 값들을 member 에 저장

- DAO 의 메소드를 2번 호출해야함, 비번 확인을 위해 상세정보 가져올때 1번, 수정할때 1번

- DAO 객체 생성 후 DB에 저장된 비번을 가져오기 위해 DAO 의 getMember() 메소드를 호출하면서 수정폼에서 넘겨준 id 를 매개변수로 넘겨준다

<DAO getMember() 에서 돌아온 후>

- old.getPasswd() 는 DB에 저장된 비번, member.getPasswd() 는 사용자가 입력한 비번

- 두 비번이 일치시 수정 하는 메소드인 DAO의 update() 메소드 호출, 수정할 데이터를 저장한 DTO 객체 member 를 넘김

<DAO update() 에서 돌아온 후>

- 수정 성공시 1을 돌려주기로 했으므로, 1을 리턴받으면 "회원수정 성공" 메세지를 콘솔에 출력

- 비번 일치가 되면 수정 후 if-else 문 아래 문장인 ActionForward 객체 foward 생성 후, main.jsp 로 Dispatcher 방식 으로 포워딩 페이지, 방식 지정

- 성공시 Controller 클래스로 가서 main.jsp 로 포워딩하면 아직 세션이 남아있으므로 세션이 있을때의 화면이 뜬다

- 비번이 일치하지 않으면 메세지를 출력한 후 다시 이전페이지인 수정폼으로 돌아간다

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- 1명에 대한 상세정보를 구하므로 selectOne() 메소드, id 값이 "idcheck" 인 Mapper 파일의 SQL문 호출

- 사용자가 입력한 id 값을 매개변수로 넘겨준다

- 돌려받은 객체 member 를 Service 클래스로 리턴

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- DTO 객체에 상세정보가 자동으로 저장(매핑) 되어 돌려준다

 

- MemberDAO.java 에서 update() 메소드 부분만

	// 회원정보 수정
	public int update(MemberDTO member) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.update("update", member);

		return result;
	}

- SqlSession 객체를 변수 session 으로 받아오고 update SQL문을 부를 것이므로 session.update() 메소드를 호출

- Mapper 파일에서 id 가 "update" 인 태그의 SQL문 호출, 호출하며 수정할 데이터를 저장한 객체 member 를 넘김

- session.update() 가 잘 실행되면 1 을 리턴하고, 그걸 result 변수로 받아서 다시 Service 클래스로 돌려줌

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 update)

	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

- DTO 객체 member 가 넘어왔으므로 parameterType 은 전달되는 값의 자료형인 DTO alias 값 member 를 작성

- DTO 객체가 전달될때 DTO 객체 안의 내용을 가져오는 방법 : #{} 안에 DTO 객체의 필드명을 쓴다

ex) #{name} 은 member.getName() 과 같은 의미, #{jumin1} 은 member.getJumin1() 과 같은 의미

- 수정된 데이터 개수를 자동으로 돌려준다, 성공시 1 반환


JSP Model 2 - MyBatis 회원관리 프로그램 : 회원 탈퇴 기능

코드와 흐름 같이 설명

- 탈퇴 폼 + 탈퇴 기능 한번에 설명

- DB 연동이 필요하지 않으므로 main.jsp 에서 '회원탈퇴' 클릭시 Controller 클래스로 가서 바로 View 탈퇴 폼으로 가기

	<a href="<%=request.getContextPath() %>/DeleteMember.do">회원탈퇴</a> <br><br>

- 회원 탈퇴 폼으로 가기 위해 "/DeleteMember.do" 로 요청함

- Controller 클래스로 넘어감

 

- Controller 클래스에서 "/DeleteMember.do" 부분만

		// 회원탈퇴 폼	
		}else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/deleteform.jsp");
		}

- DB연동이 필요하지 않으므로 Service 클래스가 아닌 View 페이지로 바로 이동

- 여기서 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 지정

- 탈퇴 폼 View 페이지인 deleteform.jsp 로 간다

 

- deleteform.jsp

<%@ 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="http://code.jquery.com/jquery-latest.js"></script>

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Delete.do"> 
<input type="hidden" name="id" value="${sessionScope.id}">
<table border=1 width=500 align=center>
	<caption>회원 탈퇴</caption>	
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>	
	<tr><td colspan=2 align=center>
			<input type=submit value="회원탈퇴">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- 아직 SESSION 영역이므로 공유설정된 세션값인 id 가 남아있다, 

- 세션 값을 ${sessionScope.id} 로 가져와서 hidden 객체를 통해 "id" 변수에 저장해서 세션 값을 "/Delete.do" 로 넘긴다

+ hidden 으로 id 값을 "/Delete.do" 로 넘기지 않아도 Delete.java 에서 공유된 세션에서 id 값을 가져와도 된다

+ 하지만 session 객체도 만들어아하고 번거로우므로 그냥 hidden 으로 비밀번호와 함께 form 으로 같이 넘기는 것

- 탈퇴를 위해선 탈퇴폼에 사용자가 비밀번호를 입력해야함

- 해당하는 회원 1명의 정보를 구하기 위해 id 값을 꼭 넘겨야함

- hidden 으로 넘어가는 id 와 입력양식으로 넘어가는 비밀번호 passwd 가 "/Delete.do" 로 요청하며 post 방식으로 넘어감

 

- Controller 클래스에서 "/Delete.do" 부분만

		// 회원탈퇴	
		}else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 회원 탈퇴 Service 클래스인 Delete.java 로 이동

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class Delete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Delete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(id);
		
		// 비번 비교
		if(old.getPasswd().equals(passwd)) {	// 비번 일치시
			int result = dao.delete(id);
			if(result == 1) System.out.println("회원삭제 성공");
			
			session.invalidate();               // 세션 삭제
			
		}else {									// 비번 불일치시
			out.println("<script>");	
			out.println("alert('비번이 일치하지 않습니다.');");	
			out.println("history.go(-1);");	
			out.println("</script>");	
			out.close();
			
			return null;			
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/LoginForm.do");
		
		return forward;
	}

}

- 회원 삭제(탈퇴) 성공시에 필요한 session 객체, out 객체 생성

- hidden 으로 넘어온 id 값과, 입력양식에서 넘어온 비번 passwd 를 getParameter() 로 받아서 변수 id, passwd 에 저장

- 비번 비교를 위해 DAO의 getMember() 메소드를 호출해서 해당 id 의 회원에 대한 상세정보를 가져온다

- DAO 에서 메소드 호출을 2번 해야한다, 비번을 가져오기 위해 상세정보 가져올때 1번, 회원 삭제할때 1번

<DAO getMember() 에서 돌아온 후>

- old.getPasswd() 는 DB의 비번, passwd 는 사용자가 탈퇴폼에 입력한 비번, 두 비번 일치시 DAO 의 delete() 메소드 호출

- delete() 를 호출하며 id 값을 매개변수로 전달

<DAO update() 에서 돌아온 후>

- 1 을 리턴받으면 성공이라 여기고, "회원삭제 성공" 메세지 출력하고 세션을 삭제한다, SESSION 영역 끝

- 삭제에 성공하면 if-else 문 아래에서 forward 객체를 생성하고 "/LoginForm.do" 로 포워딩 페이지 설정, Dispatcher 방식으로 포워딩 방식 설정

-  삭제에 실패하면 메세지를 출력하고 이전 페이지인 삭제폼으로 이동, return null; 을 추가해서 execute() 를 빠져나가며 아래의 코드를 forward 설정 코드를 실행하지 않도록 함

- 이전 페이지인 삭제폼으로 이동하는데 아래의 forward 설정 코드를 실행하면 오류가 난다. 한번에 두 곳으로 갈 수 없으므로


- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- MemberDAO.java 에서 delete() 메소드 부분만

	// 회원 탈퇴
	public int delete(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.delete("delete", id);		
		
		return result;
	}

- delete SQL문을 사용하므로 SqlSession 객체의 메소드 delete() 를 사용해야한다

- Mapper 파일에서 id 가 "delete' 인 SQL문을 호출, 매개변수로는 삭제하고자 하는 회원의 id 값을 넘김

- session.delete() 에서 삭제 성공시 리턴하는 1을 result 변수에 저장 후 다시 Service 클래스로 1 리턴

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 delete)

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete>

- 삭제하고자 하는 회원의 id 값이 넘어오므로 parameterType 은 String 이어야한다

- #{id} : session.delete() 에서 두번째 매개변수로 넘어온 변수 id 의 값을 ${id} 로 가져옴

- 자동으로 삭제된 데이터 개수를 리턴해줌 (성공시 1 리턴)

 

- main.jsp 에서 '회원탈퇴' 클릭시 콘솔창 (탈퇴 폼)

 

- 탈퇴 폼에서 맞는 비밀번호를 입력하고 '회원탈퇴' 클릭시

- 탈퇴에 성공하고 로그인 폼으로 포워딩됨

- 탈퇴 폼에서 '회원탈퇴' 클릭해서 회원탈퇴에 성공 했을때 콘솔 창


JSP Model 2 - MyBatis 연동 구조



JSP Model 2 - MyBatis 연동 : 게시판 프로그램

Model 2 과 MyBatis 연동

- 클라우드에서 Model 2 과 MyBatis 가 이미 연동이 완료된 게시판 로그램 mybatisboard 프로젝트 import

- zip 파일을 압축 풀어서 import

- import 하는 프로젝트는 maven 프로젝트이다

- Maven 프로젝트이므로 구조는 다음과 같다

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다 (Controller, Service, DAO, DTO)

+ Model 2 이므로 src/main/java 안에서도 DTO, DAO, Service, Controller 가 서로 다른 패키지 안에 들어가있음

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다 (주로 xml 파일들)

- src/main/webapp 안에 View 파일 등이 들어간다

- Maven 프로젝트는 WEB-INF 폴더 안에 lib 폴더가 생성되지 않는다, 대신 pom.xml 에서 라이브러리 추가함

- boardupload 는 첨부파일이 저장될 폴더이다

 

- import한 mybatismember 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>mybatisboard</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>ibatisboard Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 복사 시작 -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>mybatisboard</finalName>
	</build>
</project>

- 프로젝트 시작 시 가장 먼저 라이브러리를 구하는 작업을 해야한다

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

- db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

- DB 접속에 대한 내용들이 들어가 있다

- 테이블을 생성할때 totoro 계정에서 생성해야 한다


- 테이블을 만들자

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 model2.sql 에서 totoro 계정으로 연결한 후 model22테이블을 생성하자

- 커넥션 프로파일 설정

- model2.sql

-- 모델2 게시판
select * from tab;
select * from seq;
select * from model22;

create table model22(
	board_num number,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50),
	board_re_ref number,
	board_re_lev number,
	board_re_seq number,
	board_readcount number,
	board_date timestamp,
	primary key(board_num)
);

create sequence model22_seq
start with 1
increment by 1
nocache;

- 테이블 model22 와 시퀀스 model22_seq 를 생성

- board_num 이 primary key 가 되고, board_num 에는 시퀀스 model22_seq 값이 들어갈 것

 

컬럼 설명

- board_re_ref : 원문은 시퀀스, 댓글은 부모의 ref 값과 동일값

- board_re_lev : 원문은 0, 1단계 댓글은 1, 2단계 댓글은 2

- board_re_seq : 원문은 0, 댓글, 대댓글의 board_re_seq 는 계속 바뀌는 값


JSP Model 2 - MyBatis 게시판 프로그램 : 기본 파일들

 

흐름 보기

- index.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>

 모델2 게시판
 
<%
	response.sendRedirect("./BoardListAction.do");
%> 

<script>
//	location.href="./BoardListAction.do";
</script>

</body>
</html>

- index.jsp 실행이나 현재 프로젝트 Run As - Run on Server 로 실행시키자

- 목록을 요청하는 "/BoardListAction.do" 로 요청하고 있다, 목록을 요청하고 목록을 출력하는 페이지까지 온다

- 그래서 목록 페이지가 보인다

 

- '글쓰기' 를 눌러서 글 작성 폼으로 가서 게시판에 글을 작성해보자

- 글을 '등록' 하면 아래처럼 목록을 다시 가져와서 목록 페이지로 돌아간다

- 글 작성 성공시 콘솔창

path:C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\mybatisboard\boardupload

- 첨부파일도 성공적으로 서버에 업로드 되었음을 확인 가능

 

- 글을 클릭하면 상세 페이지로 넘어가면서 조회수를 1 증가시킴

- 댓글을 달아보자

- 글 삭제하기


- DTO 인 BoardBean.java 파일을 보자

- MyBatis 로 자동 매핑이 가능하도록 테이블의 컬럼명과 DTO 의 프로퍼티명을 일치시켰다

package model;

import java.sql.Timestamp;

public class BoardBean {
	private int board_num;
	private String board_name;
	private String board_pass;
	private String board_subject;
	private String board_content;
	private String board_file;
	private int board_re_ref;
	private int board_re_lev;
	private int board_re_seq;
	private int board_readcount;
	private Timestamp board_date;	
	
	public int getBoard_num() {
		return board_num;
	}
	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}
	public String getBoard_name() {
		return board_name;
	}
	public void setBoard_name(String board_name) {
		this.board_name = board_name;
	}
	public String getBoard_pass() {
		return board_pass;
	}
	public void setBoard_pass(String board_pass) {
		this.board_pass = board_pass;
	}
	public String getBoard_subject() {
		return board_subject;
	}
	public void setBoard_subject(String board_subject) {
		this.board_subject = board_subject;
	}
	public String getBoard_content() {
		return board_content;
	}
	public void setBoard_content(String board_content) {
		this.board_content = board_content;
	}
	public String getBoard_file() {
		return board_file;
	}
	public void setBoard_file(String board_file) {
		this.board_file = board_file;
	}
	public int getBoard_re_ref() {
		return board_re_ref;
	}
	public void setBoard_re_ref(int board_re_ref) {
		this.board_re_ref = board_re_ref;
	}
	public int getBoard_re_lev() {
		return board_re_lev;
	}
	public void setBoard_re_lev(int board_re_lev) {
		this.board_re_lev = board_re_lev;
	}
	public int getBoard_re_seq() {
		return board_re_seq;
	}
	public void setBoard_re_seq(int board_re_seq) {
		this.board_re_seq = board_re_seq;
	}
	public int getBoard_readcount() {
		return board_readcount;
	}
	public void setBoard_readcount(int board_readcount) {
		this.board_readcount = board_readcount;
	}
	public Timestamp getBoard_date() {
		return board_date;
	}
	public void setBoard_date(Timestamp board_date) {
		this.board_date = board_date;
	}
}

- 테이블의 컬럼명과 DTO 의 프로퍼티명을 일치시켜야 자동 매핑이 가능하다


- Controller 클래스 BoardFrontController.java 를 보자

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
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 service.Action;
import service.ActionForward;
import service.BoardAddAction;
import service.BoardDelete;
import service.BoardDetailAction;
import service.BoardListAction;
import service.BoardModify;
import service.BoardModifyAction;
import service.BoardReply;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
					
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI:"+requestURI);
		System.out.println("contextPath:"+contextPath);
		System.out.println("command:"+command);
		
		Action action = null;
		ActionForward forward = null;
		
		// 글작성
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		
		// 글작성 폼	
		}else if(command.equals("/BoardForm.do")) {	
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_write.jsp");
			
		// 글목록	
		}else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 상세 페이지	
		}else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 댓글 폼	
		}else if(command.equals("/BoardReplyAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_reply.jsp");
			
		// 댓글	
		}else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 수정 폼	
		}else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 수정	
		}else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 삭제 폼	
		}else if(command.equals("/BoardDeleteAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_delete.jsp");
			
		// 삭제	
		}else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		
		if(forward != null) {
			if(forward.isRedirect()) {	 // redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
				
			}else {						// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}			
		}		
		
	} // doProcess() end
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
//		response.getWriter().append("Served at: ").append(request.getContextPath());
		System.out.println("get");		
	
		doProcess(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
//		doGet(request, response);
		System.out.println("post");
		
		doProcess(request, response);
	}

}

- command 변수에 저장된 값에 따라 다른 Service 클래스로 이동 또는 View로 포워딩

 

+ 인터페이스 Action.java

- Service 클래스들이 인터페이스를 상속함으로서 안의 추상메소드를 반드시 메소드 오버라이딩 해야함 

- 구현클래스가 여러개 있을때 이 구현 클래스들을 통일성있게 설계하기 위해 인터페이스 상속

 

+ 클래스 ActionForward.java

- 어느 페이지로 포워딩할지, 어떤 방식으로 포워딩할지 설정하는 클래스

 

- DAO 클래스인 BoardDAO.java 를 보자

- BoardDAO.java

package dao;

import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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 model.BoardBean;

public class BoardDAO {

	private static BoardDAO instance = new BoardDAO();

	public static BoardDAO getInstance() {
		return instance;
	}
		
	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}
	
	
	//글작성(원문작성)
	public int insert(BoardBean board) throws Exception {
		int result=0;
		SqlSession session = getSession();
		result = session.insert("board_insert", board);		
		System.out.println("result:"+result);
		
		return result;
	}
	
	
	// 총데이터 갯수 구하기
	public int getCount() throws Exception{
		int result=0;
		SqlSession session=getSession();
		result = (Integer)session.selectOne("board_count");
		
		return result;
	}
	
	
	// 데이터 목록
	public List getList(int page) throws Exception {
//	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", page); 
		
		return list;
	}
	
	
	// 조회수 증가
	public void updateCount(int board_num) throws Exception{
		SqlSession session = getSession();
		session.update("board_updatecount", board_num);
	}
		
	
	// 댓글 출력 순서 (board_re_seq값 증가)
	public void updateSeq(BoardBean board) {
		SqlSession session = getSession();
		session.update("board_updateseq", board);
	}
		

	// 댓글작성
	public void boardReply(BoardBean board) {
		SqlSession session = getSession();
		session.insert("board_reply", board);
	}
	
	
	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}
	
	
	// 글수정
	public void update(BoardBean board) throws Exception {
		SqlSession session=getSession();
		session.update("board_update", board);
	}

	
	// 글삭제
	public void delete(int board_num) {
		SqlSession session=getSession();
		session.delete("board_delete", board_num);
	}

}

- DB와 연동하기 위한 여러 메소드들만으로 구성되어있다

- getSession() 메소드는 SqlSession 객체를 구해오는 메소드이고, 이 객체가 있어야 지원되는 5개 메소드 사용 가능

 

- MyBatis 환경설정 파일

<?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="db.properties" />
	<typeAliases>
		<typeAlias type="model.BoardBean" alias="board"></typeAlias>
	</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="board.xml" />
	</mappers>
</configuration>

1) DTO 인 BoardBean 클래스의 alias (별칭)를 "board" 로 설정함

2) db.properties 파일의 변수들을 불러와서 DB 와 관련된 내용들을 작성함

- property 태그의 name 은 정해져있다, "driver", "url", "username", "password" 는 바뀌지 않음

3) Mapper 파일 board.xml 을 mapper 태그로 불러오고 있다

- Mapper 파일은 테이블 개수에 비례한다

- board.xml 에 따로 패키지가 없다, 즉 같은 폴더 하위에 있으므로 패키지명을 써주지 않고 board.xml 만 쓴다

 

- Mapper 파일 board.xml 을 보자

- board.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="myboard">

	<!-- 글작성(원문) -->
	<insert id="board_insert" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},model22_seq.nextval,0,0,0,sysdate)
	</insert>
	
	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>
	
	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select> 
	
	<!-- Map 전달 -->
	<!-- <select id="board_list" parameterType="Map" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= #{start} and rnum &lt;= #{end}
	</select> -->
	
	<!-- 조회수 증가 -->
	<update id="board_updatecount" parameterType="int">
     update model22 set board_readcount=board_readcount+1 where board_num = #{board_num}	
	</update>
	
	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>
	
	<!-- 댓글 출력순서 -->
	<update id="board_updateseq" parameterType="board">
	 update model22 set board_re_seq=board_re_seq+1 
	  where board_re_ref = #{board_re_ref} and board_re_seq &gt; #{board_re_seq}
	</update>
	
	<!-- 댓글 작성 -->
	<insert id="board_reply" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,sysdate)
	</insert>

	<!-- 글수정 -->
	<update id="board_update" parameterType="board">
	 update model22 set board_name=#{board_name}, board_subject=#{board_subject},
	 board_content=#{board_content} where board_num=#{board_num}
	</update>

	<!-- 글삭제 -->
	<delete id="board_delete" parameterType="int">
	 delete from model22 where board_num=#{board_num}
	</delete>



	<!-- <select id="getSomething" parameterType="int" resultType="#package.#modelname">
		SELECT
		columnname1,
		columnname2,
		columnname3
		FROM tablename1
		WHERE columnname1 = #{value}
	</select>

	<resultMap type="#modelname" id="YourResultSet">
		<id property="param1" column="columnname1" />
		<result property="param2" column="columnname2" />
		<result property="param3" column="columnname3" />
	</resultMap>

	<select id="getAll" resultMap="YourResultSet">
		SELECT * FROM tablename1
	</select>

	<insert id="insertSomething" parameterType="#modelname">
		INSERT INTO tablename1 (columnname1, columnname2, columnname3)
		VALUES(#{value1},#{value2},#{value3})
	</insert>

	<update id="updateSomething" parameterType="#modelname">
		UPDATE tablename1
		SET
		columnname1=#{param1}
		WHERE
		columnname2 = #{param2}
	</update>

	<delete id="deleteSomething" parameterType="int">
		DELETE FROM tablename1 WHERE
		columnname1 = #{param1}
	</delete> -->

</mapper>

- MyBatis 환경설정 파일에서 이 Mapper 파일 board.xml 을 mapper 태그로 불러왔으므로 이 파일 board.xml 의 루트 엘리먼트는 mapper

 

글 목록 가져오기 주의 (board.xml 부분)

	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select>

- MyBatis 는 값이든 주소값이든 전달할 값을 단 1개만 전달 가능하므로 페이지번호 page 를 넘긴다

- 페이지번호 page 만 있으면 startRow 와 endRow 를 여기서 계산 할 수 있기 때문이다

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 작성 기능 

- 내일

복습

Maven

- Spring 프로젝트는 Maven 으로만 라이브러리 관리 가능

- Springboo 프로젝트는 Maven, Gradle 둘 다 사용 가능

 

Maven 파일 구조

- src/main/java 에 확장자가 Java 인 파일을 넣는다

- src/main/resource 는 MyBatis 의 환경 설정 파일, 기존에 DAO 에 있었던 SQL문을 따로 빼서 만든 xml 파일

- src/main/webapp 는 View 페이지가 들어감, index 파일도 아 폴더 내에 들어간다 , 기존의 WebContent 폴더 역할

- src/main/webapp/WEB-INF 안에 프로젝트 환경 설정 파일인 web.xml 이 들어감, 그리고 기존의 lib 폴더는 사라짐

- DAO 클래스는 존재하지만 DAO 클래스의 SQL문을 따로 빼서 따로 파일을 만드는데, 그걸 mapper 파일이라고 부른다

- pom.xml 이 Maven 의 환경 설정 파일, 여기에서 의존 라이브러리를 추가

 

pom.xml 환경 설정 파일

- 오라클 jdbc, mysql jdbc, cos, mybatis, jstl 등 많은 라이브러리를 다운 받았었다

 

local repository 위치

 

프로젝트 구분

- Maven 프로젝트는 m , Spring 프로젝트는 s 가 붙어있다

 

Maven 프로젝트 / Spring 프로젝트 주의

- Maven 프로젝트, Spring 프로젝트는 war 파일로 export / import 하면 Dynamic Web Project 로 풀리므로 주의

- Maven 프로젝트, Spring 프로젝트는 프로젝트를 통째로 복사 붙여넣기해서 export 해야한다

- 이클립스에서 Spring 프로젝트 만드려면 플러그인 추가 또는 STS 라는 툴을 다운받아 사용

- Spring 프로젝트는 Mavne 프로젝트와 달리 기본적인 라이브러리가 이미 pom.xml 에 다 들어가 있다

- 아파치 톰캣이 자동으로 추가되지 않는 경우가 만흥므로 직접 추가해 준다


MyBatis

데이터베이스 접속 방식

- DB 접속 방식 3번째인 ORM 프레임워크 중 MyBatis 사용할 것

 

ORM 프레임워크

- ORM 프레임워크에서 Object 는 DTO 객체를 의미, Mapping 은 연결을 의미

- 즉 DTO 클래스의 필드와 테이블 안의 컬럼을 매핑시키는 것, 매핑 처리를 위해선 두 이름을 일치시켜야함

- ORM 프레임워크를 사용시 코드가 간단해진다

- 전자정부 프레임 워크에서는 MyBatis 사용함

- MyBatis 로 Model 1 에서도 연동해보고, Model 2 에서도 연동해 볼 것

 

프로젝트에 MyBatis 연동하기 위해 필요한 파일

- MyBatis 와 연동하기 위해서는 크게 2가지 파일이 필요하다

1. MyBatis 의 환경설정 파일

2. 기존 DAO 파일에 들어있던 내용인 Mapper 파일

- 이 2개 파일이 src/main/resources 폴더 안에 있어야 한다 

 

프로젝트에 MyBatis 연동하는 방법

1. MarketPlace 에서 MyBatis Plugin 프로그램을 설치해야한다

- Mapper 파일을 생성할 수 있는 메뉴가 추가됨

- 과거에는 MarketPlace 에서 플러그인 설치시 MaBatis 환경설정 파일도 만들 수 있었지만 현재는 불가능

- 그리고 이클립스를 재시작한다

 

2. Mapper 파일을 생성해야 한다

- maventest 프로젝트의 resources 폴더에서 오른쪽 마우스 클릭

- 플러그인인을 설치시 Mapper 파일을 생성할 수 있는 메뉴가 나타남

- resources 폴더 안에 Mapper 파일이 들어갔다

- mapper.xml (예시)

- 이 파일 안에 DAO 의 insert, update, delete, select 가 들어감

- xml 파일이므로 루트 엘리먼트가 있어야하고, 루트 엘리먼트가 mapper

- insert 태그 사이에 insert SQL문 작성, 다른 DML 문도 마찬가지, 태그에는 각 SQL문을 구분하기 위한 id 가 있다

 

3. 다음으로는 MyBatis 환경설정 파일이 다운받아진 프로젝트를 import 해서 MaBatis 환경설정 파일을 살펴보자

- 현재는 플러그인 추가해도 MaBatis 환경설정 파일 생성 불가

 

MaBatis 프로젝트 import 하기

- mybatistest 프로젝트 import

- mabatistest.zip 파일을 다운로드 받아서 압축 해제 후 import

- mybatistest 가 import 되었다

- MyBatis 와 연동되는 이 3개 파일들은 모두 src/main/resources 폴더 안에 저장되어 있어야 함

 

MyBatis 연동 관련 파일 살펴보기

1. db.properties

#mysql
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/jsptest
#jdbc.username=jspid
#jdbc.password=jsppass

#oracle
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=scott
jdbc.password=tiger

- DB 접속에 필요한 내용들이 들어간다

- 위에는 mysql 과 연동하는 내용, 아래는 oracle 과 연동하는 내용을 작성했다

- 왼쪽은 변수명, 오른쪽은 변수에 할당될 값이다

- jdbc.driverClassName 변수명은 정해져 있는 이름이다, 경로를 oracle.jdbc.driver.OracleDriver 로 설정

- jdbc.url 변수에는 jdbc:oracle:thin:@localhost:1521:xe 로 DB 경로를 설정

- 현재는 scott 계정으로 연결 설정했다

- # 은 주석

- mybatis-config.xml 파일에서 이 파일 db.properties를 불러온다

 

2. mybatis-config.xml

- MyBatis 의 환경설정 파일이다

+ 환경 설정 파일은 대부분 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="db.properties" />
	<typeAliases>
		<typeAlias type="#package.#modelname" alias="#modelname"></typeAlias>
	</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="#package/#mapper.xml" />
	</mappers>
</configuration>

- 위의 DOCTYPE configuration 내용을 삭제하면 안된다, mybatis.org/dtd/mabatis-3-config.dtd 문서를 불러오고 있다

- 루트 엘리먼트는 configuration

 

Mabatis 환경설정 파일인 mybatis-config.xml 파일 안에 들어가는 내용 3가지

0) properties 태그에서 db.properties 파일을 불러온다

1) typeAliases 태그로 DTO 클래스의 경로를 alias 로 간단한 별칭을 설정

- typeAlias 태그의 type 속성에서 DTO 클래스의 경로를 패키지부터 해당 클래스까지의 경로로 작성

- typeAlias 태그의 alias 속성에서 alias는 별칭을 의미, 그 경로를 짧은 별칭으로 설정하는 것

- DTO 클래스가 많아지면 typealias 태그를 추가해주면 된다

2) environments 태그 안의 property 태그에서 DB 접속 관련 내용들 작성

- property 태그는 클래스의 멤버변수(필드, 프로퍼티) 를 의미, name 속성값은 고정되어있고, values 값으로 db.property 안의 변수명을 이용해서 변수값을 불러오고 있다

+ 변수명으로 불러오는 대신 그 자리에 바로 값을 써도 된다

ex) <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>

- 그렇게 드라이브의 경로, DB 경로, 계정명, 비밀번호 등을 불러옴

- db 접속하는 내용들이 들어간다

- 위에서 db.property 파일을 불러왔으므로 가능함

3) mappers 태그 안의 mapper 태그로 SQL문이 들어간 파일인 Mapper 파일을 불러오기

- mapper 태그의 resource 속성에 패키지명을 적어주고, Mapper 파일명을 써주기

+ 여기서 mapper 태그 안에서 Mapper 파일을 불러오므로 Mapper 파일의 루트 엘리먼트도 mapper 이다

- Mapper 파일은 테이블의 개수에 비례해서 늘어난다

- Mapper 파일이 늘어나면 mapper 태그를 추가해주면 된다

 

3. mapper.xml

- Mapper 파일 이다

- 이름은 반드시 mapper.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="#package.#mapperinterfacename">

	<select id="getSomething" parameterType="int" resultType="#package.#modelname">
		SELECT
		columnname1,
		columnname2,
		columnname3
		FROM tablename1
		WHERE columnname1 = #{value}
	</select>

	<resultMap type="#modelname" id="YourResultSet">
		<id property="param1" column="columnname1" />
		<result property="param2" column="columnname2" />
		<result property="param3" column="columnname3" />
	</resultMap>

	<select id="getAll" resultMap="YourResultSet">
		SELECT * FROM tablename1
	</select>

	<insert id="insertSomething" parameterType="#modelname">
		INSERT INTO tablename1 (columnname1, columnname2, columnname3)
		VALUES(#{value1},#{value2},#{value3})
	</insert>

	<update id="updateSomething" parameterType="#modelname">
		UPDATE tablename1
		SET
		columnname1=#{param1}
		WHERE
		columnname2 = #{param2}
	</update>

	<delete id="deleteSomething" parameterType="int">
		DELETE FROM tablename1 WHERE
		columnname1 = #{param1}
	</delete>

</mapper>

- select 문은 select 태그로 감싸준다

- 태그들을 구분하기 위해 id 값을 다르게 설정한다

- DAO 파일에서 이 Mapper 파일의 id 값을 불러서 사용한다

<값 전달하기 : parameterType>

- insert , select 할 데이터는 parameterType 속성으로 값을 받는다

- parameterType 속성값에 전달되는 값의 자료형을 작성

< 검색한 값 돌려받기 : returnType >
- select 해서 검색한 결과를 resultType 속성으로 값을 돌려준다

- select SQL문만 returnType 속성을 사용함

- resultType 속성값에는 값을 돌려줄때의 자료형을 작성

- #{} 에서 getter 메소드로 불러올 값들을 작성함

< 검색한 값 돌려받기 : 데이터 1개 검색 >

- 데이터를 1개 검색했을때는 if(rs.next()) 로 가져와서 DTO 객체에 저장했었다, 일일히 setter 메소드로 저장했었음

- 이제는 일일히 setter 메소드를 작성하지 않고 컬럼명에 일치하는 DTO 프로퍼티가 있을땐 자동으로 setter 메소드를 불러서 매핑해준다

- returnType 에 DTO 경로 별칭만 써주면 테이블명과 DTO 프로퍼티가 일치하는 경우에 자동으로 매핑

< 검색한 값 돌려받기 : 데이터 2개 이상 검색 >

- 2개 이상의 데이터를 검색할때는 리스트를 통해 값을 받았었다, 여기서는 resultType 으로 DTO 클래스의 alias 명을 쓰면 된다, 리스트에 저장시키는 것 까지 자동으로 해줌

- returnType 에 DTO 경로 별칭만 써주면 테이블명과 DTO 프로퍼티가 일치하는 경우에 자동으로 매핑, 리스트 저장까지 해줌

<자료형이 DTO일땐>

- 전달되는 값의 자료형이나 돌려주는 값의 자료형이 DTO일땐, MyBatis 환경설정 파일에서 설정한 DTO 클래스의 alias 값이 파라미터 타입값으로 들어가야한다

<resultMap>

- resultMap 은 컬럼명과 DTO 프로퍼티명이 일치하지 않을때, resultMap 으로 일일히 매핑을 잡아줘야한다

- resultMap 태그 안에서 컬럼과 프로퍼티를 일일히 매핑


JSP - MyBatis 연동

Model 1 과 MyBatis 연동

- 클라우드에서 Model 1 과 MyBatis 가 이미 연동이 완료된 member22 프로젝트 import

- zip 파일을 압축 풀어서 import

+ 그냥 member22 파일을 바로 import 하면 . 으로 시작된 파일들이 import 안된다

+ 반드시 zip 파일을 풀어서 생성된 폴더를 import 하기

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다

- src/main/webapp 안에 View 파일 등이 들어간다

 

- import한 member22 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>member22</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>member22 Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-impl</artifactId>
			<version>1.2.5</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>
		
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>member22</finalName>
	</build>
</project>

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

Model 1 에 MyBatis 연동시 구조

- Model 1 에서 MyBatis 를 연동하면 DAO 이후부터만 달라진다

- 앞부분은 Model 1과 동일하고 달라지는 내용은 DAO 이후 부터 이다

- DAO 메소드 안의 복잡한 내용들이 빠져서 MyBatis 환경설정 파일로 간다

- 안의 SQL문들이 빠져서 Mapper 파일인 member.xml 으로 간다

 

- MyBatis 환경설정 파일인 mybatis-config.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="db.properties" />
	<typeAliases>
		<typeAlias type="model.Member" alias="member"></typeAlias>
	</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="member.xml" />
	</mappers>
</configuration>

- model 패키지 안의 DTO 경로를 alias 속성을 통해 member 라는 별칭으로 지정

- mapper 태그로 Mapper 파일인 member.xml 파일을 불러옴, 같은 폴더 안에 있으므로 이름만으로 불러옴

- Mapper 파일이 다른 패키지안에 있으면 불러올 때 패키지명도 명시해줘야함

 

- Mapper 파일인 member.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="memberns">

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>
	
	<select id="list" resultType="member">
		select * from member22
	</select>
	
	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>	
	
	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>
	
	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>
	
</mapper>

 

- db.properties

#mysql
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/jsptest
#jdbc.username=jspid
#jdbc.password=jsppass

#oracle
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

 

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 member.sql 에서 totoro 계정으로 연결한 후 member22 테이블을 생성하자

- member.sql

-- model1 과 mybatis 연동
select * from tab;
select * from member22;

create table member22 (
	id varchar2(10) primary key,
	password varchar2(10)
);

- 테이블 member22 를 생성하자

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야한다

+ 간소화 시킨 테이블임


흐름 설명

- src/main/java 에서 model 패키지의 DTO 클래스인 Member.java 를 보자

package model;

public class Member {
	private String id;
	private String password;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

- 프로퍼티명이 컬럼명과 동일하게 id, password 로 되어있다, 쉽게 매핑 가능해짐

 

- 회원가입 페이지인 joinForm.jsp 에서 form 의 name 값도 똑같이 id, password 로 일치시킨다

- 그렇게 해야 setProperty 로 쉽게 저장 가능 (Model 1)

- joinForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>회원가입</h2>
<form action="joinPro.jsp">
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
</body>
</html>

 

- loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>로그인</h2>
<form action="loginPro.jsp" >
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
<p><a href="joinForm.jsp">회원가입</a>
</body>

</html>

 

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<html>
<body>
<h2>Hello World!</h2>

<script>
	location.href="member/loginForm.jsp";
</script>

</body>
</html>

- index 파일은 webapp 폴더 안에 저장되어 있어야함

- index 파일을 실행시켜도 되고, 현재 프로젝트 선택 후 Run As - Run on Server 로 실행 하면 index 파일이 실행됨

- 실행시 index.jsp 파일에서 loginForm.jsp 파일을 실행시킴

- 회원 가입을 해보자, 클릭 시 joinForm.jsp 파일로 넘어감

- '확인' 시 joinPro.jsp 파일로 넘어가면서 id, password 를 네임값으로 해서 입력된 값을 전달

- joinPro.jsp 에서 setProperty 로 DTO 객체 mem에 값 설정 후 insert(mem) 호출

- 가입 후 로그인

- master 란 아이디로 가입시 관리자

- 관리자로 로그인 시 나오는 내용이 달라짐

- 회원명단을 볼 수 있는 메뉴가 나타남

- 즉, 관리자로 로그인 시 나오는 내용이 달라짐

- 회원 수정이나 회원 탈퇴 시킬 수 있다

 

- 앞부분은 Model 1과 동일하고 달라지는 내용은 DAO 이후 부터 이다

- DAO 클래스가 SQL문을 실행하는 것은 같지만, DAO 클래스 안에서 SQL문이 빠져서 Mapper 파일로 간다

 

- Mapper 파일인 member.xml 파일을 보자

-member.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="memberns">

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>
	
	<select id="list" resultType="member">
		select * from member22
	</select>
	
	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>	
	
	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>
	
	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>
	
</mapper>

- id 값은 태그를 구별하는 역할, 이 파일 내에선 중복되선 안되는 값이다

- update 문은 update 태그 안에 작성

- DAO 클래스에서 이 id 값을 불러온다, 즉, 안의 SQL문을 불러온다

- 전달되는 값의 자료형을 parameterType 에 쓰고 돌려주는 값의 자료형을 resultType 에 쓴다

- resultType 은 select 태그 (select 문) 에만 사용가능하다

- 전달되는 값이 없을땐 parameterType 속성을 사용하지 않음

- DTO 객체가 전달되는 값의 자료형이거나 돌려주는 값의 자료형일땐 alias (별칭) 값을 쓴다

- 2개 이상의 데이터를 검색할때도 똑같이 DTO 객체인 alias 값을 사용하면 자동으로 리스트에 저장해서 돌려줌

 

- 다음은 MyBatis 환경 설정 파일인 mybatis-config.xml 을 보자

- mybatis-config.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="db.properties" />
	<typeAliases>
		<typeAlias type="model.Member" alias="member"></typeAlias>
	</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="member.xml" />
	</mappers>
</configuration>

1. 이 alias 를 Mapper 파일안에서 값을 받고 돌려줄때 사용한다

+ alias 를 설정하지 않았다면 패키지부터 클래스까지 전부 써야한다

2. DB 접속에 필요한 내용을 property 태그 안에 작성, db.properties 파일의 변수를 불러와서 설정

+ 변수 불러오지 않아도 바로 적어도 된다

3. Mapper 파일을 불러오고 있다

 

DAO 클래스에서 id 값을 불러오는 원리

- DAO 에서 바로 Mapper 파일의 id를 불러올 수 없다

- 먼저 MyBatis 환경설정 파일 3번째 자리에서 Mapper 파일을 불러온다

- DAO에서는 MyBatis 환경 설정 파일을 불러옴으로서 id 를 불러올 수 있음

- 즉 Mapper 파일을 MyBatis 환경설정 파일에서 불러오고, MyBatis 환경설정 파일을 DAO 에서 불러온다

- 연쇄적으로 불러오기 때문에 Mapper 파일에 설정된 id 를 DAO 클래스에서 불러 올 수 있음

 

- DAO 클래스인 MemberDao.java 를 보자

- MemberDao.java

package dao;

import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import model.Member;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MemberDao {
	
	private SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);			// auto commit
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		return session;
	}

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1;
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

	public List<Member> list() {
		List<Member> list = new ArrayList<Member>();
		SqlSession session=null;
		try { session = getSession(); 
			list = session.selectList("list");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return list;
	}

	public int delete(String id) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.delete("delete", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public int update(Member mem) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.update("update", mem);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}
}

- 마찬가지로 DB 연동을 처리하는 클래스이다

- SQL문이 빠지고 Mapper 파일인 member.xml 파일에 들어가있다

- SQL문을 제외한 나머지 내용이 들어가있음, 메소드만으로 구성되어있다

+ MyBatis 지원 클래스/인터페이스 (SqlSession 인터페이스 등) 들을 import 하고 있다 * 아래에 설명

 

SqlSession 객체 구하기 / MyBatis 환경설정 파일 읽어오기 (MemberDao.java 부분 getSession() 메소드)

	private SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);			// auto commit
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		return session;
	}

- getSession() 메소드에서는 try 안의 3줄을 통해 SqlSession 객체를 구해준다, 그 객체를 통해 SQL문을 실행하는 메소드들을 사용 가능하다

- Reader 객체를 구할때 MyBatis 환경설정 파일인 mybatis-config,xml 을 불러오고 있다, MyBatis 환경설정 파일에서는 Mapper 파일을 불러옴

- 그렇기 때문에 DAO 에서 Mapper 파일의 id 값을 불러올 수 있다, 즉 Mapper 파일의 SQL문을 불러올 수 있음

- sf.openSession(true) 를 하면 자동 커밋이 된다, 자동 커밋이 되지 않는다면 SQL문을 수행 후 일일히 session.commit() 으로 커밋을 수행해야함

* 커밋을 해야 DB에 실제 삽입, 수정, 삭제 등이 됨

 

DAO 안에서 id 로 불러와서 SQL문 실행 (MemberDao.java 부분)

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- 가장 먼저 getSession() 메소드로 SqlSession 객체를 구한다

- DAO 안의 메소드 안에서 그 SQL문을 실행해야하는데, 이때 SqlSession 에서 지원하는 메소드를 사용한다

- insert() 메소드를 사용해서 id 값인 "insert" 로 Mapper 클래스의 insert 문을 불러온다

메소드에서 메소드 첫번째 매개변수에는 id값, 전달될 값이 있다면 두번째 매개변수에 전달될 값을 전달

- 여기서는 전달받은 DTO 객체인 member 를 두번째 매개변수를 통해 전달하고 있음

- insert() 메소드를 실행 후 삽입 성공한 데이터 개수를 자동으로 반환해줌

- 위의 insert() 메소드는 SQL문을 실행시켜주는 메소드 5가지 중 하나이다

 

SQL문을 실행시켜주는 메소드 5가지 (MemberDao.java 부분)

- SQL문에 따라서 메소드가 정해져 있다

- 이 메소드를 지원하는 인터페이스 SqlSession 객체를 구해와야만 이 메소드 5개를 사용 가능하다

- 그래서 위에서 getSession() 메소드로 SqlSession 객체를 구해왔다

+ SqlSession 은 MyBatis 에서 지원되는 인터페이스

 

- insert SQL문은 insert() 메소드로 실행해야한다, update, delete 도 마찬가지

- 검색되는 결과가 1개인 select 문은 selectOne() 메소드로 실행

- 결과를 Object 형으로 돌려준다, 다운캐스팅

+ Member 는 DTO 클래스 이름이다

- 검색되는 결과가 2개 이상인 selec 문은 selectList() 메소드로 실행

- 결과를 List 로 돌려준다

 

- insert() / update() / delete() 메소드는 삽입, 수정, 삭제된 데이터 개수를 자동으로 돌려준다

- 메소드 자체적으로 값을 자동으로 돌려주므로 Mapper 파일에서 insert / update / delete 문은 resultType 을 쓰면 안됨

- resultType 은 select 문만 사용가능

<데이터를 1개 검색시>

- selectOne() 메소드를 사용시에는 값을 Object 형 으로 돌려주므로 다운캐스팅 해야한다

- resultType 에는 DTO 객체를 쓴다

<데이터를 2개 이상 검색시>

- 데이터 2개 이상 검색하는 select문에서도 resultType 에는 DTO 객체만 쓴다, List 를 쓰지 않음! 자동으로 리스트를 잡아서 돌려준다

- 일일히 가져와서 DTO 객체 에 setter 메소드로 저장했었지만 이제는 검색된 데이터의 컬럼명과 DTO 프로퍼티명이 같다면 자동으로 순차적으로 DTO 객체에 저장하면서 List 를 만들어서 돌려줌

 

Mapper 의 insert SQL문을 insert() 메소드로 불러와서 실행 (MemberDao.java 부분, 중복) 

- update(), delete() 도 마찬가지

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- SqlSession 객체가 있어야 insert() 메소드 사용 가능, 이전에 정의한 getSession() 으로 객체를 받아온다

- insert() 메소드 자체적으로 삽입에 성공한 데이터 개수를 돌려주는 기능이 있으므로 Mapper 파일의 insert에 resultType 을 써선 안됨

- 그렇게 돌려받은 값을 result 변수에 받아서 그 변수를 리턴하고 있음

- insert() 메소드에서 메소드 첫번째 매개변수에는 id값, 전달될 값이 있다면 두번째 매개변수에 전달될 값을 전달

- 여기서는 전달받은 DTO 객체인 member 를 두번째 매개변수를 통해 전달하고 있음

- Mapper 파일의 parameterType 에서도 같은 자료형인 DTO 를 써야함

- Mapper 파일 member.xml 부분

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>

- parameterType 에는 전달될 값의 자료형을 적음, DTO 객체를 전달받아야하므로 alias 로 설정한 별칭인 member 를 적는다

- #{} 안에 DTO 클래스의 필드명을 써서, 값을 가져온다.

- 표기만 이럴뿐 DTO 객체에 직접 접근하는 것이 아니라 getter 메소드로 가져오는 것임

- #{id} 의 의미는 member.getId() 로 리턴받은 값을 의미함

- #{password} 의 의미는 member.getPassword() 로 리턴받은 값을 의미

 

+ 단점 : 값을 1개만 전달 가능

 

Mapper 의 select SQL문을 select() 메소드로 불러와서 실행 1(MemberDao.java 부분)

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

- SqlSession 객체가 있어야 select() 메소드 사용 가능, 이전에 정의한 getSession() 으로 객체를 받아온다

- select() 메소드는 돌려주는 데이터가 몇개인지에 따라 다른 메소드를 쓴다, 1개 데이터를 돌려줄때는 selectOne() 사용

- 두번째 매개변수로 사용자의 id 값을 전달

- 마찬가지로 Mapper 파일의 parameterType 도 String 형으로 설정해야함

- Mapper 파일 member.xml 부분

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

- 넘어오는 값은 사용자의 id 값이므로 paramterType 은 String 으로 설정해야함

- #{id} 의 의미는 전달된 변수 id 값을 가져오는 것임

- 데이터 1개를 돌려주므로 resultType 을 DTO 의 alias 인 member 로 설정

- if(rs.next()) 안에서 수행했던 member.setId(rs.getString("id")) 를 더이상 쓰지 않음, 돌려주는 객체 member 에 자동으로 setter 메소드로 값을 다 저장해서 돌려준다

+ DTO 프로퍼티명과 컬럼명이 일치하는 경우에만 가능

 

Mapper 의 select SQL문을 select() 메소드로 불러와서 실행 2(MemberDao.java 부분)

- 위의 select() 와는 다른 메소드, 이건 로그인 시 회원 인증을 해주는 chk() 메소드이다

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1; // 회원인증 성공
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- 로그인 시 회원인증을 처리하는 부분이다

- mem.getId() 는 DB에 저장된 id 를 의미하고, member.getId()는 사용자가 입력한 id 를 의미

- mem.getPassword() 는 DB에 저장된 password 를 의미하고, member.getPassword()는 사용자가 입력한 password 를 의미

- id, password 가 모두 일치할때는 변수 result 에 1 을 저장해서 리턴함, 이 값으로 로그인 성공을 판별하는 것은 loginPro.jsp 에서 처리함

- id 는 일치하되, passoword 가 일치하지 않을때는 변수 result 에 -1 을 저장해서 리턴, 이것도 loginPro.jsp 에서 처리

 

- Mapper 파일 member.xml 부분

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

회원가입 기능

Model 1 & MyBatis 회원 가입 흐름

1. 처음 index.jsp 실행 -> loginForm.jsp -> '회원가입' 눌러서 joinForm.jsp 로 넘어감

2. joinForm.jsp 에서 입력된 값을 id, password 네임값에 저장해서 joinPro.jsp 로 전달

3. joinPro.jsp 에서 useBean 과 setProperty 로 DTO 객체 mem 생성 후 값 저장, DAO 객체의 insert(mem) 호출함

4. DAO 에서 insert() 메소드를 실행, 그 안에서 Mapper 파일의 insert SQL문을 id 로 불러온다

- insert() 가 잘 수행되면 1을 돌려줌

5. joinPro.jsp 로 돌아오고, 회원가입 성공 시 loginForm.jsp 로 넘어간다, 실패(중복아이디)시 이전페이지로 이동

 

- joinForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>회원가입</h2>
<form action="joinPro.jsp">
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
</body>
</html>

 

- joinPro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="dao.*"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
MemberDao md = new MemberDao();
int result = md.insert(mem);

if (result>0) {
%> 
	<script type="text/javascript">
		alert("가입성공");
		location.href="loginForm.jsp";
	</script>
<%
} else {
%> 
	<script type="text/javascript">
		alert("가입 실패");
		history.go(-1);
	</script>
<%
}
%>

 

- MemberDao.java 에서 insert() 메소드 부분만

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 


- member.xml 에서 회원 가입을 처리하는 SQL문 부분 (id 가 insert)

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>

로그인 기능

Model 1 & MyBatis 로그인 흐름

1. 처음 index.jsp 실행 -> loginForm.jsp 로 넘어감

2. loginForm.jsp 에서 입력된 값을 id, password 네임값에 저장해서 loginPro.jsp 로 전달

3. loginPro.jsp 에서 useBean 과 setProperty 로 DTO 객체 mem 생성 후 값 저장, DAO 객체의 chk(mem) 호출함

4. DAO 에서 chk() 메소드를 실행, 그 안에서 Mapper 파일의 select SQL문을 id 로 불러온다, selectOne() 가 잘 수행되면 DB에 저장된 사용자의 정보를 가져옴

- 다운캐스팅 해서 DTO 객체에 저장 (다운캐스팅 생략해도 작동한다)

- 그 정보가 사용자가 입력한 값과 일치시 result 에 1을 저장해서 돌려준다

5. loginPro.jsp 로 돌아오고 회원 인증 성공 시 id 를 세션 공유 설정하고 main.jsp 로 넘어간다, 실패(중복아이디)시 이전페이지로 이동

 

- loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>로그인</h2>
<form action="loginPro.jsp" >
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
<p><a href="joinForm.jsp">회원가입</a>
</body>

</html>

 

- loginPro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
MemberDao md = new MemberDao();
int result = md.chk(mem);

if (result==1) {
	session.setAttribute("id",mem.getId());
%> 
	<script type="text/javascript">
		alert("환영함");
		location.href="main.jsp";
	</script>
<%
} else if (result == -1) {
%> 
	<script type="text/javascript">
		alert("비번이 다르다");
		history.go(-1);
	</script>
<%
} else {
%> 
	<script type="text/javascript">
		alert("그런 ID가 없습니다.");
		history.go(-1);
	</script>
<%
}
%>

 

- MemberDao.java 에서 chk() 메소드 부분만

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1; // 회원인증 성공
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 

- member.xml 에서 로그인(회원 인증)을 처리하는 SQL문 부분 (id 가 select)

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

 


DAO 클래스 상단에서 MyBatis 지원 클래스 / 인터페이스 import



중간 페이지 기능

- 로그인 성공시 이동하는 main.jsp 내용을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<%
 	String id = (String) session.getAttribute("id");
if (id == null || id.equals("")) {
	response.sendRedirect("loginForm.jsp");
} else if (id.equals("master")) {
	out.println("관리자 모드!");	
} 
%>

<body>
	로긴함
	<p>
		정보수정<br>
<%
		if (id != null) {
		if (id.equals("master")) {
%>
		<a href="list.jsp">회원명단</a><br>
<%
		}}
%>
		<a href="logout.jsp">로그아웃</a>
		
</body>
</html>

-로그인 성공시 공유한 공유값 id 값을 가져와서 사용

- getAttribute() 로 공유값을 가져올때는 다운캐스팅을 해야함

- 받아온 공유값인 id 가 null 인 경우, Redirect 포워딩 팡식으로 loginForm.jsp 로 넘어감

- 받아온 공유값인 id 가 "master" 인 경우 "관리자 모드!" 라는 메세지가 출력됨

<body 태그 안>

- 받아온 공유값인 id가 "master" 인 경우 "회원명단" 출력, 클릭시 list.jsp 로 이동하도록 설정

- 어느 아이디로 로그인되었느냐에 따라 main.jsp 내용이 달라짐

- '로그아웃' 클릭시 logout.jsp 로 이동 : 세션 삭제, 로그아웃 메세지 출력하고 로그인 폼으로 이동하는 작업을 한다


로그아웃 기능

- '로그아웃' 을 클릭시 넘어가는 logout.jsp 를 보자

- logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
session.invalidate();
%>

<script type="text/javascript">
	alert("로그아웃 됨");
	location.href="loginForm.jsp";
</script>

</body>
</html>

1. 세션 삭제(끊어줌)

2. 로그아웃 메세지 출력하고 loginForm.jsp 로 이동

- 이 두가지 작업만 한다

- DB 연동 안함


목록 페이지 기능

- '회원명단' 을 클릭시 넘어가는 list.jsp 를 보자

- list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="model.Member"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

회원 명단
<table border=1><tr><th>아이디</th><th>비밀번호</th><th>수정</th><th>삭제</th></tr>

<%
	MemberDao md = new MemberDao();
	List<Member> list = md.list();
	
for (int i = 0;i<list.size();i++){
%>
	<tr><td><%=list.get(i).getId() %></td><td><%=list.get(i).getPassword() %></td>
	<td><input type="button" value="수정" onclick='location.href="updateForm.jsp?id=<%=list.get(i).getId() %>"'></td>
	<td><input type="button" value="삭제" onclick='location.href="delete.jsp?id=<%=list.get(i).getId() %>"'></td></tr>
<%
}
%>
</table>

<a href="main.jsp">메인으로</a>

</body>
</html>

- id 가 master 인 관리자 계정만 볼 수 있다

- DB 연동을 수행해서 전체를 검색한다, DAO 객체 생성해서 전체 회원 목록을 구해오는 list() 메소드 호출

- 결과를 list 로 돌려받은 다음 반복문에서 get() 과 getter 메소드로 값을 가져와서 테이블에 뿌리고 있다

- '수정' 또는 '삭제' 를 클릭시 해당 회원의 아이디값을 넘기면서 updateForm.jsp 또는 delete.jsp 로 이동한다

- 관리자 모드이므로 '삭제' 누르면 바로 삭제되도록 했다

- 이 list() 메소드가 정의되어 있는 DAO 클래스의 부분을 보자

 

- MemberDao.java 에서 list() 메소드 부분만

	public List<Member> list() {
		List<Member> list = new ArrayList<Member>();
		SqlSession session=null;
		try { session = getSession(); 
			list = session.selectList("list");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return list;
	}

- 제네릭을 쓰고 있으므로 리스트에서 get() 으로 가져올때 자료형 생략 가능

- SqlSession 객체를 구해와야만 selectList() 를 사용 가능하므로 getSession() 호출해서 SqlSession 객체 구해오기

- 메소드 getSession() 안에서 DB와 연동하는 것임

- List 객체 list 를 업캐스팅으로 생성

- selectList() 메소드는 검색되는 결과가 2개 이상인 경우 실행하는 메소드

- selectList() 메소드 첫번째 매개변수로 Mapper 파일의 id "list" 를 써서 해당 select SQL문을 호출

- selectList() 메소드에서 전달할 값은 없으므로 두번째 매개변수는 쓰지 않았다

- selectList() 메소드로 돌려받은 리스트를 객체 list 에 저장해서 list.jsp로 리턴

 

MyBatis 연동 전 / 후

- while(rs.next()) 에서 DTO 객체 생성하고 setter 메소드로 저장시킨 후 리스트에 객체를 저장하는 행동을 반복했다

- MyBatis 와 연동하면 이런 작업이 Mapping 을 통해서 자동으로 실행되고, 리스트를 반환시켜줌

 

selectOne() vs selectList()

- 돌려줄 데이터가 1개이면 selectOne() 사용, 2개 이상이면 selectList() 사용

- selectOne() 은 리턴자료형이 Object, selectList() 는 리턴자료형이 List

 

데이터를 2개 이상 검색해서 돌려줄때 주의사항

- Mapper 파일의 select 문에서 데이터를 2개이상 돌려주더라도 resultType 에는 리스트가 아니라 DTO 가 오면 된다

ex) 돌려주는 자료형이 List<int> 이더라도 resultType 에 List 가 아닌 int 를 씀

ex) 돌려주는 자료형이 List<DTO> 이면 resultType 에 List 가 아닌 DTO(alias 별칭 member) 를 씀

 

- Mapper 파일인 member.xml 에서 해당 SQL 문 부분만 살펴보자

- member.xml 에서 전체 목록을 가져오는 SQL문 부분

	<select id="list" resultType="member">
		select * from member22
	</select>

+ select는 반드시 resultType 을 써야함, 받을 값은 없으므로 parameterType 은 쓰지 않았다

- 전체 데이터를 검색하고 있다


정보수정 기능 : 수정폼

- list.jsp 에서 '수정' 을 눌렀을때 수정하는 기능

<td><input type="button" value="수정" onclick='location.href="updateForm.jsp?id=<%=list.get(i).getId() %>"'></td>

- '수정' 클릭시 onClick 에 의해 updateForm.jsp 로 넘어간다, 넘어갈 때 해당 회원의 id 값을 get 방식으로 전달

- 세션에 공유된 id값은 관리자 id값인 "master" 이고 여기서 넘어가는 id값은 각 회원의 id 값임

 

- 먼저 수정폼을 해야함

- updateForm.jsp 파일을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="model.Member"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
	String id = request.getParameter("id");
	MemberDao md = new MemberDao();
	Member mem = md.select(id);
%>

<h2>정보 수정</h2>
<form action="updatePro.jsp">
	<input type="hidden" name="id" value="<%=mem.getId() %>">
	<table><tr><td>아이디</td>
			   <td><input type="text" name="id" value="<%=mem.getId() %>" disabled="disabled"></td></tr>
			<tr><td>암호</td>
				<td><input type="text" name="password" value="<%=mem.getPassword() %>"></td></tr>
			<tr><td colspan="2" align="right">
					<input type="submit" value="변경"> 
					<input type="button" onclick="history.go(-1)" value="취소"></td></tr>
	</table>
</form>

</body>
</html>

- 수정폼이므로 DB와 연동하여 1명에 대한 상세정보를 수정폼에 뿌려줘야한다

- list.jsp 에서 넘어온 회원의 id값을 받고 변수 id 에 저장

- DAO 객체를 생성해서 DAO 의 select() 메소드 호출

- select() 메소드를 호출하며 id 값을 넘긴다, 1명에 대한 상세정보를 구해오므로 리턴자료형 DTO

- 검색결과를 받은 mem 객체를 통해 mem.getId() 와 mem.getPassword() 로 id, password 를 수정폼에 뿌려줌

- id 는 disabled 로 설정되어있으므로 값이 action 의 페이지로 넘어가지 않는다, 그래서 hidden 객체로 따로 id 를 넘김

+ readonly 설정은 값이 넘어감, disabled 는 값이 안넘어감 * HTML readonly vs disabled 속성 설명 아래에

- 이후 수정폼에 상세정보를 뿌린 후, 사용자가 수정폼을 입력하고 '변경' 버튼을 누르면 id, password 를 가지고 updatePro.jsp로 이동한다

 

- MemberDao.java 에서 select() 메소드 부분만

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

- getSession() 으로 SqlSession 객체 구해오기

- 1개 데이터를 검색하므로 selectOne() 메소드 호출

- 첫번째 매개변수에 들어가는 Mapper 파일의 id는 "select", 두번째 매개변수에 들어가는 전달될 값은 매개변수로 받은 id

- selectOne() 을 호출하고 결과를 Object 형으로 돌려주므로 다운캐스팅해서 DTO 객체 member 에 저장하고 그걸 리턴

+ 다운캐스팅 명시 생략 가능

- 이 select() 메소드 실행 후 이 메소드를 호출한 updateForm.jsp 로 돌아감  

 

- member.xml 에서 수정폼을 처리하는 SQL문 부분 (id 가 select)

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

- 각 회원의 id 를 받으므로 paramterType 을 String 으로 설정

- 1명에 대한 상세정보를 리턴하므로 resultType 은 DTO 클래스 별칭인 member

- 전달되는 변수명이 id 이므로, #{id} 로 전달되는 값을 가져옴, parameterType 은 "String"

 

- URL 을 보면, id 값이 get 방식으로 넘어갔음을 볼 수 있다

 

+ 매개변수로 전달된 id 를 전달

- selectOne() 에서 변수 id 에 값이 전달되고 있다

- 그래서 member.xml 에서 #{id} 로 SQL문 where절 작성


정보수정 기능 : 실제 수정

- 이후 수정폼에 상세정보를 뿌린 후, 사용자가 수정폼을 입력하고 '변경' 버튼을 누르면 id, password 를 가지고 updatePro.jsp 로 이동한다

 

- updatePro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>
<%@page import="model.Member"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
	MemberDao md = new MemberDao();
	int result = md.update(mem);

if (result == 1) {
%>
	<script type="text/javascript">
		alert("수정 성공");
		location.href="list.jsp";
	</script>
<%
} else {
%>
	<script type="text/javascript">
		alert("수정 실패");
		history.go(-1);
	</script>
<%
}
%>

- useBean, setProperty action tag 사용해서 수정폼에서 hidden 으로 넘어온 id 값과, 비밀번호 입력양식에서 넘어온 password 값을 받아서 DTO 객체 mem 에 저장

+ 입력양식의 name 값과 DTO 의 프로퍼티명이 일치할때만 가능

- DAO 객체 생성 후 DAO의 update() 메소드를 호출하며 매개변수로 객체 mem 을 전달

- 성공시 메세지 출력 후 list.jsp 로 돌아감, 즉 다시 회원목록을 구해서 뿌려준다

 

- MemberDao.java 에서 update() 메소드 부분만

	public int update(Member mem) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.update("update", mem);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- update() 메소드의 첫번째 매개변수에 Mapper 파일의 id 명인 "update" 를, 두번째 매개변수에 넘겨줄 값인 객체 mem 을 적기

- update() 메소드가 실행되고 성공적으로 수정하면 1 을 리턴해줌, 그 1 을 result 변수에 저장해서 updatePro.jsp 로 리턴함

 

- member.xml 에서 정보 수정을 처리하는 SQL문 부분 (id 가 update)

	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>

- DTO 객체가 넘어올때 #{id} 는 member.getId() 를 의미, #{password} 는 member.getPassword() 를 의미

- update SQL문이 수행되고 자동으로 수정한 데이터 개수를 돌려줌, 즉 1 을 리턴해줌


* HTML readonly vs disabled 속성

 

공통점

- 입력 양식의 값을 수정할 수 없도록 비활성화 시켜주는 역할

차이점

- readonly 속성으로 설정된 양식은 action 으로 설정된 페이지로 값이 전달되지만,
- disabled 속성으로 설정된 양식은 action 으로 설정된 페이지로 값이 전달되지 않는다.


정보수정 기능 : 삭제

- list.jsp 에서 '삭제' 를 눌렀을때 수정하는 기능

<td><input type="button" value="삭제" onclick='location.href="delete.jsp?id=<%=list.get(i).getId() %>"'></td></tr>

- '삭제' 클릭시 onClick 에 의해 delete.jsp 로 넘어간다, 넘어갈 때 해당 회원의 id 값을 get 방식으로 전달

- 즉 삭제폼을 거치지 않고 바로 삭제되게 해뒀다

 

- delete.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
String id = request.getParameter("id");
MemberDao md = new MemberDao();
int result = md.delete(id);
if (result == 1) {
	%>
	<script type="text/javascript">
	alert("삭제성공");
	location.href="list.jsp";
	</script>
	<%
} else {
	%>
	<script type="text/javascript">
	alert("실패");
	location.href="list.jsp";
	</script>
	<%
}
%>
</body>
</html>

- list.jsp 로 부터 전달받은 id 값을 받아서 저장

- DAO 객체를 생성하고 DAO의 delete() 메소드 실행, 특정 회원 삭제를 해야하므로 매개변수로 id 값을 전달한다

- 메소드 호출하여 삭제 성공시 목록 페이지인 list.jsp 로 돌아간다

 

- MemberDao.java 에서 delete() 메소드 부분만

	public int delete(String id) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.delete("delete", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 

- member.xml 에서 회원 삭제(탈퇴) 를 처리하는 SQL문 부분 (id 가 delete)

	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>

- #{id} 는 DAO에서 session.delete() 메소드 실행시 두번째 매개변수로 넘어온 변수 id 를 의미함

- delete SQL문이 수행되고 자동으로 삭제한 데이터 개수를 돌려줌, 즉 1 을 리턴해줌

- 자동으로 삭제한 데이터 개수를 돌려주므로 select 외의 insert/update/delete 는 returnType 을 쓰면 안된다

 

#{id} 의 의미

1. 일반 변수 id 가 넘어왔을때 #{id} 는 그 변수 id 값을 의미함

2. DTO 객체 member 가 넘어왔을때 #{id} 는 member.getId() 를 의미, 즉 객체 안의 프로퍼티 값을 의미

- 같은 코드지만 다른 의미이다

 

- master 로 로그인 한 뒤 회원 목록 페이지인 list.jsp 에서 회원을 삭제해보자

 


MyBatis

- DB 연동을 처리하기 위한 프레임 워크

 

프로그램 확장 시

- MyBatis 환경설정 파일은 하나만 있으면 된다

- Mapper 파일은 테이블의 개수에 비례해서 늘어난다

 

태그 맞추기

- Mapper 파일인 member.xml의 루트 엘리먼트는 mapper 이다

- MyBatis 환경 설정 파일에서 태그를 mapper 로 해야한다

- Mapper 파일의 루트 엘리먼트와 MaBatis 환경설정 파일에서 Mapper 파일을 불러올때 태그 이름을 맞춰야한다

 

Mapper 파일의 id 충돌문제

- 같은 Mapper 파일 뿐 아니라 다른 Mapper 파일 끼리도 id 값을 다르게 설정해야한다

- member 테이블, board 테이블 이 있을때 member.xml , board.xml 처럼 Mapper 파일이 각각 생성됨

- 이때 서로 다른 두 Mapper 파일에서도 같은 id값을 쓰면 안된다!, 그래야 DAO에서 불러올때 충돌되지 않음

- id 값이 중복되는 문제 해결하기 위해 namespace

 

namespace

- 논리적인 영역 이름

- namespace를 통해 id값 중복되어 DAO 에서 불러올때 충돌되는 문제 해결가능

+ 일반적으로 namespace를 테이블명+"ns" 로 설정함

- Mapper 파일에서 namespace 값만 서로 다르게 설정하면 다른 Mapper 파일끼리 같은 id값을 사용할 수 있다

- Mapper 파일인 member.xml 부분

- 그래서 insert id 를 불러오는 DAO 코드에서 namespace 를 함께 써서 충돌 피할 수 있다

- 이렇게 하면 다른 테이블의 Mapper 파일에서 id 값을 insert 로 쓴다고 해도 충돌하지 않음

+ Recent posts