상세 컨텐츠

본문 제목

Spring 블로그 만들기 - 3. 게시판 리스트 화면 만들기

개발/Spring 블로그 만들기

by 똘똘이박사 2019. 1. 18. 17:22

본문



이 포스팅의 샘플 게시판 개발 환경은 MAC OS, STS, OpenJDK11 입니다.


게시판 리스트를 만들기 위해 이번 포스팅에서 진행할 내용입니다.

  1. Service 만들기(BoardService, BoardServiceImpl)

  2. Controller 만들기(BoardController)

  3. 게시판 리스트 화면 만들기

  4. 부트스트랩(Bootstrap)을 이용해 View 화면 꾸미기



블로그 만들기 - 3. 게시판 리스트 화면 만들기


이전 포스팅에서 DB 처리와 관련된 거의 모든 작업(DAO 및 Mapper)은 끝났습니다.

따라서 이제 게시판을 목록을 조회하거나 상세내용을 조회하고, 수정/삭제 하는 등의 

비즈니스 로직과 관련된 부분을 처리할 차례 입니다.


Service 를 만들기 전에 MVC 모델에서 기본적으로 프로그램이 어떻게 흘러가는지

게시판 리스트 조회의 상황을 간단히 살펴 보겠습니다.



가장 먼저 사용자가 웹 브라우저(View)에서 게시판 목록을 선택합니다.

그러면 브라우저에서는 서버 쪽으로 '게시판 리스트 조회' 요청을 보내 옵니다.


서버에서 사용자의 요청을 받는 부분은 Controller 인데

Controller 에서는 사용자가 어떤 요청을 했는지 구분하여 요청에 맞는 Service를 호출 합니다.

따라서 여기서는 게시판 리스트 조회에 해당하는 Service를 호출하게 됩니다.


Service는 Controller의 요청에 따라 필요한 비즈니스 로직을 처리하면서

필요한 경우 데이터베이스에 데이터 처리(입력, 조회, 수정 삭제 등)를 합니다.

게시판 리스트 조회를 요청 받았으므로 데이터베이스에 접속하여 리스트를 받아 와야 합니다.

이때 Service는 데이터베이스에 바로 접근하는 것이 아니라 DAO에 관련된 작업 일체를 맡기게 됩니다.

DAO는 Service의 요청을 받아 DB접근 하여 데이터 처리를 하고 결과를 다시 서비스에 전달하게 되는데

이때 DB 접근에 필요한 connection을 관리를 DAO가 처리하게 됩니다.

이전 포스팅에서 우리는 DAO를 만들면서 이 작업을 처리 했었습니다.

(DAO에서 간단하게 SqlSession 을 주입받아 처리 했는데, 이것이 바로 DB Connection을 알아서 관리해 주는 객체 입니다.

스프링에서는 이 복잡한 작업들을 몇 가지 설정으로 편리하게 사용 할 수 있습니다.)



Service 만들기 (BoardService, BoardServiceImpl)

Service 단계 역시 DAO 와 마찬가지로 interface를 먼저 만듭니다.

기본적으로 Service 에서도 아래와 같은 기본 기능을 가지게 됩니다.

  • 게시판 리스트 조회(getBoardList)

  • 게시물 상세조회(getBoardContent)

  • 게시물 입력(insertBoard)

  • 게시물 수정(updateBoard)

  • 게시물 삭제(deleteBoard)


괄호 안에 있는 영문명은 Service에서 앞으로 만들 메소드명인데

가만히 보니 이전에 만들었던 DAO의 메소드 명과 같습니다.

(보통 동일한 동작을 하는 기능들에 대해서는 Controller, Service, DAO 에서 동일한 메소드 명으로 만듭니다.

이렇게 하는 것이 추후에 관리가 수월하기 때문입니다.)

만들어야 할 기능을 확인 했으니 이제 interface 부터 만들어 보겠습니다.


BoardService.java

package com.freehoon.web.board.service;


import java.util.List;

import java.util.Map;


import com.freehoon.web.board.model.BoardVO;


public interface BoardService {


public List<BoardVO> getBoardList() throws Exception;

}


BoardServiceImpl.java

package com.freehoon.web.board.service;


import java.util.List;


import javax.inject.Inject;


import org.springframework.stereotype.Service;


