Chapter#01 : [Spring] IntelliJ를 사용한 Spring Project 생성( Gradle )
Chapter#02 : [Spring] MVC 패턴 및 MyBatis를 사용한 게시판 제작
Chapter#03 : [Spring] Log4j 설정 및 사용하기( log 파일 저장하기 )
Chapter#04 : [Spring] SpringSecurity를 이용한 사용자 인증 프로세스 구축
Chapter#05 : [Spring] JWT 토큰 발급 및 토큰 인증 받기
Chapter#06 : [Spring] Swagger 웹 서비스 RESTful API 문서 자동 생성
※ 스프링 MVC 패턴실행 순서
01. 클라이언트의 요청은 웹 서버(Tomcat, Jetty 등)로 전송됩니다.
02. 웹 서버는 요청을 받아서 web.xml 파일을 읽습니다.
03. web.xml 파일에서는 ContextLoaderListener를 등록하여 Spring의 ApplicationContext를 생성하고 초기화합니다.
04. ContextLoaderListener는 applicationContext.xml( context-*.xml ) 파일을 찾아서 읽습니다.
05. applicationContext.xml 파일에서는 빈(Bean) 객체의 정의와 의존성 관계를 설정합니다.
06. Spring 컨테이너는 applicationContext.xml에 정의된 빈 객체들을 생성하고 필요에 따라 의존성을 주입합니다.
07. DispatcherServlet은 클라이언트의 요청을 받습니다.
08. DispatcherServlet은 HandlerMapping을 사용하여 어떤 컨트롤러가 요청을 처리할지 결정합니다.
09. 선택된 컨트롤러는 요청을 처리하기 위해 Service 계층으로 작업을 위임합니다.
10. Service 계층은 비즈니스 로직을 수행하기 위해 필요한 데이터를 접근하기 위해 DAO에 요청을 전달합니다.
11. DAO는 데이터베이스나 다른 데이터 저장소에 접근하여 데이터를 읽거나 쓰기 위해 Mapper를 사용합니다.
12. Mapper는 실제로 데이터베이스와의 상호작용을 담당하고 SQL 쿼리를 실행하여 데이터를 검색, 삽입, 갱신 또는 삭제합니다.
13. DAO는 Mapper를 통해 반환된 데이터를 가공하거나 필요한 형식으로 변환하여 Service 계층으로 반환합니다.
14. Service 계층은 필요에 따라 데이터를 가공하고 비즈니스 로직을 수행한 뒤, 결과를 다시 컨트롤러로 반환합니다. 15. 컨트롤러는 최종적으로 응답을 생성하여 DispatcherServlet에 반환합니다.
16. DispatcherServlet은 응답을 받아서 클라이언트에게 전송합니다.
1. MariaDB 데이터 베이스에 테이블 생성
해당 포스팅에서는 마리아DB가 설치되어 있다는 것을 전제로진행한다.
MariaDB의 DataBase가 생성되어 있다면 해당 데이터베이스에 example_board 테이블을 생성한다.
CREATE TABLE example_board (
seq INT AUTO_INCREMENT PRIMARY KEY
, write_id VARCHAR(20) NOT NULL
, board_title VARCHAR(50) NOT NULL
, board_content TEXT NULL
, board_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP() NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
위 쿼리를 실행하여 `example_board` 테이블을 추가하여준다.
2. MyBaits 프레임워크 사용을 위한 패키지 추가
Spring 프로젝트에서 MyBatis 프레임워크를 사용하기 위해build.gradle을 열고 아래 패키지들을 추가하여 준다.
① JSTL 패키지 추가
// JSTL
implementation "javax.servlet:jstl:1.2"
② SrpingFramework : Spring JDBC 패키지 추가
// SpringFramework
implementation "org.springframework:spring-jdbc:5.2.22.RELEASE"
③ MariaDB 패키지 추가
// MariaDB
implementation "org.mariadb.jdbc:mariadb-java-client:2.5.4"
④ MyBatis 패키지 추가
// MyBatis
implementation "org.mybatis:mybatis:3.5.8"
implementation "org.mybatis:mybatis-spring:2.0.6"
⑤ Jackson 패키지 추가
// Jackson
implementation "com.fasterxml.jackson.core:jackson-core:2.11.4"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.11.4"
implementation "com.fasterxml.jackson.core:jackson-databind:2.11.4"
plugins {
id "java"
id "war"
}
apply plugin : "war"
group "org.example"
version "0.0.1-SNAPSHOT"
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.1"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.1"
// Servelt API
providedCompile "javax.servlet:servlet-api:2.5"
// JSTL
implementation "javax.servlet:jstl:1.2"
// SpringFramework
implementation "org.springframework:spring-aop:5.2.22.RELEASE"
implementation "org.springframework:spring-beans:5.2.22.RELEASE"
implementation "org.springframework:spring-context:5.2.22.RELEASE"
implementation "org.springframework:spring-core:5.2.22.RELEASE"
implementation "org.springframework:spring-expression:5.2.22.RELEASE"
implementation "org.springframework:spring-jcl:5.2.22.RELEASE"
implementation "org.springframework:spring-tx:5.2.22.RELEASE"
implementation "org.springframework:spring-web:5.2.22.RELEASE"
implementation "org.springframework:spring-webmvc:5.2.22.RELEASE"
implementation "org.springframework:spring-jdbc:5.2.22.RELEASE"
// MariaDB Connect
implementation "org.mariadb.jdbc:mariadb-java-client:2.5.4"
// MyBatis
implementation "org.mybatis:mybatis:3.5.8"
implementation "org.mybatis:mybatis-spring:2.0.6"
// Jackson
implementation "com.fasterxml.jackson.core:jackson-core:2.11.4"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.11.4"
implementation "com.fasterxml.jackson.core:jackson-databind:2.11.4"
}
test {
useJUnitPlatform()
}
3. Spring MVC - 모델( Model ) 계층 VO 사용
MVC (Model-View-Controller) 패턴에서 "VO"는 "Value Object"의 약어로,
어플리케이션 내에서 데이터를 전달하거나 저장하기 위해 사용되는 객체를 나타낸다.
Value Object는 보통 데이터를 나타내기 위한 단순한 객체로, 주로 읽기 전용이며 데이터의 불변성을 유지하기 위해 설계한다.
MVC 패턴에서 VO는 주로 모델(Model) 계층에서 사용되며,
비즈니스 로직이나 데이터베이스와의 상호 작용 없이 순수한 데이터만을 나타냅니다. VO는 데이터를 저장하고 전달하기 위한 용도로 사용되며,
뷰(View)와 컨트롤러(Controller)에서도 필요한 데이터를 전달하기 위해 사용될 수 있다.
※ MVC 모델 VO의 특징
① 불변성
VO는 보통 변경 불가능한 객체로 설계됩니다. 한 번 생성된 후에는 내부 데이터의 수정이 불가능하도록 만들어져 있다.
이는 데이터의 일관성을 유지하고 예상치 못한 부작용을 방지하기 위해 중요하다.
② 순수 데이터
VO는 주로 데이터만을 담는 역할을 하며, 비즈니스 로직을 가지고 있지 않는다.
이는 데이터와 로직의 분리를 유지하고 가독성을 높이기 위함이다.
③ 이관성
VO는 여러 계층 간의 데이터 전달을 쉽게 하기 위해 사용된다.
예를 들어, 데이터베이스에서 조회한 결과를 VO로 변환하여 비즈니스 로직에서 사용하거나, 비즈니스 로직의 결과를 VO로 만들어 뷰에서 화면에 표시할 수 있다.
④ 직렬화 가능성
VO는 직렬화(Serialization) 가능한 형태로 설계될 수 있다
이는 네트워크 통신이나 데이터 저장에 사용될 때 유용합니다.
VO는 간단한 데이터 전달을 위한 구조로, 주로 뷰와 모델, 컨트롤러 사이에서 데이터 교환을 위해 사용된다.
DataBase 테이블의 레코드나 API 응답 데이터 등을 VO로 표현하여 애플리케이션의 모듈 간에 데이터를 효율적으로 전달할 수 있다.
org.example.board 패키지에 service라는 패키지를 하나 추가한다.
org.example.board.service 패키지가 만들어 지면 service 패키지 안에 BoardVO.java 파일을 생성한다.
1) BoardVO - 멤버 변수 선언
BoardVO 클래스 파일이 생성되면 아래 내용을 추가하여 준다.
BoardVO.java - 멤버 변수 선언
package org.example.board.service;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class BoardVO {
private int seq;
private String writeId;
private String boardTitle;
private String boardContent;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date boardDate;
}
2) BoardVO - Getter & Setter 생성
BoardVO 클래스의 멤버 변수를 추가하였으면 클래스 내에 Getter, Setter를 추가한다.
BoardVO 클래스 내부의 하단 빈 공간을 선택하고 마우스를 우클릭 하여 Genserate → Getter and Setter를 선택한다.
Select Fields to Generate Getters and Setters 팝업창이 오픈되면 선언한 모든 멤버 변수를 선택하고 [ok] 버튼을 클릭한다.
그럼 BoardVO 클래스 내부에 위와같이 선언한 멤버 변수의 Getter와 Setter 메서드가 자동으로 추가된다.
BoardVO.java - Getter & Setter 메서드 추가
package org.example.board.service;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class BoardVO {
private int seq;
private String writeId;
private String boardTitle;
private String boardContent;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date boardDate;
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getWriteId() {
return writeId;
}
public void setWriteId(String writeId) {
this.writeId = writeId;
}
public String getBoardTitle() {
return boardTitle;
}
public void setBoardTitle(String boardTitle) {
this.boardTitle = boardTitle;
}
public String getBoardContent() {
return boardContent;
}
public void setBoardContent(String boardContent) {
this.boardContent = boardContent;
}
public Date getBoardDate() {
return boardDate;
}
public void setBoardDate(Date boardDate) {
this.boardDate = boardDate;
}
}
3) BoardVO - toString 메서드 추가
BoardVO 클래스에 Getter & Setter 메서드를 추가하였다면 다음으로 toString( ) 메서드도 추가하여 준다.
BoardVO 클래스 내부의 하단 빈 공간을 선택하고 마우스를 우클릭 하여 Genserate → toString( )을 선택한다.
Generate toString( ) 팝업창이 오픈되면 선언한 모든 멤버 변수를 선택하고 [ok] 버튼을 클릭한다.
그럼 BoardVO 클래스 내부에 위와같이 선언한 멤버 변수의 값을 출력하는 toString( ) 메서드가 생성된다.
BoardVO.java - toString( ) 메서드 추가
package org.example.board.service;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class BoardVO {
private int seq;
private String writeId;
private String boardTitle;
private String boardContent;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date boardDate;
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getWriteId() {
return writeId;
}
public void setWriteId(String writeId) {
this.writeId = writeId;
}
public String getBoardTitle() {
return boardTitle;
}
public void setBoardTitle(String boardTitle) {
this.boardTitle = boardTitle;
}
public String getBoardContent() {
return boardContent;
}
public void setBoardContent(String boardContent) {
this.boardContent = boardContent;
}
public Date getBoardDate() {
return boardDate;
}
public void setBoardDate(Date boardDate) {
this.boardDate = boardDate;
}
@Override
public String toString() {
return "BoardVO {"
+ "seq=\'" + seq + "\'"
+ ", writeId=\''" + writeId + "\'"
+ ", boardTitle=\''" + boardTitle + "\'"
+ ", boardContent=\''" + boardContent + "\'"
+ ", boardDate=\'" + boardDate + "\'"
+ " }";
}
}
3. Spring MVC - 컨틀롤러( Controller ) 수정
1) BoardController 클래스 수정하기
이전 포스팅에스 org.example.board.controller 패키지에 `BoardController.java` 클래스 파일을 생성하였다.
이제 BoardController 클래스 파일의 내용을 아래와 같이 수정한다.
BoardController.java
package org.example.board.controller;
import org.example.board.service.BoardService;
import org.example.board.service.BoardVO;
import org.example.utility.OutputPagination;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@Controller
@RequestMapping("/board")
public class BoardController {
@Resource(name="boardService")
private BoardService boardService;
@RequestMapping(value = "/boardDetail.do", method = RequestMethod.GET)
public String boardDetail(HttpServletRequest request, Model model) throws Exception {
BoardVO detailBoardVO = new BoardVO();
if(request.getParameter("seq").isEmpty() == false) {
detailBoardVO.setSeq(Integer.parseInt(request.getParameter("seq")));
BoardVO responseBoardVO = boardService.selectBoard(detailBoardVO);
model.addAttribute("writeId", responseBoardVO.getWriteId());
model.addAttribute("boardTitle", responseBoardVO.getBoardTitle());
model.addAttribute("boardContent", responseBoardVO.getBoardContent());
model.addAttribute("boardDate", responseBoardVO.getBoardDate());
}
return "board/boardDetail";
}
@RequestMapping(value = "/boardList.do")
public String boardList(HttpServletRequest request, Model model) throws Exception {
BoardVO boardVO = new BoardVO();
int totalRow = boardService.selectCountBoard(boardVO); // 해당 테이블의 전체 갯수
int choicePage = 0; // 선택 페이지
int startRow = 0; // MySQL LIMIT 시작점
int limitRow = 10; // MySQL LIMIT 종료점( 출력될 가로(row)의 개수를 지정 )
if(request.getParameter("page") != null && request.getParameter("page").length() > 0) {
choicePage = Integer.parseInt(request.getParameter("page"));
startRow = (choicePage - 1) * limitRow;
} else {
choicePage = 1;
startRow = 0;
}
OutputPagination outputPagination = new OutputPagination();
model.addAttribute("boardList", boardService.selectListBoard(boardVO, startRow, limitRow));
model.addAttribute("boardPagination", outputPagination.outputServletPagination(choicePage, limitRow, totalRow, "boardMovePage"));
return "board/boardList";
}
@RequestMapping(value = "/boardWrite.do")
public String boardWrite() throws Exception {
return "board/boardWrite";
}
@RequestMapping(value = "/boardRevise.do", method = RequestMethod.GET)
public String boardEdit(HttpServletRequest request, Model model) throws Exception {
BoardVO editBoardVO = new BoardVO();
editBoardVO.setSeq(Integer.parseInt(request.getParameter("seq")));
BoardVO responseBoardVO = boardService.selectBoard(editBoardVO);
model.addAttribute("seq", request.getParameter("seq"));
model.addAttribute("writeId", responseBoardVO.getWriteId());
model.addAttribute("boardTitle", responseBoardVO.getBoardTitle());
model.addAttribute("boardContent", responseBoardVO.getBoardContent());
model.addAttribute("boardDate", responseBoardVO.getBoardDate());
return "board/boardRevise";
}
@ResponseBody
@RequestMapping(value = "/boardWriteInsert.do", method = RequestMethod.POST)
public void boardWriteInsert(HttpServletRequest request, HttpServletResponse response) throws Exception {
BoardVO boardVO = new BoardVO();
if(request.getParameter("writeId").isEmpty() == false) {
boardVO.setWriteId(request.getParameter("writeId"));
}
if(request.getParameter("boardTitle").isEmpty() == false) {
boardVO.setBoardTitle(request.getParameter("boardTitle"));
}
if(request.getParameter("boardDate").isEmpty() == false) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
boardVO.setBoardDate(formatter.parse(request.getParameter("boardDate")));
}
if(request.getParameter("boardContent").isEmpty() == false) {
boardVO.setBoardContent(request.getParameter("boardContent"));
}
int resultNumber = boardService.insertBoard(boardVO);
if(resultNumber > 0) {
response.sendRedirect("./boardList.do");
} else {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<script type='text/javascript'>alert('해당 글을 등록하는데 실패하였습니다.');</script>");
out.flush();
}
}
@ResponseBody
@RequestMapping(value = "/boardReviseUpdate.do", method = RequestMethod.POST)
public void boardEditUpdate(HttpServletRequest request, HttpServletResponse response) throws Exception {
BoardVO boardVO = new BoardVO();
if(request.getParameter("seq").isEmpty() == false) {
boardVO.setSeq(Integer.parseInt(request.getParameter("seq")));
}
if(request.getParameter("writeId").isEmpty() == false) {
boardVO.setWriteId(request.getParameter("writeId"));
}
if(request.getParameter("boardTitle").isEmpty() == false) {
boardVO.setBoardTitle(request.getParameter("boardTitle"));
}
if(request.getParameter("boardContent").isEmpty() == false) {
boardVO.setBoardContent(request.getParameter("boardContent"));
}
if(request.getParameter("boardDate").isEmpty() == false) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
boardVO.setBoardDate(formatter.parse(request.getParameter("boardDate")));
}
int resultNumber = boardService.updateBoard(boardVO);
if(resultNumber > 0) {
response.sendRedirect("./boardDetail.do?seq=" + boardVO.getSeq());
} else {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<script type='text/javascript'>alert('해당 글을 수정하는데 실패하였습니다.');</script>");
out.flush();
}
}
@RequestMapping(value = "/boardDelete.do", method = RequestMethod.GET)
public void boardDelete(HttpServletRequest request, HttpServletResponse response) throws Exception {
BoardVO boardVO = new BoardVO();
if(request.getParameter("seq").isEmpty() == false) {
boardVO.setSeq(Integer.parseInt(request.getParameter("seq")));
}
int resultNumber = boardService.deleteBoard(boardVO);
if(resultNumber > 0) {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<script type='text/javascript'>");
out.println("alert('해당 글이 삭제되었습니다.');");
out.println("window.location.href='./boardList.do';");
out.println("</script>");
out.flush();
} else {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<script type='text/javascript'>alert('해당 글을 삭제하는데 실패하였습니다.');</script>");
out.flush();
}
}
}
2) 게시글 리스트 페이지 Paging 처리를 담당하는 OutputPagingnation 클래스 생성
게시글 리스트 페이지의 페이지 네비게이션 기능을 처리하는 OutputPagingnation 클래스를 생성한다.
src/main/java 경로의 org.example 패키지에 utility 패키지를 추가 생성한다.
utility 패키지가 생성되었다면 OutputPagination.java 클래스 파일을 utility 패키지 내에 생성한다.
OutputPagination 클래스 파일이 생성되면 페이지 네비게이션 생성하는 아래 코드를 추가하여 준다.
outputServletPagination 메서드를 생성하고 추가하여 준다.
OutputPagination.java
package org.example.utility;
public class OutputPagination {
public String outputServletPagination(int choicePage, int limitRow, int totalRow, String executionFunction) {
int totalPage = (int)((totalRow - 1) / limitRow);
int lastPage = 0;
int prevPage = (int)((choicePage - 1) / 5) * 5;
if((prevPage + 4) > totalPage) {
lastPage = totalPage;
} else {
lastPage = prevPage + 4;
}
String pagination = "<ul class='pagination'>";
if(prevPage == 0) {
pagination += "<li class='pagination-btn'><a class='pagination-link' href='javascript:;'>첫페이지</a></li>";
pagination += "<li class='pagination-btn'><a class='pagination-link' href='javascript:;'><<</a></li>";
} else {
pagination += String.format("<li class='pagination-btn'><a class='pagination-link' href='javascript:;' onClick='%s(%s);'>첫페이지</a></li>", executionFunction, 1);
pagination += String.format("<li class='pagination-btn'><a class='pagination-link' href='javascript:;' onClick='%s(%s);'><<</a></li>", executionFunction, prevPage);
}
// 페이징 INDEX를 출력한다.
for(int pageNum = prevPage; pageNum <= lastPage; pageNum++) {
int disPage = pageNum + 1;
if(disPage == choicePage) {
pagination += String.format("<li class='pagination-btn active'><a class='pagination-link' href='javascript:;'>%s</a></li>", disPage);
} else {
pagination += String.format("<li class='pagination-btn'><a class='pagination-link' href='javascript:;' onClick=\"%s(%s);\">%s</a></li>", executionFunction, disPage, disPage);
}
}
if(lastPage != totalPage) {
int nextPage = prevPage + 6;
pagination += String.format("<li class='pagination-btn'><a class='pagination-link' href='javascript:;' onClick='%s(%s);'>>></a></li>", executionFunction, nextPage);
pagination += String.format("<li class='pagination-btn'><a class='pagination-link' href='javascript:;' onClick='%s(%s);'>끝페이지</a></li>", executionFunction, totalPage + 1);
} else {
pagination += "<li class='pagination-btn'><a class='pagination-link' href='javascript:;'>>></a></li>";
pagination += "<li class='pagination-btn'><a class='pagination-link' href='javascript:;'>끝페이지</a></li>";
}
String returnNav = pagination + "</ul>";
return returnNav;
}
}
4. 비즈니스 로직을 처리하는 Service 구성
Spring Framework에서의 "Service"는 주로 비즈니스 로직을 처리하는 계층을 나타내는 역할을 한다.
비즈니스 로직은 어플리케이션의 핵심 기능이며, 데이터 처리, 계산, 검증 등과 같은 작업을 포함합니다.
Spring Framework에서는 비즈니스 로직을 분리하고 모듈화하기 위해 @Service 애노테이션을 사용하여 Service 클래스를 정의하게 된다.
※ Spring Framework의 @Service 애노테이션은 다음과 같은 역할
① 비즈니스 로직 분리
@Service 애노테이션을 사용하여 비즈니스 로직을 처리하는 클래스를 표시함으로써,
컨트롤러(Controller)나 데이터 액세스 계층(DAO)과 같은 다른 계층과 분리하여 관리한다.
② DI(Dependency Injection) 지원
Spring Framework는 @Service 애노테이션을 통해 Service 클래스를 빈(Bean)으로 등록하여 컨테이너가 관리하도록 한다.
이를 통해 의존성 주입(DI)을 적용하여 Service 클래스가 필요한 의존성을 자동으로 주입받을 수 있다.
③ 트랜잭션 관리
@Service 클래스에서는 트랜잭션 관련 설정을 적용할 수 있다.
Spring의 @Transactional 어노테이션을 사용하여 메서드 수준에서 트랜잭션을 설정하거나, XML 설정을 통해 트랜잭션 매니저를 구성하게 된다.
④ AOP(Aspect-Oriented Programming) 적용
@Service 클래스에서는 AOP를 활용하여 관점 지향 프로그래밍을 적용한다.
이를 통해 로깅, 보안 등과 같은 횡단 관심사를 분리하여 모듈화할 수 있다.
1) Service 인터페이스 생성
VO 클래스를 생성하면서 org.example.board.service 패키지를 생성하였다.
service 패키지 내부에 BoardService.java 인터페이스 파일을 생성한다.
BoardService 인터페이스가 생성되면 아래와 같이 코드를 작성하여 준다.
BoardService.java
package org.example.board.service;
import java.util.List;
public interface BoardService {
/* @brief 사용자 정보 등록*/
BoardVO selectBoard(BoardVO boardVo) throws Exception;
int selectCountBoard(BoardVO boardVo) throws Exception;
List<BoardVO> selectListBoard(BoardVO boardVo, int startRow, int limitRow) throws Exception;
/* @brief 사용자 정보 등록*/
int insertBoard(BoardVO boardVo) throws Exception;
/* @brief 사용자 정보 수정 */
int updateBoard(BoardVO boardVo) throws Exception;
/* @brief 사용자 삭제 */
int deleteBoard(BoardVO boardVo) throws Exception;
}
2) ServiceImpl 클래스 생성
ServiceImpl은 Spring Framework에서 비즈니스 로직을 구현하는 데 사용되는 클래스의 네이밍 컨벤션 중 하나이다.
이 컨벤션은 주로 @Service 애노테이션과 함께 사용되며, 비즈니스 로직을 실제로 구현하는 클래스의 이름을
**ServiceImpl과 같이 지정하는 방식을 의미한다.
※ Spring에서 ServiceImpl을 클래스 이름에 사용하는 목적
① 네이밍 구분
어플리케이션 내에서 여러 계층의 클래스가 사용될 수 있는데, ServiceImpl이라는 접미사를 사용하여 비즈니스 로직을 구현하는 클래스임을 명시적으로 나타낸다.
이는 코드의 가독성을 높이고 클래스의 역할을 쉽게 파악할 수 있도록 도움을 준다.
② 인터페이스 구현
대부분의 경우 Spring에서는 인터페이스와 그 구현체를 사용하여 비즈니스 로직을 분리한다.
이때 SomethingService라는 인터페이스와 이를 구현한 SomethingServiceImpl 클래스를 사용하는 것이 일반적이다.
이는 인터페이스와 구현체 간의 관계를 명확하게 나타내기 위함이다.
org.example.board.service에 impl 패키지를 추가한다.
org.example.board.service.impl 패키지가 생성되면 BoardServiceImpl.java 클래스 파일을 생성한다.
BoardServiceImpl 클래스가 생성되면 아래 코드를 추가하여 준다.
BoardServiceImpl.java
package org.example.board.service.impl;
import org.example.board.service.BoardDAO;
import org.example.board.service.BoardService;
import org.example.board.service.BoardVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service("boardService")
@Transactional
public class BoardServiceImpl implements BoardService {
@Resource(name="boardDaoMyBatis")
private BoardDAO boardDAO;
public BoardVO selectBoard(BoardVO boardVO) throws Exception {
return boardDAO.selectBoard(boardVO);
}
public int selectCountBoard(BoardVO boardVO) throws Exception {
return boardDAO.selectCountBoard(boardVO);
}
public List<BoardVO> selectListBoard(BoardVO boardVO, int startRow, int limitRow) throws Exception {
return boardDAO.selectListBoard(boardVO, startRow, limitRow);
}
public int insertBoard(BoardVO boardVO) throws Exception {
return boardDAO.insertBoard(boardVO);
}
public int updateBoard(BoardVO boardVO) throws Exception {
return boardDAO.updateBoard(boardVO);
}
public int deleteBoard(BoardVO boardVO) throws Exception {
return boardDAO.deleteBoard(boardVO);
}
}
위의 코드에서 BoardServiceImpl 클래스는 @Service 어노테이션을 사용하여 스프링 빈으로 등록하였다.
이렇게 함으로써 비즈니스 로직을 분리하고 의존성 주입( DI )을 활용할 수 있게 된다.
5. Data Access 로직을 분리하여 관리하는 DAO 구성
Spring Framework에서의 DAO 계층은 데이터 액세스와 관련된 로직을 분리하여 관리하는 역할을 수행한다.
"DAO"는 "Data Access Object"의 약어로, 데이터베이스나 다른 영구 저장소와의 상호 작용을 처리하는 역할을 담당한다.
DAO는 비즈니스 로직과 데이터 액세스 로직의 분리를 통해 코드의 모듈화와 유지보수성을 개선한다.
1) DAO 인터페이스 생성
org.example.board.service 패키지에 BoardDAO.java 인터페이스 파일을 생성한다.
BoardDAO 인터페이스 파일이 생성되면 아래와 같이 CRUD 작업을 수행하는 코드를 작성하여 준다.
BoardDAO.java
package org.example.board.service;
import java.util.List;
public interface BoardDAO {
BoardVO selectBoard(BoardVO boardVO) throws Exception;
int selectCountBoard(BoardVO boardVO) throws Exception;
List<BoardVO> selectListBoard(BoardVO boardVO, int startRow, int limitRow) throws Exception;
int insertBoard(BoardVO boardVO) throws Exception;
int updateBoard(BoardVO boardVO) throws Exception;
int deleteBoard(BoardVO boardVO) throws Exception;
}
2) DAOMyBatis 클래스 생성
Spring Framework에서는 DAO 계층을 구현하는데 다양한 방식을 지원하는데
@Repository 어노테이션을 통해 DAO 클래스를 빈으로 등록하여 Spring 컨테이너에서 관리할 수 있습다.
DAO 클래스는 DataBase 접근하고 조작하는 작업을 수행하기위해 MyBatis와 같은 Data Access 기술과 통합하여 사용한다.
※ DAO 사용의 장점
① 로직의 분리
비즈니스 로직과 데이터 액세스 로직을 분리하여 각각의 계층을 관리하므로 코드의 모듈화와 유지보수성이 개선된다.
② 테스트 용이성
DAO 계층은 데이터베이스 액세스와 관련된 로직을 캡슐화하므로 테스트를 용이하게 만들어 준다.
가짜 데이터나 메모리 내 데이터베이스를 사용하여 테스트할 수 있게된다.
③ 데이터베이스 추상화
Spring의 데이터베이스 액세스 기술을 활용하면 데이터베이스에 종속적인 코드를 최소화하고, 여러 데이터베이스를 동시에 지원한다.
④ 트랜잭션 관리
Spring의 트랜잭션 관리 기능을 활용하여 데이터베이스 트랜잭션을 쉽게 관리할 수 있다.
요약하자면, Spring Framework의 DAO 계층은 데이터 액세스 로직을 분리하여 코드의 모듈화와 유지보수성을 높여준다.
사용자 입장에서는 데이터베이스 액세스 기술과의 통합을 통해 CRUD등의 작업을 보다 편리하게 처리할 수 있게된다.
org.example.board.service.impl 패키지에 BoardDAOMyBatis.java 클래스 파일을 생성하여 준다.
BoardDAOMyBatis 클래스 파일이 생성되면 아래와 같이 코드를 추가하여 준다.
BoardDAOMyBatis.java
package org.example.board.service.impl;
import org.example.board.service.BoardDAO;
import org.example.board.service.BoardVO;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
@Repository("boardDaoMyBatis")
public class BoardDAOMyBatis implements BoardDAO {
@Resource(name="boardMapper")
private BoardMapper boardMapper;
public BoardDAOMyBatis() {
System.out.println("===> BoardDAOMyBatis 생성");
}
public BoardVO selectBoard(BoardVO boardVO) throws Exception {
System.out.println("===> MyBatis로 selectBoard() 기능 처리");
return (BoardVO) boardMapper.selectBoard(boardVO);
}
public int selectCountBoard(BoardVO boardVO) throws Exception {
System.out.println("===> MyBatis로 selectCountBoard() 기능 처리");
return boardMapper.selectCountBoard(boardVO);
}
public List<BoardVO> selectListBoard(BoardVO boardVO, int startRow, int limitRow) throws Exception {
System.out.println("===> MyBatis로 selectListBoard() 기능 처리");
return boardMapper.selectListBoard(boardVO, startRow, limitRow);
}
public int insertBoard(BoardVO boardVO) throws Exception {
System.out.println("===> MyBatis로 insertBoard() 기능 처리");
return boardMapper.insertBoard(boardVO);
}
public int updateBoard(BoardVO boardVO) throws Exception {
System.out.println("===> MyBatis로 updateBoard() 기능 처리");
return boardMapper.updateBoard(boardVO);
}
public int deleteBoard(BoardVO boardVO) throws Exception {
System.out.println("===> MyBatis로 deleteBoard() 기능 처리");
return boardMapper.deleteBoard(boardVO);
}
}
위의 코드에서 @Repository 어노테이션을 사용하여 Spring의 빈으로 등록한다.
5. Query를 호출하고 DataBase와 상호작용하는 Mapper 객체 생성
Spring Framework와 MyBatis를 함께 사용할 때 MyBatis의 SQL 매핑과 관련된 작업을 수행하는 객체를 "Mapper" 또는 "Mapper Interface"라고 하는데
Mapper Interface 정의
MyBatis에서 Mapper 객체를 정의하기 위해 인터페이스를 생성한다.
이 인터페이스는 데이터베이스와 상호 작용할 메서드를 정의하는데, 각 메서드는 SQL 문을 호출하거나 매개 변수를 전달하고 결과를 반환하는 역할을 수행한다.
org.example.board.service.impl 패키지 내부에 BoardMapper.java 인터페이스 파일을 생성한다.
BoardMapper 인터페이스 파일이 생성되면 아래와 같이 코드를 추가하여 준다.
BoardMapper.java
package org.example.board.service.impl;
import org.example.board.service.BoardVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface BoardMapper {
BoardVO selectBoard(BoardVO boardVO) throws Exception;
int selectCountBoard(BoardVO boardVO) throws Exception;
List<BoardVO> selectListBoard(BoardVO boardVO, @Param("startRow") int startRow, @Param("limitRow") int limitRow) throws Exception;
int insertBoard(BoardVO boardVO) throws Exception;
int updateBoard(BoardVO boardVO) throws Exception;
int deleteBoard(BoardVO boardVO) throws Exception;
}
6. DataBase Acces 설정
1) DataBase 접속 정보를 가지는 properties 파일 생성
src/main/resources 디렉토리에 config 디렉토리를 생성한다.
config 디렉토리가 추가되면 database.properties 파일을 생성한다.
database.properties 파일이 생성되면 아래와 같이 DataBase 접속정보를 추가하여 준다.
database.properties
jdbc.drvier = org.mariadb.jdbc.Driver
jdbc.url = jdbc:mariadb://HOST_주소:포트번호/데이터베이스_이름
jdbc.username = 사용자_계정
jdbc.password = 사용자_비밀번호
2) DataSource와 설정정보를 포함하는 XML 파일 설정
context-datasource.xml은 Spring Framework 기반의 어플리케이션에서 데이터 소스와 관련된 설정정보를 포함하는 XML 파일이다.
데이터 소스는 데이터베이스와의 연결을 관리하고 데이터 베이스와의 상호 작용을 가능하게 해주는 중요한 요소이다.
context-datasource.xml 파일은 데이터베이스 연결 정보를 설정하고 데이터 소스를 빈으로 등록하는 역할을 한다.
이 파일은 Spring의 DataSource 인터페이스를 구현한 구체적인 데이터 소스 객체를 설정하고,
데이터베이스 연결 정보, 풀링 옵션, 드라이버 클래스, 연결 URL 등을 지정하는데 사용됩니다.
src/main/resources/context 디렉토리에 context-datasource.xml 파일을 생성한다.
context-datasource.xml 파일이 생성되면 아래와 같이 DataBase 접속을 위한 설정을 작성한다.
context-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DataSource 설정 -->
<context:property-placeholder location="classpath:config/database.properties"></context:property-placeholder>
<!-- MariaDB DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.drvier}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
위 코드는 MariaDB 데이터 베이스에 대한 데이터 소스를 설정할 수 있다.
DriverManagerDataSource를 사용하여 데이터 소스를 사용하여 데이터 소스를 정의하고, 연결 정보를 제공하고 있다.
3) Spring Framework 어플리케이션에서 MyBatis 연동하는 XML 파일 설정
context-mapper.xml은 Spring Framework 기반의 애플리케이션에서 MyBatis와 관련된 설정 정보를 포함하는 XML 파일이다.
MyBatis는 SQL 매핑과 데이터베이스 연동을 위한 프레임워크로서, context-mapper.xml 파일은 MyBatis의 SQL 매핑과 관련된 설정을 정의하고 관리하는 역할을 수행한다.
context-mapper.xml 파일은 MyBatis의 Mapper XML 파일들을 매핑하고, MyBatis 설정을 설정한다.
src/main/resources/context 디렉토리에 context-mapper.xml 파일을 생성한다.
context-mapper.xml 파일이 생성되면 아래와 같이 MyBatis와 Mapper 객체를 연결하는 코드를 추가한다.
context-mapper.xml
<?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">
<!-- Spring과 MyBatis 연동 설정 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 데이터베이스 연결을 위한 데이터 소스를 지정합니다. -->
<property name="dataSource" ref="dataSource"></property>
<!-- MyBatis의 설정 파일(sql-mapper-config.xml)의 위치를 지정합니다. -->
<property name="configLocation" value="classpath:/sqlmap/sql-mapper-config.xml"></property>
<!-- Mapper XML 파일의 위치를 지정 -->
<property name="mapperLocations" value="classpath:sqlmap/mappings/*-mapping.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.example.*.service.impl"></property>
</bean>
</beans>
위의 코드는 MyBatis와 Spring 통합하기위해 sqlSession 등의 설정을 사용한다.
org.example.*.service.impl 패키지 경로의 Mapper 객체를 스캔하는 역할을 수행하며
이를 통해 MyBatis의 SQL 매핑과 DataBase 연동 설정을 간편하게 관리할 수 있다.
4) Spring Framework와 MyBatis를 통합하는 Class
SqlSessionFactoryBean은 Spring Framework에서 MyBatis와의 통합을 위해 제공되는 클래스로서,
MyBatis의 SqlSessionFactory를 생성하고 구성하기 위해 사용된다.
SqlSessionFactory는 MyBatis의 핵심 인터페이스로, 데이터베이스와의 연결을 관리하고 SQL 매핑을 수행하는 역할을 한다.
SqlSessionFactoryBean 클래스는 MyBatis와 Spring을 함께 사용할 때 SqlSessionFactory를 생성하는데 사용되며,
다양한 설정을 지원하여 SqlSessionFactory의 생성과 초기화를 간편하게 수행할 수 있도록 도와준다.
org.example.utility 패키지 경로에 SqlSessionFactroyBean.java 클래스 파일을 생성한다.
SqlSessionFactroyBean 클래스 파일을 생성한다.
SqlSessionFactoryBean.java
package org.example.utility;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
public class SqlSessionFactoryBean {
private static SqlSessionFactory sessionFactory = null;
static {
try {
if(sessionFactory == null) {
Reader reader = Resources.getResourceAsReader("sql-mapper-config");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
} catch(Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSessionInstance() {
return sessionFactory.openSession();
}
}
SqlSessionFactoryBean 클래스는 Spring과 무관하게 MyBatis만을 사용할 때 자체적으로 SqlSessionFactory를 생성하고 SqlSession을 반환하는 역할을 한다.
코드의 내용을 살펴보면
① org.example.utility 패키지 내에 SqlSessionFactoryBean 클래스가 정의
② private static SqlSessionFactory sessionFactory는 싱글톤 패턴을 사용하여 한 번만 SqlSessionFactory를 생성
③ static 블록 내에서 Resources.getResourceAsReader("sql-mapper-config")를 통해 sql-mapper-config 리소스의 리더를 가져온다.
④ SqlSessionFactoryBuilder().build(reader)를 통해 SqlSessionFactory를 생성합니다.
⑤ getSqlSessionInstance() 메서드는 생성된 SqlSessionFactory로부터 새로운 SqlSession을 반환합니다.
위 클래스를 사용하면 MyBatis의 SqlSessionFactory와 SqlSession을 손쉽게 생성하고 관리할 수 있다.
SqlSessionFactoryBean은 Spring과 무관하게 독립적으로 사용될 수 있으며,
Spring과 통합하여 사용하려면 Spring에서 제공하는 SqlSessionFactoryBean을 활용하면 된다.
5) VO 클래스의 객체를 SQL에 매핑하는 XML
sql-mapper-config.xml 파일은 MyBatis의 설정 파일 중 하나로, DataBase 연결 및 기타 설정을 포함한다.
sql-mapper-config.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>
<typeAliases>
<typeAlias type="org.example.board.service.BoardVO" alias="board"></typeAlias>
</typeAliases>
</configuration>
org.example.board.service.BoardVO 클래스에 대한 별칭을 board로 설정하여 이렇게 설정하면
BoardVO 클래스의 객체를 SQL 매핑 파일에서 board 라는 별칭으로 사용할 수 있다.
7. Mapper XML 매핑
위에서 정의한 Mapper 인터페이스 객체에 각 메서드에 대한 SQL 문을 Mapper XML 파일에 매핑한다.
Mapper XML 파일은 SQL 쿼리와 매개 변셔위 매핑, 결과 객체의 매핑 등을 정의한다.
src/main/resources/sqlmap 디렉토리에 mapping 디렉토리를 추가한다.
※ 파일명 mappings
mapping 디렉토리가 생성되면 board-mapping.xml 파일을 생성한다.
board-mapping.xml 파일이 생성되면 Mapper 객체와 연동할 Query문을 작성한다.
board-mapping.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="org.example.board.service.impl.BoardMapper">
<resultMap id="boardResult" type="board">
<id property="seq" column="seq"></id>
<result property="writeId" column="write_id"></result>
<result property="boardTitle" column="board_title"></result>
<result property="boardContent" column="board_Content"></result>
<result property="boardDate" column="board_date" jdbcType="TIMESTAMP" javaType="java.sql.Timestamp"></result>
</resultMap>
<select id="selectBoard" resultMap="boardResult">
SELECT seq, write_id, board_title, board_Content, board_date FROM example_board WHERE 1 = 1
<if test="seq!=null and !seq.equals('')">
AND seq = #{seq}
</if>
</select>
<select id="selectCountBoard" resultType="int">
SELECT COUNT(seq) AS boardCount FROM example_board WHERE 1 = 1
</select>
<select id="selectListBoard" parameterType="int" resultMap="boardResult">
SELECT seq, write_id, board_title, board_content, board_date
FROM example_board WHERE 1 = 1
ORDER BY seq DESC
LIMIT ${startRow}, ${limitRow}
</select>
<insert id="insertBoard" useGeneratedKeys="true" keyProperty="seq" parameterType="board">
INSERT into example_board(write_id, board_title, board_content, board_date)
VALUE(#{writeId}, #{boardTitle}, #{boardContent}, #{boardDate})
</insert>
<update id="updateBoard">
UPDATE example_board SET
board_title = #{boardTitle}
, board_content = #{boardContent}
, board_date = #{boardDate}
WHERE 1 = 1
AND seq = #{seq}
AND write_id = #{writeId}
</update>
<delete id="deleteBoard">
DELETE FROM example_board
WHERE 1 = 1
AND seq = #{seq}
</delete>
</mapper>
8. MVC 뷰( View ) - 게시판( JSP ) 제작
src/main/webapp/WEB-INF/views/ 디렉토리에 board 디렉토리를 추가한다.
1) 게시판 - 게시글 리스트 페이지 수정
Board 디렉토리가 생성되었다면 이전 포스팅에서 만들어둔 boardList.jsp 파일을 Board 디렉토리로 이동시킨다.
boardList.jsp 파일을 아래와 같이 수정하여 준다.
boardList.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<html>
<head>
<title>게시판 리스트</title>
</head>
<style type="text/css">
table, thead, tbody, tfoot { border:1px solid #000000;border-collapse:collapse; }
tfoot { text-align:right; }
th, td { border:1px solid #000000;padding:10px; }
tbody > tr > td { cursor:pointer;cursor:hand; }
tbody > tr > td:first-child { text-align:center; }
button { cursor:pointer;cursor:hand; }
ul.pagination { list-style:none;margin:0;padding:0; }
li.pagination-btn { margin-right:5px;margin-left:5px;border:0;float:left;}
li.active { font-weight:bold; }
a.pagination-link { color:#000000;text-decoration:none; }
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("btnWrite").addEventListener("click", function() {
window.location.href = "/board/boardWrite.do";
});
document.getElementsByClassName("")
});
function boardMovePage(page) {
window.location.href = "/board/boardList.do?page=" + page;
}
</script>
<body>
<h1>${headTitle}</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
</tr>
</thead>
<tbody>
<c:forEach var="board" items="${boardList}">
<tr onClick="window.location.href='/board/boardDetail.do?seq=${board.seq}'">
<td><c:out value="${board.seq}"></c:out></td>
<td><c:out value="${board.boardTitle}"></c:out></td>
<td><c:out value="${board.writeId}"></c:out></td>
<td><fmt:formatDate value="${board.boardDate}" pattern="yyyy-MM-dd"></fmt:formatDate></td>
</tr>
</c:forEach>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<button id="btnWrite" type="button">새 글쓰기</button>
</td>
</tr>
</tfoot>
</table>
<br/>
${boardPagination}
</body>
</html>
2) 게시판 - 게시글 상세 보기 페이지 제작
board 디렉토리에 boardDetail.jsp 파일을 추가하여 준다.
boardDetail.jsp 파일이 생성되면 게시글 상세정보를 노출할 페이지의 코드를 작성한다.
boardDetail.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>${boardTitle}</title>
</head>
<style type="text/css">
table, thead, tbody { border: 1px solid #000000;border-collapse:collapse; }
th, td { border:1px solid #000000;padding:10px; }
tfoot { text-align:right; }
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("btnList").addEventListener("click", function() {
history.back();
});
document.getElementById("btnErase").addEventListener("click", function() {
if(confirm("해당 글을 삭제하시겠습니까?") == true) {
window.location.href = "./boardDelete.do?seq=" + getParameter("seq");
}
});
document.getElementById("btnRevise").addEventListener("click", function() {
window.location.href = "./boardRevise.do?seq=" + getParameter("seq");
});
});
var getParameter = function(param) {
let returnValue;
let url = location.href;
let parameters = (url.slice(url.indexOf("?") + 1, url.length)).split("&");
for(let i = 0; i < parameters.length; i++) {
let varName = parameters[i].split("=")[0];
if(varName.toUpperCase() == param.toUpperCase()) {
returnValue = parameters[i].split("=")[1];
return decodeURIComponent(returnValue);
}
}
}
</script>
<body>
<table>
<tbody>
<tr>
<th>제목</th>
<td><c:out value="${boardTitle}"></c:out></td>
</tr>
<tr>
<th>작성자</th>
<td><c:out value="${writeId}"></c:out></td>
</tr>
<tr>
<th>작성일</th>
<td><fmt:formatDate value="${board.boardDate}" pattern="yyyy-MM-dd"></fmt:formatDate></td>
</tr>
<tr>
<th>내용</th>
<td><c:out value="${boardContent}"></c:out></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<button type="button" id="btnList">리스트</button>
<button type="button" id="btnErase">글삭제</button>
<button type="button" id="btnRevise">글수정</button>
</td>
</tr>
</tfoot>
</table>
</body>
</html>
3) 게시판 - 게시글 작성 페이지 제작
이제 게시글을 작성고 게시판에 추가하기위한 게시글 작성페이지를 제작해보자.
board 디렉토리에 boarWrite.jsp 파일을 추가한다.
boardWrite.jsp 파일이 생성되면 아래와 같이코드를 추가하여 준다.
boardWrite.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
<head>
<title>신규 글 등록</title>
</head>
<style type="text/css">
table, thead, tbody, tfoot { border:1px solid #000000;border-collapse:collapse; }
tfoot { text-align:right; }
th, td { border:1px solid #000000;padding:10px; }
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("btnList").addEventListener("click", function() {
history.back();
});
document.getElementById("btnRegistry").addEventListener("click", function() {
submitBoardRegistry();
});
});
function submitBoardRegistry() {
if(document.getElementsByName("boardTitle")[0].value.replace(/\s/gi, "") == "") {
alert("제목을 입력하지 않았습니다.\n제목을 입력해 주세요.");
document.getElementsByName("exampleTitle")[0].focus();
return false;
}
if(document.getElementsByName("boardDate")[0].value.replace(/\s/gi, "") == "") {
alert("날짜를 입력하지 않았습니다.\n날짜를 입력해 주세요.");
document.getElementsByName("boardDate")[0].focus();
return false;
}
if(document.getElementsByName("boardContent")[0].value.replace(/\s/gi, "") == "") {
alert("내용을 입력하지 않았습니다.\n내용을 입력해 주세요.");
document.getElementsByName("boardContent")[0].focus();
return false;
}
document.getElementById("formBoardWrite").method = "POST";
document.getElementById("formBoardWrite").action = "./boardWriteInsert.do";
document.getElementById("formBoardWrite").submit();
}
</script>
<body>
<h3>신규 글 등록</h3>
<form id="formBoardWrite">
<!-- 사용자 ID는 임시로 'user'를 고정으로 사용 -->
<input type="hidden" name="writeId" value="user"/>
<table>
<tbody>
<tr>
<th>제목</th>
<td>
<input type="text" name="boardTitle" value=""/>
</td>
</tr>
<tr>
<th>날짜</th>
<td>
<input type="date" name="boardDate" value="" required/>
</td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="boardContent"></textarea>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<button id="btnList" type="button">리스트</button>
<button id="btnRegistry" type="button">글등록</button>
</td>
</tr>
</tfoot>
</table>
</form>
</body>
</html>
4) 게시판 - 게시글 수정 페이지 제작
게시글을 작성하였다면 작성한 게시글을 수정 할 수 있는 기능도 필요하다.
board 디렉토리를 선택하고 boardRevise.jsp 파일을 추가한다.
boardRevise.jsp이 추가되었다면 게시글 수정을 위한 코드를 작성한다.
boardRevise.jsp 파일은 boardWrite.jsp의 내용을 수정하는 식으로 작업을 진행한다.
boardRevise.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
<head>
<title>${exampleTitle}</title>
</head>
<style type="text/css">
table, thead, tbody, tfoot { border:1px solid #000000;border-collapse:collapse; }
tfoot { text-align:right; }
th, td { border:1px solid #000000;padding:10px; }
.fakeDisabled { cursor:default;background-color:#F8F8F8;color:#545454;border-color:#D6D6D6; }
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("btnModify").addEventListener("click", function() {
submitBoardModify();
});
});
function submitBoardModify() {
if(document.getElementsByName("boardTitle")[0].value.replace(/\s/gi, "") == "") {
alert("제목을 입력하지 않았습니다.\n제목을 입력해 주세요.");
document.getElementsByName("boardTitle")[0].focus();
return false;
}
if(document.getElementsByName("boardDate")[0].value.replace(/\s/gi, "") == "") {
alert("날짜를 입력하지 않았습니다.\n날짜를 입력해 주세요.");
document.getElementsByName("boardDate")[0].focus();
return false;
}
document.getElementById("formBoardRevise").method = "POST";
document.getElementById("formBoardRevise").action = "./boardReviseUpdate.do";
document.getElementById("formBoardRevise").submit();
}
</script>
<body>
<h3>신규 글 등록</h3>
<form id="formBoardRevise">
<input type="hidden" name="seq" value="${seq}"/>
<table>
<tbody>
<tr>
<th>ID</th>
<td>
<input type="text" class="fakeDisabled" name="writeId" value="<c:out value="${writeId}"></c:out>" readonly/>
</td>
</tr>
<tr>
<th>제목</th>
<td>
<input type="text" name="boardTitle" value="<c:out value="${boardTitle}"></c:out>"/>
</td>
</tr>
<tr>
<th>날짜</th>
<td>
<input type="date" name="boardDate" value="<fmt:formatDate value="${board.boardDate}" pattern="yyyy-MM-dd"></fmt:formatDate>"/>
</td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="boardContent"><c:out value="${boardContent}"></c:out></textarea>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<button id="btnModify" type="button">글수정</button>
</td>
</tr>
</tfoot>
</table>
</form>
</body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:redirect url="/board/boardList.do"></c:redirect>
'Spring Web > Spring Framework' 카테고리의 다른 글
[Spring] SpringSecurity를 이용한 사용자 인증 프로세스 구축 (0) | 2023.06.13 |
---|---|
[Spring] Log4j 설정 및 사용하기(log 파일 저장하기) (0) | 2023.06.13 |
[Spring] IntelliJ를 사용한 Spring Project 생성(Gradle) (1) | 2023.06.13 |
[Spring] src/main/resources 경로 폴더 형태로 노출 (2) | 2023.03.30 |
[Spring] MyBatis를 사용한 DataBase 연동 - MySQL (0) | 2022.12.09 |