Learn/KH정보교육원

[KH 정보교육원 당산]63일 (Spring Simple Board(플러그인이 아닌 라이브러리 사용))

Dahoon06 2021. 6. 10. 20:57
728x90
반응형

 

이번 실습에서는 Spring 방식으로 간단한 게시판을 만들 것이다.

 

이클립스에 플러그인을 설치해서 하는 방식이 아닌 스프링 라이브러리를 가져와서 하는 방법

 

spring 관련 라이브러리를 추가해준다.

 

 

 

먼저 생성된 데이터베이스 테이블

springBoard 테이블


1. DispatcherServlet => 스프링이 제공.(모든 요청사항을 처리)

     : web.xml에서 등록할것이기에 별도의 class파일 생성 X

2. HandlerMapping => 스프링이 제공.(요청사항을 찾는 역할)

     : 요청사항에 따른 Controller 반환, board-servlet.xml문서로 생성

3. ~~Controller 객체들 => 개발자가 직접 생성(Controller 관련 : 상속, 구현)

4. DB접근 => .xml문서 생성 (Context.xml)

     : META_INF 디렉토리에 생성

     : Connection 객체를 여러개 미리 생성 => pool 담아놓는다.

5. DAO / DTO : 개발자가 직접 생성

6. 웹브라우저 응답 파일 => HTML / JSP

- 프로젝트 구동시 - 
application => web.xml(가장 먼저 읽음)
웹브라우저 요청 => web.xml의 DispatcherServlet => board-servlet.xml 내의 HandlerMapping에게 전달
=> HandlerMapping은 요청을 처리할 수 있는 컨트롤러 반환
=> ~~Controller요청사항 처리 후,결과 값과 출력파일명을 ModelAndView 객체로 반환
=> 반환 받은 DispathcerServlet은 => viewResolver 에게 전달
=> ViewResolver는 해당 파일의 위치와 확장자를 반환
=> 반환받은 DispatcherServlet은 해당 위치의 파일을 웹브라우저에게 응답 처리

 

 


web.xml 작성

servlet 등록 읽어들이는 순서

<servlet>

    <servlet-name>서블릿 이름</servlet-name> (3) mapping 이름과 서블릿이름과 같은지 비교(다르면 에러)

    <servlet-class>둥록할 서블릿 클래스의 전체경로 클래스명</servlet-class> (4) 클래스를 가지고 객체 생성

</servlet>

<servlet-mapping>

    <servlet-name>서블릿 이름</servlet-name> (2)

    <url-pattern>웹주소줄을 통하여 요청</url-pattern> (1)

</servlet-mapping>

 

<!-- 컨트롤러역할을 하는 서블릿의 이름 및 요청경로를 지정  -->
<!-- DispatcherServlet 등록 => 스프링이 제공(라이브러리 필요) -->
<servlet>
  	<servlet-name>board</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<!-- 요청사항에 따른 서블릿 클래스 찾기 -->
<!-- 모든 요청 사항에 .do가 붙으면 DispatcherServlet을 찾아서 처리 -->
<servlet-mapping>
  	<servlet-name>board</servlet-name>
  	<url-pattern>*.do</url-pattern>
</servlet-mapping>

스프링이 제공하는 DispatcherServlet 클래스 사용 => board 라는 이름으로

url-patter => *.do.  => ~~~~.do 앞쪽이 뭐가 됐든 .do로 끝나면 DispatcherServlet으로 넘어간다.

 


DAO 및 DTO 생성

DTO는 테이블에 해당되는 getter와 setter를 만들어 주면된다.

 

xml문서로 데이터 접속

public class BoardDAO {
	//데이터를 저장 받을 변수 선언
	DataSource ds;
	