import com.freehoon.web.board.dao.BoardDAO;

import com.freehoon.web.board.model.BoardVO;


@Service

public class BoardServiceImpl implements BoardService{


@Inject

private BoardDAO boardDAO;

public List<BoardVO> getBoardList() throws Exception {

return boardDAO.getBoardList();

}


}




Controller 만들기 (BoardController)

이제 Controller를 만들어 봅니다.


BoardController.java

package com.freehoon.web.board.controller;


import javax.inject.Inject;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

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


import com.freehoon.web.board.model.BoardVO;

import com.freehoon.web.board.service.BoardService;


@Controller

@RequestMapping(value = "/board")

public class BoardController {


@Inject

private BoardService boardService;

@RequestMapping(value = "/getBoardList", method = RequestMethod.GET)

public String getBoardList(Model model) throws Exception {

model.addAttribute("boardList", boardService.getBoardList());

return "board/index";

}

}


Controller에는 URL 주소를 조합 하는 방법이 담겨 있습니다.

@RequestMapping 어노테이션을 통해서 주소를 조합 할 수 있는데 의미는 아래와 같습니다.



전체적인 흐름을 다시 정리해 보면

웹 브라우조 주소창에 localhost:8080/web/board/getBoardList 를 입력하게 되면

(보통은 메뉴에서 게시판 이름을 클릭하게 되겠지만)

Controller에서 위와 같은 주소 결합을 통해 getBoardList 메소드를 실행하게 됩니다.

Controller의 getBoardList 메소드 에서는 Service의 getBoardList()를 호출합니다.

Service에서는 DAO의 getBoardList()를 호출하게 됩니다.

이전 포스팅에서 DAO 테스트 했던 것과 마찬가지로 DAO의 getBoardList()는 데이터베이스에서

게시판 리스트를 조회해서 그 결과를 BoardVO 타입으로 Service에 반환하게 되고

Service는 반환 받은 BoardVO 타입의 게시판 데이터를 Controller 에 전달하게 됩니다.

Controller에서는 전달받는 데이터를 "boardList" 라는 이름으로 화면에 전달해야 하는데

화면의 이름은 return 에 있는 문자열 "board/index" 입니다.

화면을 결정 짓는 부분에 대해서는 servlet-context.xml에 대해서 이야기를 해야 하는데

servlet-context.xml 을 보면 중간에 아래와 같은 문구를 확인 할 수 있습니다.


<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<beans:property name="prefix" value="/WEB-INF/views/" />

<beans:property name="suffix" value=".jsp" />

</beans:bean>


이 부분이 뷰 리졸버(View Resolver)에 대한 정의 부분인데

Controller 에서 return 값으로 문자열을 보내면 

위 문구의 prefix 와 suffix를 조합하여 보여줄 화면을 찾아내는 것입니다.

따라서 위의 값을 조합해 보면 아래의 주소가 만들어 집니다.


/WEB-INF/views/board/index.jsp



게시판 리스트 화면 만들기

이제 화면을 만들어 데이터를 제대로 불러 오는지 확인을 해봅시다.

View 화면은 WEB-INF/views 디렉토리 아래에 board 라는 디렉토리를 만듭니다.

여기에 게시판에 대한 리스트, 글쓰기, 상세조회 등 게시판과 관련된 화면의 소스들을 저장할 것입니다.




board/index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>board</title>

</head>

<body>

<h2>board list</h2>

<table>

<colgroup>

<col style="width:5%;" />

<col style="width:auto;" />

<col style="width:15%;" />

<col style="width:10%;" />

<col style="width:10%;" />

</colgroup>

<thead>

<tr>

<th>NO</th>

<th>글제목</th>

<th>작성자</th>

<th>조회수</th>

<th>작성일</th>

</tr>

</thead>

<tbody>

<c:choose>

<c:when test="${empty boardList }" >

<tr><td colspan="5" align="center">데이터가 없습니다.</td></tr>

</c:when> 

<c:when test="${!empty boardList}">

<c:forEach var="list" items="${boardList}">

<tr>

<td><c:out value="${list.bid}"/></td>

<td><c:out value="${list.title}"/></td>

<td><c:out value="${list.reg_id}"/></td>

<td><c:out value="${list.view_cnt}"/></td>

<td><c:out value="${list.reg_dt}"/></td>

