Spring07_스프링으로 게시판 구현하기

5 minute read



0. 구현하기 전Permalink

uri 축소하기Permalink

  • REST방식의 uri는 method + uri로 구성되어 있다.
  • POST방식은 테이블 기반으로 살펴보았을 때, 일반적으로 정보를 등록하겠다는 의미가 강하다.
  • 기존의 URI가 /board/selectOne.do?no=?일때 Spring 방식의 URI는 /board?no=5 또는 /board/5
    • GET - 조회
    • POST - 수정
    • DELETE - 삭제

REST는 왜쓸까?Permalink

  • URI로 내 정보를 전달하기 위해 사용.
  • 분산서버에서 많이 씀

구현 순서Permalink

  • SpringContainer 실행
  • Spring-mvc.xml에 등록된 bean객체 생성
    • SqlSessionFactory, SqlSessionTemplate…
  • Autowired로 자동주입
    • 개발자에게 필요한건 SqlTemplate

그외 TipPermalink

  • 게시판 하나 조회하는데 Spring을 사용하지 않음
  • Spring은 규모가 있는 프로젝트를 위해 사용하는 프레임워크
  • 계속 유지보수가 필요한 프로젝트에 필요 ( 변화에 민감하지 않음 ) - 상속, 추상클래스, 인터페이스
    • 인터페이스먼저만들어놓고 DAO 생성하는게 맞다.



1. 환경설정Permalink

  • Maven Project생성

pom.xmlPermalink

<dependencies>
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.3.9</version>
  </dependency>

   <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
  <dependency>
     <groupId>javax.servlet.jsp</groupId>
     <artifactId>javax.servlet.jsp-api</artifactId>
     <version>2.3.3</version>
     <scope>provided</scope>
  </dependency>

   <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
  <dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>4.0.1</version>
     <scope>provided</scope>
  </dependency>

</dependencies>

config/spring/spring-mvc.xmlPermalink

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">

	<context:component-scan base-package="kr.ac.kopo" />
    <mvc:annotation-driven />
	<mvc:default-servlet-handler />
	<mvc:view-resolvers>
		<mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp" />
	</mvc:view-resolvers>

</beans>