	public BoardDAO() {
		//1. 데이터 소스를 관리하는 Context객체를 먼저 가져와야한다.(Context.xml 문서내의 name="jdbc/mysql"속성값을 추출
		//방법 : InitialContext 객체를 이용
		//	해당 객체가 가지고 있는 lookup()를 통해 name 속성 값을 추출할 수 있다.
		//	단, jdbc/mysql을 찾기 위해서는 java:comp/env/jdbc/mysql 와 같이 적어야 한다. 
                //      (comp => component env => environment)
		try {
		InitialContext ctx = new InitialContext();
		
		//lookup() 메서드는 반환 타입이 Object 이기 때문에 형변환 필수
		//Object타입으로 반환하는 이유 : 어떤타입의 DBDriver에서 접근할지 모르기 때문에 Object 타입으로 반환 해주는 것!!
		ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mysql");
		
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
    
  }

기존에 만들었던 Connection 객체 대신 DataSource 객체를 만들어서 사용.

 

먼저 Context.xml을 작성해야한다. (web -> META-INF -> Context.xml)

 

Context.xml

이와같이 이름을 통해서 접근하는 방식을 JNDI방식

=> jdbc/mysql 이름을 통해서 접근하는 방식 : JNDI방식 : "java:comp/env/name속성값"

<?xml version="1.0" encoding="UTF-8"?>

<Context>
	<Resource name="jdbc/mysql"
			  auth="container"
			  type="javax.sql.DataSource"
			  username="dahoon226"
			  password="ekgns00"
			  driverClassName="com.mysql.jdbc.Driver"
			  factory="org.apache.commons.dbcp.BasicDataSourceFactory"
			  url="jdbc:mysql://localhost:3306/springTest?useSSL=false"
			  maxActive="20"
			  maxIdle="10">
	</Resource>
</Context>

해당 xml에서 데이터베이스 접속에 필요한 데이터를 기입

maxActive => 최대 커넥션풀 객체의 개수

maxidle => 여분의 커넥션풀

 

** xml문서를 작성할때 대소문자 구별, 속성명이 정확히 일치해야한다.

실수했던 예로 username을 userName같이 사용하면 해당 이름으로 된 속성명이 없기 때문에 값을 찾지 못한다.

 

name 속성의 값은 임의로 설정 가능. 

 

예시) 오라클

web -> WEB-INF -> board-servlet.xml 생성

 

<bean> 태그를 사용하기 위해서는 반드시 <beans> </beans> 사이에 생성해야한다.

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd">



</beans>

 

 

데이터 베이스 접속

<!-- 컨트롤러를 알려주는 역할 id값은 변경 가능 -->
	<bean id="defaultHandlerMapping"  class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
	
<!-- DB 접속 : JNDI 방식 -->
	<bean id="boardDAO" class="board.dao.BoardDAO"></bean>

JNDI (Java Naming and Directory Interface)

: WAS단에 데이터베이스 컨넥션 객체를 미리 네이밍 해두는 방식!!

 

특징)

- DB 커넥션 WAS단에서 제어하면서 서버에서 하나의 커넥션 풀을 가짐

- Application이 DB에 직접 Connection을 요청하는 것이 아니라 JNDI lookup()을 통하여

DataSource의 객체를 얻은 후 Connection 요청

 

** JNDI 는 재활용을 하기 위한 코드로 부하가 발생하지 않게 하기 위한 JNDI를 써야한다.

 


글목록 조회 dao 작성

기존 DAO 클래스에 코드 추가

public ArrayList<BoardDTO> getList(){
		ArrayList<BoardDTO> list = new ArrayList<BoardDTO>();
		String sql = "SELECT * FROM springBoard order by num desc";
		
		try {
			Connection conn = ds.getConnection();
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql);
			
			while(rs.next()) {
				BoardDTO dto = new BoardDTO();
				
				dto.setNum(rs.getInt("num"));
				dto.setAuthor(rs.getString("author"));
				dto.setTitle(rs.getString("title"));
				dto.setContent(rs.getString("content"));
				dto.setWriteDay(rs.getString("writeDay"));
				dto.setReadCnt(rs.getInt("readCnt"));
				list.add(dto);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return list;
	} // getList() END

 

글목록을 보기위한 컨트롤러 클래스 추가

스프링 라이브러리를 임포트 했기 때문에 클래스 생성시에 스프링이 가지고 있는 Controller를 사용할 수 있다.(인터페이스 검색을 통하여)

이때 데이터를 전송 할때 ModelAndView 객체를 만들어서 사용

(데이터 전송과 페이지 전환 둘다 할것이기 때문에 ModelAndView 객체 생성)

페이지 전환 => ModelAndView가 가지고 있는 setViewName("이동할 페이지") 메소드

데이터 전송 => ModelAndView가 가지고 있는 addObject("값을 담을 변수",값이들어있는변수")

기존에 사용했던 request.setAttribute("x", x);와 같은 방식이다.

 

 

** setViewName으로 페이지를 이동하기 위해서는 board-servlet에 viewResolver를 등록해줘야한다.!!

board-servlet에 해당 빈 추가

 

중요!!!

<!-- viewResolver(위치, 이동할 페이지 지정) : 실제 넘어오는 값 => "list" 같이 문자열 상태로 넘어온다. -->
	<bean id="viewResolover"  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
	    <property name="prefix" value="/"/> <!-- prefix를 먼저 읽기 때문에 "/list" 이렇게 읽음 -->
	    <property name="suffix" value=".jsp"/> <!-- suffix에 의해 "/list.jsp"로 변환됨 -->
	</bean>

 

예시) 위에 setViewName으로 값을 list로 세팅했다 그러면 먼저 prefix 부분으로 전달. => value="/" 경로를 설정했기 때문에

/list로 변환되고 다음 suffix로 전달되어 value=".jsp" 확장자가 선택되어 /list.jsp로 변환된다.

 

따라서 컨트롤러에 의해 페이지를 이동할 수 있도록 값이 세팅되게 된다.

 

 

컨트롤러 등록

기존 board-servlet.xml에 코드 추가

<!-- "글목록 보기" 컨트롤러 등록 : private dao => setDao(boardDAO) -->
	<bean name="/list.do" class="board.controller.ListActionController">
		<property name="dao"> <!-- DAO클래스에 setter를 만들었으니 자동으로 DAO 클래스의 멤버변수 dao에 자동으로 값이 들어간다. -->
			<ref bean="boardDAO" />
		</property>
	</bean>

list.do의 이름으로 값이 들어왔을 경우 ListActionController를 호출하게 되어있다.

 

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	response.sendRedirect("http://localhost:8080/spring_simple_board/list.do");
%>

프로젝트 구동 시 실행되는 index.jsp => url을 보면 list.do이름으로 구동된다.

 

따라서 위 과정을 통하여 list.jsp로 바로 이동하게 된다.

 

list.jsp <body> 부분

간단하게 테스트

ListActionController에서 ModelAndView의 addObject()를 통하여 list라는 이름으로 값을 담았다.

현재 페이지에서 그안에 담긴 값을 사용하기 위해 값을 꺼내어 사용

 

다음 글 작성)