</tr>

</c:forEach>

</c:when>

</c:choose>

</tbody>

</table>

</body>

</html>


위에서 반드시 알고 넘어가야 할 문법이 몇 가지 있습니다.

바로 JSTL과 SpEL입니다.

JSTL과 SpEL의 자세한 설명은 다음 링크를 통해 확인 자세히 설명 하도록 하겠습니다.

이 포스팅에서는 <c:choose> 와 <c:when>, 그리고 <c:forEach> 에 대해서만 간략하게 짚고 넘어 가도록 하겠습니다.

<c:choose></c:choose> 문은 우리가 알고 있는 if - else - 문과 동일한 동작을 하는 명령어 입니다.


if(조건1)

else if(조건2)

else if(조건3)

else 


위 문장은 정확히 아래 문장과 동일한 동작을 수행합니다.


<c:choose>

<c:when test="조건1">.....</c:when>

<c:when test="조건2">.....</c:when>

<c:when test="조건3">.....</c:when>

<c:otherwise>...</c:otherwise>

</c:choose>


따라서 위 소스의 <c:choose></c:choose> 구문을 해석해 보면

데이터가 없다면 (<c:when test="${empty boardList}"><./c:when>) '데이터가 없습니다' 라는 문구를 출력하고

데이터가 있다면 반복문을 통해 데이터를 한 줄 한 줄 출력 하는 구문입니다.


<c:forEach> 문은 for 문과 동일한 동작을 수행합니다.


<c:forEach items="boardList" var="list">

....

</c:forEach>


이 문장은 데이터베이스를 검색해 넘겨 받은 boardList 를 list 라는 이름으로 순차적으로 실행을 시키게 됩니다.



이제 서버를 구동시키고

웹 브라우저 주소창에 아래와 같이 입력합니다.

localhost:8080/web/board/getBoardList

그럼 아래와 같은 화면을 볼 수 있습니다.





추가 작업

여기서 한 가지 더 작업을 해 보려고 합니다.

주소에 있는 web 이라는 프로젝트명을 빼고 싶습니다.

STS의 서버를 일단 중지 시키고 서버명을 더블클릭 하면 아래와 같은 화면이 나타납니다.

이 화면에서 서버의 포트등을 비롯한 여러가지 정보를 수정할 수 있습니다.

주소와 관련된 부분을 처리하기 위해 우선 하단에 있는 [Modules] 탭을 선택합니다.



아래와 같은 창이 열리면 [Edit...] 버튼을 클릭 합니다.



아래와 같이 팝업 창이 열리면 우측의 그림처럼 "web" 이라는 글자를 삭제 하고 [OK]를 눌러 내용을 저장합니다.




다시 서버를 구동 시키고

변경된 주소를 입력해 봅니다.

변경된 주소는 아래와 같습니다.


localhost:8080/board/getBoardList


현재는 '데이터가 없습니다.' 라는 화면 밖에 볼 수 없습니다.

하지만 데이터를 제대로 출력하는지 확인하기 위해 임의의 데이터가 있어야 하는데

글쓰기 화면을 만들어 놓지 않은 상태 이므로

지난 포스팅에서 사용했던 BoardDAOTest의 '글저장 테스트 부분'을 몇 번 돌려

데이터를 몇 개 추가해 봅니다.

(DB에서 직접 insert 문을 실행 시켜도 상관 없습니다.)



현재 글 번호가 2번 부터 시작을 하고 있는데 그 이유는 지난 포스팅 BoardDAOTest를 하면서

글을 하나 저장하고 삭제하는 테스트를 진행 하였기 때문입니다.


테이블을 만들때 bid에 auto_increment 속성을 주었는데 이 속성은 글 번호를 자동으로 증가 시켜주면서 

현재 어디까지 번호를 증가 시켰는지를 내부적으로 기억하고 있습니다.

따라서 1번글을 삭제 후 다른 글을 저장한다 하더라도 글 번호가 1번이 아닌 그 다음부터 시작하게 됩니다.

이것을 초기화 하려면 아래의 명령을 실행해야 합니다.


ALTER TABLE tablename AUTO_INCREMENT = 1;



부트스트랩(Bootstrap)을 이용해 View 화면 꾸미기

개발자들에게 가장 어려운 작업중에 하나가 사이트 디자인과 관련된 부분인데