webapps/WEB-INF/web.xmlPermalink

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>spring-mvc</display-name>
  
  <servlet>
  	<servlet-name>dispatcher</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<init-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>
  			classpath:config/spring/spring-mvc.xml
  		</param-value>
  	</init-param>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>  
  </servlet-mapping>
  
  <filter>
  	<filter-name>encodingFilter</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>
  </filter>

  <filter-mapping>
  	<filter-name>encodingFilter</filter-name>
	<url-pattern>/*</url-pattern>  
  </filter-mapping>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>



2. Spring DBPermalink

  • spring은 미리 컨테이너가 필요한 객체를 만들어놓고 필요한 객체를 주입하는 방식을 사용한다.
  • SQL Session팩토리 객체 -> SQL Session Templates객체 생성

2-1. 환경설정Permalink

  1. DriverManager를 이용하여 DataSource 설정하는 방법
  2. 커넥션 풀을 이용한 data source 설정하는 방법 : DBCP API를 이용한 설정
    • IO를 사용하면 DB 열고 닫는데 시간 오래걸림
  3. 인터페이스를 이용하는 방법
  • DAO 에서 SQL Session을 얻기 위해 XML 문서에 SqlSessionTemplate를 선언
  • 필요 라이브러리
    • commons-dbcp
    • ojdbc8
    • spring-jdbc
    • MyBatis
    • MyBatis spring


01) commons-dbcp, ojdbc8 추가Permalink

  • pom.xml
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>


<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 -->
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.1.0.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.9</version>
</dependency>


02) DataSource설정Permalink

  • config/spring/spring-mvc.xml
<!-- DataSource설정 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
  <property name="url" value="jdbc:oracle:thin:@localhost:xe"/>
  <property name="username" value="hr"/>
  <property name="password" value="hr"/>
</bean>

02-1) properties파일로 db driver 정보 넘기고 싶을 때.Permalink

  • config/prop 폴더 생성 후 db.properties파일 생성후 DB연결 정보 입력
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:포트번호:sid
username=hr
password=hr


  • config/spring/spring-mvc.xml
<!-- properties파일 연결 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="classpath:config/prop/db.properties" />
  <property name="fileEncoding" value="UTF-8"></property>
</bean>


<!-- DataSource설정 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</bean>


잘 연결되었나 확인하기Permalink

  • 내가 만든 메소드가 잘 실행되는지 확인해보고 싶을 때 수행하는 것이 단위테스트 (JUnit)
  • 필요 라이브러리를 pom.xml 에 붙여넣는다.
    • JUnit
    • Spring test
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.9</version>
    <scope>test</scope>
</dependency>


  • src/test/java에 class 만들기
    • kr.ac.kopo -> MybatisTest.java생성
package kr.ac.kopo;

import static org.junit.Assert.assertNotNull;
import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:config/spring/spring-mvc.xml"})
public class MybatisTest {
	
	@Autowired
	private DataSource dataSource;
	
	@Test // Test실행시 사용하는 메소드
	public void DataSourceTest() throws Exception{
		System.out.println("dataSource : " + dataSource);
    //또는
    assertNotNull(dataSource);//올바르게 주입되었는지 확인.
	}

}
  • Junit 실행하면 dataSource가 나옴.

03) MyBatis, MyBatis Spring 추가Permalink

  • pom.xml
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

04) Sql Session Factory 연결Permalink

  • src/main/resourcesmybatis 폴더생성 후 sqlMapConfig.xml 생성
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>
  • config/spring/spring-mvc.xmlsqlSessionFactory , sqlSessionTemplate추가
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:config/mybatis/sqlMapConfig.xml" />
</bean>

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

05) Mapper파일 만들기Permalink

  • config/sqlmap/oracle경로에 sqlmap-board.xml파일 생성
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board.BoardDAO">

</mapper>


  • config/spring/spring-mvc.xml 파일 sqlSessionFactory 부분 property 추가 (Mapper 추가)
    • *.xml은 모든 xml파일 읽겠다는 뜻
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configLocation" value="classpath:config/mybatis/sqlMapConfig.xml" />
  <property name="mapperLocations" value="classpath:config/sqlmap/oracle/*.xml" />	
</bean>




2-2. 실습하기Permalink

01) 전체게시글 조회Permalink

sqlmap-board.xmlPermalink

  • mapper태그 안에 추가
  • 편의를 위해 resultMap 사전 설정
	<resultMap type="kr.ac.kopo.board.vo.BoardVO" id="boardMap">
		<result column="view_cnt" property="viewCnt"/>
		<result column="reg_date" property="regDate"/>
	</resultMap>
	<select id="selectAll" resultMap="boardMap">
		select no, title, writer, to_char(reg_date,'yyyy-mm-dd') as reg_date
		from t_board
		order by no desc
	</select>

DAO Interface 생성Permalink

  • BoardDAO
package kr.ac.kopo.board.dao;

import java.util.List;

import kr.ac.kopo.board.vo.BoardVO;

public interface BoardDAO {
	
	/**
	 * 전체 게시글 조회
	 * @return DB에서 조회된 BoardVO List
	 */
	List<BoardVO> searchAll();
}

DAO Class 생성Permalink

  • BoardDAOImpl
package kr.ac.kopo.board.dao;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.ac.kopo.board.vo.BoardVO;

@Repository
public class BoardDAOImpl implements BoardDAO{

	@Autowired
	private SqlSessionTemplate sqlSessionTemplate;
	
	
	public List<BoardVO> searchAll() {
		List<BoardVO> list = sqlSessionTemplate.selectList("board.BoardDAO.selectAll");
		return list;
	}
}
  • 단위 테스트 (BoardTest.java)
    • componet scan에 의해 객체가 만들어졌기 때문에 Test에서 boardDAO로 자동주입할 수 있다.
package kr.ac.kopo;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import kr.ac.kopo.board.dao.BoardDAO;
import kr.ac.kopo.board.vo.BoardVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:config/spring/spring-mvc.xml"})
public class BoardTest {
	
	@Autowired
	private BoardDAO boardDAO;
	//private SqlSessionTemplate sqlSessionTemplate;
	
	
	@Test
	public void 전체게시글조회Test() throws Exception{
		List<BoardVO> list  = boardDAO.searchAll();
		//List<BoardVO> list = sqlSessionTemplate.selectList("board.BoardDAO.selectAll");
		for(BoardVO board:list) {
			System.out.println(board);
		}
		
	}

}

Service Interface 생성Permalink

  • BoardService.java
package kr.ac.kopo.board.service;

import java.util.List;

import kr.ac.kopo.board.vo.BoardVO;

public interface BoardService {
	List<BoardVO> selectAllBoard();
}

Service 생성Permalink

  • BoardServiceImpl.java
package kr.ac.kopo.board.service;

import java.util.List;

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

