본문 바로가기
[ JAVA ]/JAVA Spring

[ Spring ] 게시판 01 - 목록/글쓰기 구현

by 환이s 2023. 4. 24.


프로젝트 구조

 

 

 

오늘은 Spring을 사용해서 게시판 목록/글쓰기 기능 구현을 해보겠습니다.

파일 구조는 위 사진과 같고, 다음은 테이블을 생성합니다.

 


SQL Table  생성

 

Board 테이블을 생성해서 연습용 데이터를 추가합니다. 

 

--게시판 테이블
create table board (
bno number not null, --게시물번호
title varchar2(200) not null, --제목
content clob, --본문
writer varchar2(50) not null, --작성자
regdate date default sysdate, --작성일자
viewcnt number default 0, --조회수
primary key(bno)
);

insert into board (bno,title,content,writer) values
(1,'제목','내용','kim');

select * from board;

 

데이터가 제대로 들어갔는지 확인해 보고 코드 작업 전에 작성자와 유저 아이디를 조인시켜주는데, 즉,  board 테이블과 이전 포스팅에서 만들었던 member 테이블을 조인 시켜줍니다.

 

select bno,title,writer,name,content,regdate,viewcnt
from board b, member m
where b.writer=m.userid
order by bno desc;

 

다음으로 DTO 생성해 줍니다.


BoardDTO

 

테이블 칼럼 조건과 동일한 타입 설정을 해주고 getter/setter , toString 까지만 생성합니다.

 

추가로 member 테이블과 조인시켜서 데이터를 가져올 수 있게 name 변수와 다음 포스팅을 위해 게시글 삭제했을 때,

화면에 표출 여부를 정할 수 있는 show , 첨부파일 변수를 배열로 생성합니다.

package com.example.spring.model.board.dto;

import java.util.Arrays;
import java.util.Date;

public class BoardDTO {
	private int bno; //게시물번호
	private String title; //제목
	private String content; //본문
	private String writer;  //작성자 id
	private Date regdate; //작성일자
	private int viewcnt;
	private String name; //작성자 이름
	private int cnt; // 댓글 갯수
	private String show; // 화면 표시 여부
	private String[] files; // 첨부파일 이름 배열
	