부트스트랩(bootstrap)을 이용하면 깔끔한 만들어 낼 수 있습니다.

아래의 부트스트랩 홈페이지에 가보시면 부트스트랩 소스코드를 다운로드 받거나 CDN 주소를 확인 할 수 있습니다.

부트스트랩 : https://getbootstrap.com/docs/4.2/getting-started/introduction/

현재 최신 버전은 4.2 버전입니다.


이 포스팅에서는 부트스트랩을 다운로드 받지 않고 CDN 방식을 이용할 예정입니다.

그리고 아래 주소의 부트스트랩 샘플을 이용해 저희 게시판 디자인을 꾸며볼 예정입니다.

부트스트랩 샘플 : https://getbootstrap.com/docs/4.2/examples/


우선 <head>와 </head> 사이에 부트스트랩의 CDN 경로를 추가합니다.

그리고 table 태그에 아래와 같이 클래스 태그와 속성을 추가 하고,

table를 div 태그로 감쌉니다.

붉은 글씨를 해놓은 부분이 추가를 한 부분입니다.


<head>

<meta charset="UTF-8">


<!-- Bootstrap CSS -->

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">


<title>board</title>

</head>
<body>
<article>

<div class="container">

<div class="table-responsive">

<table class="table table-striped table-sm">

....

</table>

</div>

</div>

</article>


화면을 새로고침하여 보면 아래와 같이 부트스트랩이 적용된 것을 확인 할 수 있습니다.





조금 아까보다는 뭔가 나아진 모습입니다.

하지만 전체적으로 너무 상단에 붙어 있으므로 조금 떨어 트리기 위해

아래와 같이 스타일 시트를 <head></head> 사이에 넣습니다.


<title>board</title>

<style>

body {

padding-top: 70px;

padding-bottom: 30px;

}

</style>

</head>


붉은색 부분이 추가한 스타일 시트 입니다.


마지막으로 추가할 내용은 글쓰기 페이지로 이동할 버튼을 추가하는 것입니다.

글쓰기 버튼은 <button> 태그를 이용해 만들고, 버튼이 추가될때 발생할 이벤트는 

jQuery를 이용해 처리 합니다.

따라서 jQuery도 페이지에 로딩 할 수 있도록 CDN 주소를 추가해 주도록 합니다.


button 추가

</table>

</div>

<div >

<button type="button" class="btn btn-sm btn-primary" id="btnWriteForm">글쓰기</button>

</div>

</div>

</article>

</body>

</html>


버튼 태그를 이용해 id 가 btnWriteForm 이라는 버튼을 만들었습니다.

앞의 class 에는 부트스트랩을 이용해 버튼의 모양을 바꿀 수 있습니다.

부트스트랩에서 제공하는 버튼의 종류는 아래 링크에서 확인 수 있습니다.

부트스트랩 버튼 디자인 샘플


jquery CDN의 추가 위치는

조금 위에서 추가 했던 부트스트랩 CDN 위에 추가 합니다.

<!-- jQuery -->

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>


<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">



button 이벤트 추가

버튼 클릭시 동작할 스크립트 이벤트는 <style>...</style> 태그 다음에 추가 합니다


<style>

body {

padding-top: 70px;

padding-bottom: 30px;

}

</style>

<script>

$(document).on('click', '#btnWriteForm', function(e){

e.preventDefault();

location.href = "${pageContext.request.contextPath}/board/boardForm";

});

</script>

<body>


$는 jQuery를 시작하는 명령어로

$(BOM요소) 와 같은 명령어로 각 요소에 접근 할 수 있습니다.

e.preventDefault(); 는 버튼 고유의 기능을 막는 명령어 입니다.


location.href 는 순수 자바스크립트 명령어로 뒤에 오는 주소로 페이지를 이동 시키는 명령어 입니다.

이제 페이지를 다시 확인해 보면 아래와 같은 모습으로 보입니다




[글쓰기] 버튼을 클릭하면 에러가 나게 됩니다.

아직 글쓰기 관련된 프로그램을 만들지 않았으니까요

다음 포스팅에서는 글쓰기 화면을 만들고 저장하는 부분까지 진행하도록 하겠습니다.



SQL 로그 출력하기

