- 사원명을 클릭하면 "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" 인 태그 부분만
<selectid="select"parameterType="int"resultType="emp">
select * from emp where empno=#{empno}
</select>
- 전달받은 사원 번호로 해당 사원의 상세정보(모든 정보) 를 검색함
- 전달받은 값은 사원번호이므로 전달받은 자료형 parameterType 은 "int"
- 돌려주는 자료형 resultType 은 Emp DTO alias 인 "emp"
- 다시 DAO -> Service -> Controller 로 갔다가 View 로 오게 된다
- 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() 메소드 부분만
<updateid="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 절에 넣는다
- 삭제 성공시 "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 생성
- 한글값 인코딩 처리 코드는 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" 로 수정
- 이 폼에서 넘어가는 내용은 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() 메소드를 생성해야한다
- Mapper 파일인 Board.xml 에 id 가 "insert" 인 insert (글 작성) 문을 생성해야함
<!-- 글 작성 --><insertid="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} 로 세팅
- 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() 메소드를 생성해야함
- 그룹함수는 결과가 1개이므로 selectOne() 메소드 사용, id 가 "count" 인 SQL문 불러오기
- Mapper 파일 Board.xml 에서 id 가 "count" 인 SQL문 생성
<!-- 총 데이터 갯수 --><selectid="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문 작성
<!-- 글 목록 --><!-- > : > , < : < --><selectid="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 로 계산 가능해서 사용하기
+ <. > 인식을 못하므로 > , $lt; 사용, 또는 <![CDATA[ ]]> 로 전체 SQL문으로 감싸주면 부등호 기호 <, > 를 인식함
- View 페이지를 보기 전에 페이징 처리를 확인하기 위해 myboard.sql 에서 데이터를 강제로 많이 삽입하자
- View 페이지 board/boardlist.jsp 를 생성해서 Contorller 에서 Model 객체에 저장된 값들을 활용해서 목록을 출력하자
- boardlist.jsp
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core" %><%@taglibprefix="fmt"uri="http://java.sun.com/jsp/jstl/fmt" %><!DOCTYPE html><html><head><metacharset="UTF-8"><title>게시판 목록</title></head><body><ahref="boardform.do">글작성</a><br>
글갯수 : ${listcount }
<tableborder=1align=centerwidth=700><caption>게시판 목록</caption><tr><th>번호</th><th>제목</th><th>작성자</th><th>날짜</th><th>조회수</th></tr><!-- 화면 출력 번호 --><c:setvar="num"value="${listcount - (page - 1) * 10}"/><c:forEachvar="b"items="${boardlist}"><tr><td>${num}
<c:setvar="num"value="${num-1}"/></td><td><ahref="boardcontent.do?no=${b.no}&page=${page}">${b.subject}</a></td><td>${b.writer}</td><td><fmt:formatDatevalue="${b.register}"pattern="yyyy-MM-dd HH:mm:ss"/></td><td>${b.readcount}</td></tr></c:forEach></table><br><!-- 페이지 처리 --><center><c:iftest="${listcount > 0}"><!-- 1페이지로 이동 --><ahref="boardlist.do?page=1"style="text-decoration:none"><< </a><!-- 이전 블럭으로 이동 --><c:iftest="${startpage > 10}"><ahref="boardlist.do?page=${startPage - 10}">[이전]</a></c:if><!-- 각 블럭에 10개 페이지 출력 --><c:forEachvar="i"begin="${startPage}"end="${endPage}"><c:iftest="${i == page}"><!-- 현재 페이지 -->
[${i}]
</c:if><c:iftest="${i != page}"><!-- 현재 페이지가 아닌 경우--><ahref="boardlist.do?page=${i}">[${i}]</a></c:if></c:forEach><!-- 다음 블럭으로 이동 --><c:iftest="${endPage < pageCount}"><ahref="boardlist.do?page=${startPage + 10}">[다음]</a></c:if><!-- 마지막 페이지로 이동 --><ahref="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:setvar="num"value="${num-1}"
</td>
- 등록일은 국제화 라이브러리 formatDate 태그로 패턴지정해서 적용
- 제목을 클릭하면 상세페이지로 가기 위해 "boardcontent.do" 로 요청하고 글번호 no, 페이지 번호 page 전달
- Spring 에서 MyBatis 를 쓰려면 MyBatis 라이브러리 뿐 아니라 MyBatis-Spring 연결 라이브러리도 필요하다
파일들 살펴보기 : web.xml
- web.xml
<?xml version="1.0" encoding="UTF-8"?><web-appversion="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 이름, 위치, 매핑 등록
- DispatcherServlet 클래스에 대한 위치가 등록되어있다
- 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:beansxmlns="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 --><resourcesmapping="/resources/**"location="/resources/" /><!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --><beans:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><beans:propertyname="prefix"value="/WEB-INF/views/" /><beans:propertyname="suffix"value=".jsp" /></beans:bean><context:component-scanbase-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) 도 있다
- @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 부분
@Autowiredprivate DeptDao dd;
public List<Dept> list(){
return dd.list();
}
- @Autowired 를 통해서 DeptDaoImpl 객체를 인터페이스인 DeptDao dd 에 주입
+ 엄밀히 말하면 구현클래스가 주입되는 것이지만, 어차피 업캐스팅 하므로 그냥 DeptDao 객체가 됨
+ 그래서 하나의 인터페이스에 구현클래스를 2개를 만들면 안된다
- 돌려받은 값(리스트)을 그대로 리턴
DAO 클래스 DeptDaoImpl.java 에서 list() 메소드 부분만+ @Autowired 부분
@Autowiredprivate 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. --><resultMapid="deptResult"type="dept"><resultproperty="deptno"column="deptno" /><resultproperty="dname"column="dname" /><resultproperty="loc"column="loc" /></resultMap><selectid="list"resultMap="deptResult">
select * from dept order by deptno
</select>
- Dept 테이블의 모든 데이터를 검색
- order by 로 부서번호 기준 오름차순 정렬되어서 돌려준다
- 현재는 Dept DTO 클래스 프로퍼티명과 검색되는 컬럼명인 Dept 테이블의 컬럼명이 일치하므로 resultMap 을 사용하지 않아도 된다, 사용은 하고 있음
+ 엄밀히 말하면 구현클래스가 주입되는 것이지만, 어차피 업캐스팅 하므로 그냥 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 부분
@Autowiredprivate EmpDao ed;
public List<Emp> empAllList(){
return ed.empAllList();
}
- 위에서 @Autowired 로 주입받은 EmpDao 객체 ed 를 사용해서 DAO 클래스의 메소드인 empAllList() 호출
DAO 클래스 EmpDaoImpl.java 에서 empAllList() 메소드 부분만+ @Autowired 부분
@Autowiredprivate 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. --><resultMapid="empResult"type="emp"><resultproperty="empno"column="empno" /><resultproperty="ename"column="ename" /><resultproperty="job"column="job" /><resultproperty="mgr"column="mgr" /><resultproperty="hiredate"column="hiredate" /><resultproperty="sal"column="sal" /><resultproperty="comm"column="comm" /><resultproperty="deptno"column="deptno" /><resultproperty="dname"column="dname" /><resultproperty="loc"column="loc" /></resultMap>
+ 현재는 모든 10개의 테이블과 검색결과의 컬럼명이 일치하므로 일일히 resultMap 을 사용할 필요 없다, 여기선 resultMap 을 사용했음
- Emp DTO 가 저장된 리스트를 리턴하므로, type 을 "emp" 로 설정함
<selectid="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" 를 써도 된다
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" 인 태그 부분만
<selectid="select"parameterType="int"resultType="dept">
select * from dept where deptno=#{deptno}
</select>
- 검색되는 결과는 1개이다
- 결과는 Dept DTO 형으로 돌려준다, 그래서 resultType 에 "dept" 를 썼다
View 페이지 deptUpdateForm.jsp
- deptUpdateForm.jsp
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@includefile="header.jsp"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><divclass="container"align="center"><h2class="text-primary">부서정보 수정</h2><formaction="deptUpdate.do"method="post"><inputtype="hidden"name="deptno"value="${dept.deptno }"><tableclass="table table-hover"><tr><td>부서코드</td><td>${dept.deptno}</td></tr><tr><td>부서명</td><td><inputtype="text"name="dname"required="required"value="${dept.dname}"></td></tr><tr><td>근무지</td><td><inputtype="text"name="loc"required="required"value="${dept.loc}"></td></tr><tr><tdcolspan="2"><inputtype="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() 메소드 부분만
- 앞에서 넘어온 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:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><beans:propertyname="prefix"value="/WEB-INF/views/" /><beans:propertyname="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;
publicclassLoginCheckimplementsHandlerInterceptor{
// DispatcherServlet의 화면 처리가 완료된 상태에서 처리publicvoidafterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)throws Exception {
}
// 지정된 컨트롤러의 동작 이후에 처리, Spring MVC의 Front Controller인 DispatcherServlet의 화면 처리가 완료된 상태에서 처리publicvoidpostHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)throws Exception {
}
// 지정된 컨트롤러의 동작 이전에 가로채는 역할 (세션이 없으면, 로그인 폼으로 이동 하도록 해준다 )publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object arg2)throws Exception {
HttpSession session = request.getSession();
if (session.getAttribute("id") == null) { // 세션이 없으면
response.sendRedirect("loginForm.do"); // loginform 으로 이동returnfalse;
}
returntrue;
}
}
- 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
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@includefile="header.jsp"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><h2class="text-primary">로그인 성공</h2><ahref="upload.do"class="button button-success">업로드</a></body></html>
- '업로드' 메뉴를 선택하면 "upload.do" 로 요청한다
- "upload.do" 로 요청했으므로 인터셉터로 간다
+ 메소드 오버라이딩이므로, 메소드 형식을 유지해야한다, 그래서 Session 객체를 매개변수에 써서 자동으로 구하지 않고 직접 구하고 있다
- 세션값이 존재하므로 return true; 되어 원래 가던 Controller 클래스로 간다
- GET 방식 요청이므로 위의 uploadForm() 코드가 실행된다
- upload.jsp 로 간다
- upload.jsp
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@includefile="header.jsp"%><!DOCTYPE html><html><head><metacharset=UTF-8"><title>Insert title here</title></head><body><divclass="container"><h2class="text-primary">파일 업로드</h2><formaction="upload.do"method="post"enctype="multipart/form-data"><tableclass="table table-bordered"><tr><th>파일</th><td><inputtype="file"name="file"></td></tr><tr><thcolspan="2"><inputtype="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 클래스로 간다
- Spring 에서 MyBatis 를 쓰려면 MyBatis 라이브러리 뿐 아니라 MyBatis-Spring 연결 라이브러리도 필요하다
파일들 살펴보기 :web.xml
- Apache Tomcat 이 실행시 자동으로 실행되는 파일
- 코드는 생략
web.xml 에 들어가는 내용 3가지
1. Dispatcher Servlet 이름, 위치, 매핑 등록
- DispatcherServlet 클래스에 대한 위치가 등록되어있다
- 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:beansxmlns="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 --><resourcesmapping="/resources/**"location="/resources/" /><!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --><beans:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><beans:propertyname="prefix"value="/WEB-INF/views/" /><beans:propertyname="suffix"value=".jsp" /></beans:bean><context:component-scanbase-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"?><beansxmlns="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연동 처리
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="deptns"><!-- Use type aliases to avoid typing the full classname every time. --><resultMapid="deptResult"type="dept"><resultproperty="deptno"column="deptno" /><resultproperty="dname"column="dname" /><resultproperty="loc"column="loc" /></resultMap><selectid="list"resultMap="deptResult">
select * from dept order by deptno
</select><selectid="select"parameterType="int"resultType="dept">
select * from dept where deptno=#{deptno}
</select><updateid="update"parameterType="dept">
update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
</update><deleteid="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
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE htmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><scripttype="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;
@ControllerpublicclassDeptController{
@Autowiredprivate 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 에서 프로퍼티 부분만
@Autowiredprivate DeptService ds;
- 이 DeptService 는 Service 인터페이스이다
- 인터페이스로 주입해도 되고, 구현클래스로 주입해도 된다, 여기서는 인터페이스에 주입하고 있다
- 즉 어노테이션 기반 DI이며 이미 Service 객체 ds 에 주입이 완료되었음
+ 어노테이션 기반 DI 이므로 Service 클래스에 Setter 메소드가 없어도 여기서 주입 가능
- 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 mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="deptns"><!-- Use type aliases to avoid typing the full classname every time. --><resultMapid="deptResult"type="dept"><resultproperty="deptno"column="deptno" /><resultproperty="dname"column="dname" /><resultproperty="loc"column="loc" /></resultMap><selectid="list"resultMap="deptResult">
select * from dept order by deptno
</select><selectid="select"parameterType="int"resultType="dept">
select * from dept where deptno=#{deptno}
</select><updateid="update"parameterType="dept">
update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno}
</update><deleteid="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. --><resultMapid="deptResult"type="dept"><resultproperty="deptno"column="deptno" /><resultproperty="dname"column="dname" /><resultproperty="loc"column="loc" /></resultMap><selectid="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
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@includefile="header.jsp"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><divclass="container"><h2class="text-primary">부서 목록</h2><tableclass="table table-hover"><tr><th>부서코드</th><th>부서명</th><th>근무지</th></tr><c:iftest="${not empty list }"><c:forEachvar="dept"items="${list}"><tr><td>${dept.deptno }</td><td><ahref="deptView.do?deptno=${dept.deptno}"class="btn btn-info"> ${dept.dname}</a></td><td>${dept.loc }</td></tr></c:forEach></c:if><c:iftest="${empty list }"><tr><thcolspan="3">데이터가 없습니다</th></tr></c:if></table></div></body></html>
- Controller 클래스에서 Model 에 저장했던 리스트의 네임값이 "list"
- Model 에 저장된 것이 리스트 이므로 forEach 태그의 items 에 ${list} 를 써서 출력한다
- forEach 태그를 통해 dept 로 개별 요소를 받고 있그 ${dept.deptno}, ${dept.dname}, ${dept.loc} 으로 목록을 출력
- '수정' 버튼 클릭시 "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" 인 태그 부분만
<selectid="select"parameterType="int"resultType="dept">
select * from dept where deptno=#{deptno}
</select>
수정 폼 View 페이지인 deptUpdateForm.jsp
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@includefile="header.jsp"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><divclass="container"align="center"><h2class="text-primary">부서정보 수정</h2><formaction="deptUpdate.do"method="post"><inputtype="hidden"name="deptno"value="${dept.deptno}"><tableclass="table table-striped"><tr><th>부서코드</th><td>${dept.deptno}</td></tr><tr><th>부서명</th><td><inputtype="text"name="dname"required="required"value="${dept.dname }"></td></tr><tr><th>근무지</th><td><inputtype="text"name="loc"required="required"value="${dept.loc }"></td></tr><tr><thcolspan="2"><inputtype="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 로 간다
- "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 를 전달
- 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"?><beansxmlns="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
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title><scripttype="text/javascript">functionchk() {
var url = document.getElementById("url").value;
location.href = url;
}
</script></head><body><h2>원하는 작업을 선택하세요</h2><!-- <script type="text/javascript">
location.href="hello";
</script> --><selectid="url"onchange="chk()"><optionvalue="hello">hello</option><optionvalue="color">배경색</option><optionvalue="gugu">구구단</option><optionvalue="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") 으로 요청을 받는 어노테이션이 있다
- @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;
@ControllerpublicclassHomeController{
privatestaticfinal 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 (수정 전)
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><h2>이름과 주소</h2><formaction="addr"><!-- <form action="addr2"> -->
이름 : <inputtype="text"name="name"><p>
주소 : <inputtype="text"name="addr"><p><inputtype="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 로 간다\
- Model 객체에 String 형으로 저장되어있으므로 단순히 ${name}, ${addr} 을 통해 View 에서 출력 가능
- 개별적으로 전달되는 값을 받을때는 @RequestParam 을 사용했다
- 이번에는 전달되는 값을 한번에 받아서 객체에 자동으로 저장하는 @ModelAttribute 를 사용해보자
- person.jsp 에서 action 값이 "addr2" 인 경우 form 태그를 설명
- person.jsp (수정 후)
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><h2>이름과 주소</h2><!-- <form action="addr"> --><formaction="addr2">
이름 : <inputtype="text"name="name"><p>
주소 : <inputtype="text"name="addr"><p><inputtype="submit"value="확인"></form></body></html>
- 입력하고 '확인' 을 누르면 이름과 주소가 Dispatcher Servlet -> Controller 클래스로 간다
- PersonController.java 부분
- 즉 앞에 @ModelAttribute 가 생략되어 있는 것이다
- 폼에서 넘어온 값들을 한번에 DTO Person 객체인 p 에 저장(세팅)한다
- 이때, DTO Person 클래스의 프로퍼티명과 넘어온 값의 name 이 일치하므로, 넘어온 값이 자동 매핑되어 Person 객체에 저장되는게 가능하다
- 해당 UploadController.java 클래스명 위에 @Controller 어노테이션이 붙어있다
- 어노테이션 기반으로 쓰기 위한 두번째 조건이다
servlet-context.xml 부분 2
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --><beans:beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><beans:propertyname="prefix"value="/WEB-INF/views/" /><beans:propertyname="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 --><resourcesmapping="/resources/**"location="/resources/" /><resourcesmapping="/css/**"location="/WEB-INF/css/" /><resourcesmapping="/js/**"location="/WEB-INF/js/" /><resourcesmapping="/fonts/**"location="/WEB-INF/fonts/" />
- 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() 를 메소드 오버라이딩 하는 코드
- toString() 은 DTO 객체가 리턴될때 자동으로 실행됨, 실행되면 값들을 리턴해줌
- 이제 DAO -> Service 로 돌아가야한다
- BookServiceImpl.java (중복)
package sample12.service;
import sample12.dao.BookDao;
import sample12.model.Book;
publicclassBookSericeImplimplementsBookService{
private BookDao bd;
// BookDao bd = new BookDaoImpl()publicvoidsetBd(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;
publicclassEx01{
publicstaticvoidmain(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"?><beansxmlns="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-scanbase-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 객체를 구해올까?
- 이제 어노테이션 @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;
publicclassEx01{
publicstaticvoidmain(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"?><beansxmlns="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-scanbase-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*/@ServicepublicclassBookServiceImplimplementsBookService{
@Autowiredprivate 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*/@RepositorypublicclassBookDaoImplimplementsBookDao{
public Book getBook(String title){//title="바람과 함께 사라지다"returnnew 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() 메소드를 호출
인터페이스를 사용하는 이유
- 예전에는 통일성있는 클래스를 작성하기 위해 인터페이스를 사용
- 지금은 메소드가 많아서 그걸 관리하기 위해 인터페이스를 사용한다 (어떤 메소드가 있는지 색인처럼 확인 가능)
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;
publicclassMemberServiceImplimplementsMemberService{
private MemberDao md;
publicvoidsetMd(MemberDao md){
this.md = md;
}
publicintinsert(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();
}
publicintdelete(String email){
int result = 0; // 데이터가 이미 있는지 확인
Member member = md.selectByEmail(email);
if (member != null) {
md.delete(email);
result = 1;
} else {
System.out.println("없는네 우찌 삭제하니");
}
return result;
}
publicintupdate(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 부분)
publicintinsert(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 로 이동한다
- DB 연동을 안하므로, Map 자료구조에 회원 데이터를 저장할 것임, 여기 DAO 의 Map 객체 map 에 저장할 것
- Map 자료구조에서 키값은 String, 밸류값은 Member(DTO) 가 자료형이다
- 키값이 Email 주소이고, 밸류값은 해당 회원의 정보를 담게 할 것임
- nextId 는 몇명이 등록되어있는지 저장하는 역할, insert() 가 되면 이 값을 증가시켜서 setId() 메소드로 넘어온 DTO 객체 member 에 저장 * 아래까지 모두 보고와야 이해됨
<selectbyemail() 메소드>
- map.get() 으로 넘어온 이메일을 키로, 그 키의 밸류인 DTO 객체를 돌려주고 있다
- 해당 키(매개변수로 넘어온) 에 해당하는 밸류가 있으면 밸류를 리턴, 없으면 null 을 리턴
- Service 로 돌아가자
- Service 의 insert() 부분만
publicintinsert(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 이다
- 매개변수로 넘어온 객체 member 에 setId() 로 id(아이디 비번할떄 id 가 아니라 인식을 위한 id) 를 세팅
- 현재는 DB 가 아닌 메모리에 저장한다, 그래서 map 자료구조에 put() 으로 키를 이메일, 밸류를 상세정보를 저장한 member 로 준다
+ map 은 키값 중복 안됨
- DAO 의 insert() 에서 반환은 하지 않음
- 다시 Service로 돌아옴
- Service 클래스 insert() 부분만
publicintinsert(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() 부분만
publicstaticvoidinsert(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()
publicstaticvoidselect(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() 부분만
publicstaticvoidselect(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() 부분만
publicstaticvoidlist(){
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() 부분만
publicstaticvoidlist() {
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() 메소드를 호출
인터페이스를 사용하는 이유
- 예전에는 통일성있는 클래스를 작성하기 위해 인터페이스를 사용
- 지금은 메소드가 많아서 그걸 관리하기 위해 인터페이스를 사용한다 (어떤 메소드가 있는지 색인처럼 확인 가능)
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")publicclassMemberServiceImplimplementsMemberService{
@Autowiredprivate MemberDao md;
publicintinsert(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();
}
publicintdelete(String email){
int result = 0; // 데이터가 이미 있는지 확인
Member member = md.selectByEmail(email);
if (member != null) {
md.delete(email);
result = 1;
} else {
System.out.println("없는네 우찌 삭제하니");
}
return result;
}
publicintupdate(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") 불러옴
- 지금은 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 객체가 주입됨
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@tagliburi="http://java.sun.com/jsp/jstl/core"prefix="c" %><%@pagesession="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-appversion="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 어노테이션으로 요청을 받는다
- 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.
*/@ControllerpublicclassHomeController{
privatestaticfinal 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로 출력하는 것임
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@tagliburi="http://java.sun.com/jsp/jstl/core"prefix="c" %><%@pagesession="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 폴더가 없다
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점]
사용자 폼 태그에서 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 파일로 작성해서 제출하세요.
[문항10] 위 8번 문제의 게시판 폼에서 넘어온 값을 액션태그를 사용하여 자바빈 클래스(DTO)에 저장하고 출력하는 프로그램을 작성하세요. [10점]
1) 한글값이 깨지지 않도록 처리한다. 2) 게시판 폼에서 넘어온 값을 액션태그를 이용해서 자바빈 클래스에 저장하고, 저장된 값을 출력하시오. 3) 게시판 양식 : boardform.html 폼에서 넘어온 값을 받는 파일 : board.jsp 자바빈 클래스 : board – BoardBean.java board.txt 파일로 작성해서 제출하세요.
- 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 전달 --><selectid="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 >= #{start} and rnum <= #{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() 메소드 부분만
// 총데이터 갯수 구하기publicintgetCount()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)
<!-- 글갯수 --><selectid="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 전달--><selectid="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 >= (#{page}-1) * 10 + 1 and rnum <= #{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 파일에서는 < 와 > 를 잘 인식하지 못함, > 대신 특수문자 > 를 사용, < 대신 특수문자 &li; 을 사용
+ <와 > 를 대신하는 다른 방법도 있다, 여기서는 > 과 < 을 사용
- 목록을 성공적으로 가져올때 콘솔창
- 이제 목록을 가져오는 작업은 끝났다, Controller 클래스로 갔다가 Dispatcher 방식으로 qna_board_list.jsp 로 포워딩됨
- 전달받은 글 번호 board_num 을 session.update() 의 두번째 매개변수로 전달함
- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_updatecontent)
<!-- 조회수 증가 --><updateid="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} 으로 넘어온 글 번호를 쓴다
- 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() 부분
- 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중복검사, 회원인증 --><selectid="idcheck"parameterType="String"resultType="member">
select * from member0609 where id = #{id}
</select>
- MemberDAO.java 에서 delete() 메소드 부분만
// 회원 탈퇴publicintdelete(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)
<!-- 회원 삭제 --><deleteid="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
<projectxmlns="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 라이브러리를 불러오기
- model 패키지 안의 DTO 경로를 alias 속성을 통해 member 라는 별칭으로 지정
- mapper 태그로 Mapper 파일인 member.xml 파일을 불러옴, 같은 폴더 안에 있으므로 이름만으로 불러옴
- Mapper 파일이 다른 패키지안에 있으면 불러올 때 패키지명도 명시해줘야함
- Mapper 파일인 member.xml 파일을 보자
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="memberns"><insertid="insert"parameterType="member">
insert into member22 values (#{id}, #{password})
</insert><selectid="list"resultType="member">
select * from member22
</select><selectid="select"parameterType="String"resultType="member">
select * from member22 where id = #{id}
</select><updateid="update"parameterType="member">
update member22 set password = #{password} where id = #{id}
</update><deleteid="delete"parameterType="String">
delete from member22 where id = #{id}
</delete></mapper>
- 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 mapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="memberns"><insertid="insert"parameterType="member">
insert into member22 values (#{id}, #{password})
</insert><selectid="list"resultType="member">
select * from member22
</select><selectid="select"parameterType="String"resultType="member">
select * from member22 where id = #{id}
</select><updateid="update"parameterType="member">
update member22 set password = #{password} where id = #{id}
</update><deleteid="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 값을 사용하면 자동으로 리스트에 저장해서 돌려줌
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><%@pageimport="java.util.List"%><%@pageimport="model.Member"%><%@pageimport="dao.MemberDao"%><!DOCTYPE html><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>
회원 명단
<tableborder=1><tr><th>아이디</th><th>비밀번호</th><th>수정</th><th>삭제</th></tr><%MemberDaomd = newMemberDao();
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><inputtype="button"value="수정"onclick='location.href="updateForm.jsp?id=<%=list.get(i).getId() %>"'></td><td><inputtype="button"value="삭제"onclick='location.href="delete.jsp?id=<%=list.get(i).getId() %>"'></td></tr><%
}
%></table><ahref="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문 부분
<selectid="list"resultType="member">
select * from member22
</select>
+ select는 반드시 resultType 을 써야함, 받을 값은 없으므로 parameterType 은 쓰지 않았다