	public int getBno() {
		return bno;
	}
	public void setBno(int bno) {
		this.bno = bno;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public Date getRegdate() {
		return regdate;
	}
	public void setRegdate(Date regdate) {
		this.regdate = regdate;
	}
	public int getViewcnt() {
		return viewcnt;
	}
	public void setViewcnt(int viewcnt) {
		this.viewcnt = viewcnt;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getCnt() {
		return cnt;
	}
	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
	public String getShow() {
		return show;
	}
	public void setShow(String show) {
		this.show = show;
	}
	public String[] getFiles() {
		return files;
	}
	public void setFiles(String[] files) {
		this.files = files;
	}
	@Override
	public String toString() {
		return "BoardDTO [bno=" + bno + ", title=" + title + ", content=" + content + ", writer=" + writer
				+ ", regdate=" + regdate + ", viewcnt=" + viewcnt + ", name=" + name + ", cnt=" + cnt + ", show=" + show
				+ ", files=" + Arrays.toString(files) + "]";
	}
	
	
	
	
	
}

 

다음으론 DAO 인터페이스를 생성해 주는데, 미리 기능에 필요한 메서드를 생성해 줍니다.

 


BoardDAO, Impl

 

package com.example.spring.model.board.dao;

import java.util.List;

import com.example.spring.model.board.dto.BoardDTO;

public interface BoardDAO {
	public void deleteFile(String fullName); //첨부파일 삭제
	public List<String> getAttach(int bno); //첨부파일 정보
	public void addAttach(String fullName);// 첨부파일 저장
	public void updateAttach(String fullName, int bno); //첨부파일 수정
	public void create(BoardDTO dto) throws Exception; // 글쓰기 
	public void update(BoardDTO dto) throws Exception; //글수정
	public void delete(int bno) throws Exception; // 글 삭제
	public List<BoardDTO> listAll() throws Exception; // 목록
	public void increaseViewcnt(int bno) throws Exception; // 조회수 증가 처리
	public int countArticle() throws Exception; //레코드 개수 계산
	public BoardDTO read(int bno) throws Exception; //레코드 조회
	
	
}

 

이전 포스팅과 다르게 이번 포스팅은 순서를 역순으로 작업했습니다.

오늘 포스팅에 필요한 기능은 목록/ 글쓰기 기능이라서 각 메서드에 세션 처리를 해주고 mapper에 요청을 보내는 코드까지만 작성해 줍니다.

 

package com.example.spring.model.board.dao;

import java.util.List;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import com.example.spring.model.board.dto.BoardDTO;

@Repository
public class BoardDAOImpl implements BoardDAO {

	@Inject
	SqlSession sqlsession;
	
	@Override
	public void deleteFile(String fullName) {
		// TODO Auto-generated method stub

	}

	@Override
	public List<String> getAttach(int bno) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void addAttach(String fullName) {
		// TODO Auto-generated method stub

	}

	@Override
	public void updateAttach(String fullName, int bno) {
		// TODO Auto-generated method stub

	}

	@Override
	public void create(BoardDTO dto) throws Exception { //글쓰기 기능
		sqlsession.insert("board.insert", dto);

	}

	@Override
	public void update(BoardDTO dto) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void delete(int bno) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public List<BoardDTO> listAll() throws Exception { //목록 기능
		
		return sqlsession.selectList("board.listAll") ;
	}

	@Override
	public void increaseViewcnt(int bno) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public int countArticle() throws Exception {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public BoardDTO read(int bno) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

}

 

다음으론 ServiceDAODI 해줍니다.

Service 단에서는 DAO에서 생성한 메서드를 동일하게 Service단에 적용시키고, 간단하게 DAO와 의존 관계를 맺고, 요청을 보내는 역할만 해줍니다.


BoardService, Impl

 

package com.example.spring.service.board;

import java.util.List;

import com.example.spring.model.board.dto.BoardDTO;

public interface BoardService {
	
	public void deleteFile(String fullName); //첨부파일 삭제
	public List<String> getAttach(int bno); //첨부파일 정보
	public void addAttach(String fullName);// 첨부파일 저장
	public void updateAttach(String fullName, int bno); //첨부파일 수정
	public void create(BoardDTO dto) throws Exception; // 글쓰기 
	public void update(BoardDTO dto) throws Exception; //글수정
	public void delete(int bno) throws Exception; // 글 삭제
	public List<BoardDTO> listAll() throws Exception; // 목록
	public void increaseViewcnt(int bno) throws Exception; // 조회수 증가 처리
	public int countArticle() throws Exception; //레코드 개수 계산
	public BoardDTO read(int bno) throws Exception; //레코드 조회
	

}

 

Impl 클래스에서 DAO에 요청을 보내줍니다.

 

package com.example.spring.model.board.dao;

import java.util.List;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import com.example.spring.model.board.dto.BoardDTO;

@Repository
public class BoardDAOImpl implements BoardDAO {

	@Inject
	SqlSession sqlsession;
	
	@Override
	public void deleteFile(String fullName) {
		// TODO Auto-generated method stub

	}

	@Override
	public List<String> getAttach(int bno) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void addAttach(String fullName) {
		// TODO Auto-generated method stub

	}

	@Override
	public void updateAttach(String fullName, int bno) {
		// TODO Auto-generated method stub

	}

	@Override
	public void create(BoardDTO dto) throws Exception {
		sqlsession.insert("board.insert", dto); //글쓰기 기능

	}

	@Override
	public void update(BoardDTO dto) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void delete(int bno) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public List<BoardDTO> listAll() throws Exception {
		
		return sqlsession.selectList("board.listAll") ; // 목록 기능
	}

	@Override
	public void increaseViewcnt(int bno) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public int countArticle() throws Exception {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public BoardDTO read(int bno) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

}

 

여기까지 역순으로 코드 작업을 하고,

다음으로는 이전에 생성했던 메뉴 페이지에서 게시판 페이지를 요청할 수 있는 메뉴를 생성해 줍니다.

 


menu.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="path" value="${pageContext.request.contextPath}" />
<div style="text-align: center;"> 
 <a href="${path}">Home</a> |
 
 <c:if test="${sessionScope.admin_userid == null}">
	 <a href="${path}/memo/list.do">메모장</a> |
	  <a href="${path}/board/list.do">게시판</a> | <!-- 게시판 페이지 추가 url 요청 -->
	 <a href="${path}/upload/uploadForm">업로드 테스트</a> |
	  <a href="${path}/upload/uploadAjax">업로드(Ajax)</a> |
 </c:if>
 
 <c:if test="${sessionScope.userid != null && sessionScope.admin_userid == null}">
 	<a href="${path}/shop/cart/list.do">장바구니</a> |
 </c:if>
 
 <div style="text-align: right;">
  <c:choose>
   <c:when test="${sessionScope.userid == null}">
    <!-- 로그인하지 않은 상태 -->
  	<a href="${path}/member/login.do">로그인</a> |
  	<a href="${path}/admin/login.do">관리자 로그인</a> |
   </c:when>
   <c:otherwise>
    <!-- 로그인한 상태 -->
    ${sessionScope.name}님이 로그인중입니다.    
    <a href="${path}/member/logout.do">로그아웃</a>
   </c:otherwise>	
  </c:choose>
 </div>
</div>
<hr>

 

메뉴 페이지에서 Controllerurl 요청을 보냈으니, Controller를 생성해서 요청 처리를 받고, Service 단에 보내줍니다.


BoardController

 

위 작업에서 미리 Service, DAO 작업을 끝냈으니, Contrller 에서는 Service@Inject 시켜주고 각 기능 코드를 생성해서 요청받은 url 처리를 해줘서 Service 단에 보내줍니다.

 

package com.example.spring.controller.board;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.example.spring.model.board.dto.BoardDTO;
import com.example.spring.service.board.BoardService;

@Controller
@RequestMapping("board/*")
public class BoardController {

	@Inject
	BoardService boardService;
	
	//로깅을 위한 변수
	private static final Logger logger=
			LoggerFactory.getLogger(BoardController.class);
	
	@RequestMapping("list.do") // 세부 url
	public ModelAndView list() throws Exception{
		List<BoardDTO> list=boardService.listAll();
		logger.info(list.toString());
		
		ModelAndView mav = new ModelAndView();
		
		Map<String, Object> map = new HashMap<>();
		map.put("list", list); // map에 자료 저장
		map.put("count", list.size()); //레코드 갯수 파악
		mav.setViewName("board/list"); // 포워딩 뷰
		mav.addObject("map",map); //전달 데이터
		return mav;
	}
	@RequestMapping("write.do")
	public String write() {
		//글쓰기 폼 페이지 이동
		return "board/write";
	}
	@RequestMapping("insert.do") //@ModelAttribute 생략 가능
	public String insert(@ModelAttribute BoardDTO dto, HttpSession session) throws Exception{
		//이름이 없기 때문에 대신 세션에서 사용자 id 를 가져옴
		String writer = (String)session.getAttribute("userid");
		dto.setWriter(writer);
		//레코드 저장
		boardService.create(dto);
		//게시물 목록 이동
		return "redirect:/board/list.do";
	}
	
}

 

그럼 이제 포워딩 설정한 뷰단 페이지를 생성합니다.

 


view단

먼저 목록 페이지를 생성해 주고, 글쓰기 버튼을 구현해서 해당 페이지로 이동할 수 있는 url 요청을 Controller에서 요청받은 url 주소와 동일하게 세팅해줍니다.

 

< list.jsp >

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<%@ include file="../include/header.jsp" %>
<script type="text/javascript">
$(function() {
	$("#btnWrite").click(function() {
		location.href="${path}/board/write.do";
	});
});
</script>
</head>
<body>
<%@ include file="../include/menu.jsp" %>
<h2>게시판</h2>
<button type="button" id="btnWrite">글쓰기</button>
${map.count}개의 게시물이 있습니다.
<table border="1">
  <tr>
    <th>번호</th>
    <th>제목</th>
    <th>이름</th>
    <th>내용</th>
    <th>날짜</th>
    <th>조회수</th>
  </tr>
  <c:forEach var="row" items="${map.list }">
  <tr>
    <td>${row.bno}</td>
    <td>${row.title }</td>
    <td>${row.name }</td>
    <td>${row.content }</td>
    <td><fmt:formatDate value="${row.regdate }" pattern="yyyy-MM-dd HH:mm:ss"/></td>
    <td>${row.viewcnt }</td>
  </tr>
  </c:forEach>
</table>

</body>
</html>

 

글쓰기 페이지를 생성하기 전에 insert 페이지에서 첨부파일 처리를 해야 합니다.

js 페이지에서 파일 처리 코드를 구현해서 스크립트로 가져옵니다.

 

< js >

/**
 * 
 */
 
 function checkImageType(fileName) {
  var pattern=/jpg|gif|png|jpeg/;
  return fileName.match(pattern);
 }
 
 function getFileInfo(fullName) {
  var fileName, imgsrc, getLink, fileLink;
  if(checkImageType(fullName)) {//이미지 파일인 경우
   imgsrc="/spring02/upload/displayFile?fileName="+fullName;
   fileLink=fullName.substr(14); //14인덱스 ~끝
   var front=fullName.substr(0,12); //0~11
   var end=fullName.substr(14);
   getLink="/spring02/upload/displayFile?fileName="+front+end;
  }else{//이미지가 아닌 경우
   fileLink=fullName.substr(12);
   getLink="/spring02/upload/displayFile?fileName="+fullName;
  }
  // uuid_filename (+1은 _다음의 filename을 뽑아내기 위함)
  fileName=fileLink.substr(fileLink.indexOf("_")+1);
  //json으로 리턴
  return {fileName: fileName, imgsrc: imgsrc, getLink: getLink, fullName: fullName};
  
 }

 

js 코드 작업이 끝났다면 글쓰기 페이지에 스크립트 처리 해줍니다.

 

< write.jsp >

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<%@ include file="../include/header.jsp" %>
<script src="${path}/include/js/common.js"></script> <!-- js 파일  -->
<!-- ckeditor의 라이브러리 -->
<script src="${path}/ckeditor/ckeditor.js"></script>

<script type="text/javascript">
$(function() {
	$("#btnSave").click(function(){
		var str="";
		//uploadedList영역에 클래스 이름이 file인 히든타입의 태그를 각각 반복
		$("#uploadedList .file").each(function(i){
			console.log(i);
			//hidden태그 구성
			str += "<input type='hidden' name='files["+i+"]' value='" 
			+ $(this).val()+"'>";
		});
		//폼에 hidden 태그를 붙임
		$("#form1").append(str);
		document.form1.submit();
	});
	//파일을 마우스로 드래그해서 업로드 영역에 올릴때 파일이 열리는 기본효과 막는 처리
	$(".fileDrop").on("dragenter dragover", function(e) {
		e.preventDefault();
	});
	//마우스로 파일을 드롭할 때 파일이 열리는 기본 효과 막음
	$(".fileDrop").on("drop", function(e){
		e.preventDefault();
		//첫번째 첨부 파일
		var files=e.originalEvent.dataTransfer.files;
		var file=files[0];
		//폼 데이터에 첨부파일 추가
		var formData=new FormData();
		formData.append("file",file);
		$.ajax({
			url: "${path}/upload/uploadAjax",
			data: formData,
			dataType: "text",
			processData: false,
			contentType: false,
			type: "post",
			success: function(data){
				//data: 업로드한 파일 정보와 Http 상태 코드
				var fileInfo=getFileInfo(data);
			  console.log(fileInfo);
			  var html="<a href='"+fileInfo.getLink+"'>"+fileInfo.fileName+"</a><br>";
			  html += "<input type='hidden' class='file' value='"+fileInfo.fullName+"'>";
			  $("#uploadedList").append(html);
			}
		});
	});
});
</script>
<style type="text/css">
.fileDrop {
	width: 600px;
	height: 100px;
	border: 1px dotted gray;
	background-color: gray;
}
</style>
</head>
<body>
<%@ include file="../include/menu.jsp" %>
<h2>글쓰기</h2>
<form id="form1" name="form1" method="post" action="${path}/board/insert.do">
 <div>제목 <input name="title" id="title" size="80" placeholder="제목을 입력하세요"></div>
 <div style="width: 800px">
   내용 <textarea id="content" name="content" rows="3" cols="80" 
   placeholder="내용을 입력하세요"></textarea>
   <script>
   //ckeditor 적용
   CKEDITOR.replace("content", {
	   filebrowserUploadUrl: "${path}/imageUpload.do"
   });// ImageUploadController.java에서 처리
   </script>
 </div>
 <div> 첨부파일을 등록하세요
  <div class="fileDrop"></div>
  <div id="uploadedList"></div>
 </div>
 <div style="width: 700px; text-align: center;">
  <button type="button" id="btnSave">확인</button>
 </div>
</form>

</body>
</html>

 

마지막으로 mapper 파일에서 기능에 맞게 쿼리문 작성만 해주면 끝나는데, 간단한 유효성 검사를 해주기 위해 이전 포스팅에서 생성해 둔 interceptor 태그에 mapping 시켜줍니다. 

 

	<interceptor >
		<mapping path="/board/write.do"/>
		<mapping path="/board/insert.do"/>
   	</interceptor >

boardMapper.xml

 

목록 페이지 데이터를 가져올 땐 처음에 테이블 생성할 때 조인 시켜둔 select문을 그대로 가져와서 적어주고,

insert문에서는 들어오는 값을 함수 처리 해줍니다.

 

<!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace="board">
	<select id="listAll" resultType="com.example.spring.model.board.dto.BoardDTO">
	select bno,title,writer,name,content,regdate,viewcnt
	from board b, member m
	where b.writer=m.userid
	order by bno desc
	</select>
	<insert id="insert">
	insert into board (bno,title,content,writer) values
(seq_board.nextval,#{title},#{content},#{writer})
	</insert>
</mapper>

출력

 

< 목록 >

 

 

< interceptor >

 

< insert >

 

 

< list >

 


마치며

 

오늘은 간단한 게시판 목록/ 글쓰기 기능 구현에 대해서 알아보았습니다.

다음 포스팅에서 뵙겠습니다.

 

 

728x90