IDE 하단의 콘솔창에는 프로그램의 진행 상황이 실시간으로 출력이 됩니다. 프로그램 상에 에러가 있다면 에러메시지가 출력이 됩니다. 하지만 SQL문의 경우 에러가 아닌 이상 현재까지는 어떤 SQL이 어떻게 실행되는지 알 수 없습니다. 따라서 콘솔창에 현재 실행되는 SQL 문장이 출력 되도록 설정 하는 방법을 알아 봅니다.


가장 먼저 의존성 추가를 위해 pom.xml 파일을 열어 아래의 내용을 추가 합니다.


pom.xml 추가 내용

      <!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4 -->

<dependency>

    <groupId>org.bgee.log4jdbc-log4j2</groupId>

    <artifactId>log4jdbc-log4j2-jdbc4</artifactId>

    <version>1.16</version>

</dependency>



데이터베이스 접속 설정이 들어 있는 설정파일 (dataSource-context.xml)을 수정해야 합니다. 


dataSource-context.xml 수정

<!--dataSource 객체 설정 -->

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<!-- 아래의 기존 내용은 주석 처리 하거나 삭제 합니다.-->

<!--  <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />

        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mess?allowPublicKeyRetrieval=true&amp;useSSL=false&amp;serverTimezone=UTC" /> -->

        

        <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy" />

        <property name="url" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/mess?allowPublicKeyRetrieval=true&amp;useSSL=false&amp;serverTimezone=UTC" />

        <property name="username" value="mess"></property>

        <property name="password" value="mess"></property>

    </bean>  


붉은 색 부분이 수정된 부분입니다.



log4jdbc.log4j2.properties 파일을 새로 추가 해야 합니다.

위치는 log4j.xml이 있는 /src/main/resources 에 만들도록 합니다.




log4jdbc.log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator


파일의 내용은 위와 같이 딱 한 줄 입니다.



마지막으로 어떤 로그를 출력할지 log4j.xml에 속성을 추가 합니다.


log4j.xml 수정

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

<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">


<!-- Appenders -->

<appender name="console" class="org.apache.log4j.ConsoleAppender">

<param name="Target" value="System.out" />

<layout class="org.apache.log4j.PatternLayout">

<param name="ConversionPattern" value="%-5p: %c - %m%n" />

</layout>

</appender>

<!-- Application Loggers -->

<logger name="com.freehoon.web">

<level value="info" />

</logger>

<!-- 3rdparty Loggers -->

<logger name="org.springframework.core">

<level value="info" />

</logger>

<logger name="org.springframework.beans">

<level value="info" />

</logger>

<logger name="org.springframework.context">

<level value="info" />

</logger>


<logger name="org.springframework.web">

<level value="info" />

</logger>

<!-- SQL Logger -->

<logger name="jdbc.sqltiming" additivity="false">

<level value="warn" />

<appender-ref ref="console"/> 

</logger>

<logger name="jdbc.sqlonly" additivity="false"> 

<level value="info"/> 

<appender-ref ref="console"/> 

</logger>

<logger name="jdbc.audit" additivity="false"> 

<level value="warn"/>  

<appender-ref ref="console"/> 

</logger> 

    

<logger name="jdbc.resultset" additivity="false">

<level value="warn" />

<appender-ref ref="console"/> 

</logger>

     

<logger name="jdbc.resultsettable" additivity="false"> 

<level value="info"/>  

<appender-ref ref="console"/> 

</logger> 


<!-- Root Logger -->

<root>

<priority value="warn" />

<appender-ref ref="console" />

</root>

</log4j:configuration>



붉은색으로 표시한 부분이 로그를 출력하기 위해 추가된 부분입니다.

로그 이름과 레벨은 아래의 표를 참조하시면 됩니다.


조금 더 상세한 내용은 아래의 포스팅을 참고해 주세요

[JAVA/Spring] SQL 로그 출력하기


위의 설정 예는 일반적인 SQL문장(sqlonly)과 그 문장의 결과(resultsettable)를 로그에 출력 할 수 있도록 로그 레벨을 'INFO' 로 설정하였습니다.


위에 log4j.xml의 설정을 따르면 로그는 아래와 같이 출력 됩니다.













※ 포스팅에 오타나 잘못된 부분, 추가적으로 더 알고 싶은 부분이 있으면 댓글 주세요~


반응형

관련글 더보기