import kr.ac.kopo.board.dao.BoardDAO;
import kr.ac.kopo.board.vo.BoardVO;

@Service
public class BoardServiceImpl implements BoardService{

	
	@Autowired
	private BoardDAO boardDAO;
	
	
	/**
	 * DAO에서 sqlSessionTemplate로 반환한 전체게시글 리스트 반환 
	 */
	public List<BoardVO> selectAllBoard() {
		List<BoardVO> boardList = boardDAO.searchAll();
		return boardList;
	}
	
}

Controller생성Permalink

  • BoardController.java
package kr.ac.kopo.board.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class BoardController {
	
	@RequestMapping("/board")
	public ModelAndView selectAll() {
		List<BoardVO> boardList = service.selectAllBoard();
		ModelAndView mav = new ModelAndView("board/list");
		mav.addObject("list", boardList); // 공유영역에 업로드
		
		return mav;
	}
}

index.jsp 수정Permalink


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>안녕!</h2>
	<a href="${ pageContext.request.contextPath }/board">게시글</a>
</body>
</html>
  

list.jspPermalink


<div align="center">
  <hr>	
  <h2>게시판 목록</h2>
  <hr>

  <br>
  <table id="list" border="1" style="width:100%">
    <tr>
      <th width="7%">번호</th>
      <th>제목</th>
      <th width="16%">작성자</th>
      <th width="20%">등록일</th>
    </tr>

    <c:forEach items="${ requestScope.list }" var="l" varStatus="loop">
    <tr <c:if test="${ loop.index mod 2 ne 0 }"> class="odd"</c:if>>
      <td>${ l.no }</td>
      <td>
        <a href="${ pageContext.request.contextPath }/board/detail?no=${l.no}">
        <%-- href="javascript:doAction()" a태그에서 자바스크립트 문법 실행하도록. --%>
        <%-- <a href="javascript:doAction(${ l.no })"> --%>
          <c:out value="${ l.title }" />
        </a></td>
      <td>${ l.writer }</td>
      <td>${ l.regDate }</td>
    </tr>
    </c:forEach>
  </table>
  <br>
  <c:if test="${ not empty sessionScope.loginfo }">
    <button id="addBtn">새글등록</button>
  </c:if>
</div>

02) 상세게시글 조회Permalink

DAO InterfacePermalink

  • 번호로 조회하는 VO객체 추가
/**
 * 글 번호로 게시글 조회
 * @return DB에서 No로 조회된 BoardVO
 */
BoardVO searchOne(int no);

DAOPermalink

public BoardVO searchOne(int no) {
  BoardVO board= sqlSessionTemplate.selectOne("board.BoardDAO.selectByNo", no);
  return board;
}

Service InterfacePermalink

BoardVO selectOneBoard(int no);

ServicePermalink

public BoardVO selectOneBoard(int no) {
  BoardVO board = boardDAO.searchOne(no);
  return board;
}

Contorller 메소드 추가1 ( ?no= 방법 )Permalink

  • /board/detail?no=11 형식
/**
 * 상세게시글 조회 ?no=
 * @param request
 */
@RequestMapping("/board/detail")
public void selectByNo(HttpServletRequest request) {
  int no = Integer.parseInt(request.getParameter("no"));
}

//또는

/**
 * 상세게시글 조회 ?no=
 * @param request
 */
@RequestMapping("/board/detail")
public void selectByNo(@RequestParam("no") int no) {
  // HttpServletRequest request
  //int no = Integer.parseInt(request.getParameter("no"));
  System.out.println(no);
}
  • list-> detail연결부분 herf 링크
    • <a href="${ pageContext.request.contextPath }/board/detail?no=${l.no}">


Contorller 메소드 추가2 ( REST방식 )Permalink

  • GET방식일 때 /board/11 형식 (11번 게시물 조회)
    • @PathVariable 사용
@RequestMapping("/board/{no}")
public ModelAndView selectByNo2(@PathVariable("no") int no) { // Path에서 날아온 변수를 사용 (RequestMapping{no}에 설정한 변수이름과 같게한다.)

  BoardVO board = service.selectOneBoard(no);
  //BoardVO board = sessionTemplate.selectOne("board.BoardDAO.selectByNo", 11);
  ModelAndView mav = new ModelAndView("board/detail");
  mav.addObject("board", board);

  return mav;
}
  • list-> detail연결부분 herf 링크
    • <a href="${ pageContext.request.contextPath }/board/${l.no}">
  • 목록으로 되돌아가기 링크 변경
    • location.href=”${ pageContext.request.contextPath }/board