a태그로 감싸진 제목 부분을 클릭하게 되면 writeui.do로 되어있다.

 

web.xml에서 .do로 끝나는 값은 무조건 서블릿을 호출하게 설정했으니 서블릿을 통하여 컨트롤러로 이동하게 된다.

 

board-servlet에 writeui.do 컨트롤러 등록

<!-- 글쓰기 화면 처리 컨트롤러 등록 : 스프링이 제공하는 클래스 사용 -->
<!-- 단순히 화면 이동 viewName으로 write(파일명)이 저장된다. -->
	<bean name="/writeui.do" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
		<property name="viewName" value="write"></property> <!-- write.jsp로 읽을것이다. 아래 suffix에 의해 -->
		<!-- ModelAndView.setViewName("write") return ModelAndView 객체 -->
	</bean>

viewName을 통하여 write 값을 전달 => 마찬가지로 viewResolver 를 통하여 write.jsp로 변환되어 페이지 이동

 

write.jsp

Spring 흐름을 익히기 위한 실습이기 때문에 간략하다.

 

form 태그를 보면 write.do  끝에 .do가 붙었기 때문에 바로 서블릿으로

 

서블릿으로 가기 전에

DAO 추가 코드

게시글 작성하는 코드

//새 글에 대한 글번호를 위한 메서드
	public int getNewNum() {
		int newNum = 1;
		try {
			String sql = "SELECT max(num) FROM springBoard";
			Connection conn = ds.getConnection();
			PreparedStatement stmt = conn.prepareStatement(sql);
			ResultSet rs = stmt.executeQuery();
			
			if(rs.next()) {
				newNum = rs.getInt(1)+1;
				//max(num)는 실제 필드명이 아니기 때문에index를 사용 (1)
				//현재 필드 값 중 가장 큰값의 +1 => 시퀀스를 사용하지 않기 때문(물론 나는 auto-increment)
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return newNum;
	}
	
	//글 저장
	public void write(BoardCommand data) {
		try {
			int newNum = getNewNum(); //새로운 글 번호
			String sql = "INSERT INTO springBoard (num,author,title,content) values (?,?,?,?)";
			
			Connection conn = ds.getConnection();
			PreparedStatement pstmt = conn.prepareStatement(sql);
			
			pstmt.setInt(1, newNum);
			pstmt.setString(2, data.getAuthor());
			pstmt.setString(3, data.getTitle());
			pstmt.setString(4, data.getContent());
			
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}// 글작성 끝

글번호를 증가시키는 코드는 이번 예제에서 시퀀스르 사용하지 않았다.

그렇기 때문에 현재 게시된 글 중에 최대 번호의 +1을 하여 이번에 작성되는 게시글번호로 지정할 것이다.

** 난 mysql로 작업했기 때문에 시퀀스가 아닌 AL(auto-increment)설정을 하지 않았다.

 

 

다시 board-servlet으로 돌아가서 write.do의 컨트롤러 설정

 

<!-- 글 저장 처리 컨트롤러 등록  -->
	<bean name="/write.do" class="board.controller.WriteActionController">
		<property name="dao">
			<ref bean="boardDAO" /> <!-- dao의 값 => boardDAO -->
		</property>
		<property name="commandClass" value="board.command.BoardCommand"></property>
<!-- BoardCommand 객체는 handle() 세번째 매개변수에 자동 전달 -->
	</bean>

클래스를 보면 WriteActionController를 받게 되어있다.

 

새로운 클래스를 만들어준다(WriteActionController 클래스)

ListActionController에서는 Controller를 구현 받았다면 이번엔 상속을 통하여 작성할것이다.

클래스를 만들때 SuperClass 검색하는 것을 통하여 AbstractCommandController를 상속 받는다.

 

상속을 받으면 주석처리 해놓은 생성자 2개가 만들어진다.

이 생성자를 사용하기 위해서는 기본 생성자가 존재해야 사용가능하다고 한다.

저 부분 때문에 계속 에러가 나와서(생성자쪽 에러인거같다.)지우지 않고 주석처리 해놓은것 

저 부분을 지우니까 에러는 나지 않았다.

 

write.do가 WriteActionController를 받게 되어 있으므로 여기서 데이터를 처리 한 후 페이지 이동

index.jsp때 처럼 다시 list.do로 경로를 잡아서 리턴.

 

데이터 베이스에 정상적으로 데이터가 들어오는지 확인!!

번호도 정상적으로 증가하고 값도 잘 들어왔다.

 

다음 부분은 내일 이어서..

 


728x90
반응형