본문 바로가기
BACK-END/SPRING

[Spring]Spring-MyBatis를 이용해 게시판 만들기

by 썬키 2022. 2. 27.

오늘은 Spring과 MyBatis 라이브러리를 이용해 게시판을 만드는것을 배웠다.

 

우선 MyBatis의 역할에 대해 간단하게 정리하면 이렇다.

MyBatis는 ORM(Object Relational Mapping) 프로그램의 일종인데

이클립스에서의 JDBC와 같은 개념이라고 생각하면 된다.

 

Object(java의 VO객체)

Relational(관계형 데이터베이스)

Mapping(연결)

 

JDBC에서는 DAO 파일에 속하는 부분에서

실행 메서드, 예외처리, 연결, SQL문, 실행객체 생성 등등을 일일이 다 생성해줬어야 했는데

MyBatis를 이용하면 SQL문과 실행 메서드, 연결을 제외하곤 자동 생성해준다는 이점이 있다. 

 

MyBatis의 이해를 돕기 위해 3가지 xml 파일에 대한 설명이 필요하다.

 

 

 

 

첫번째로 web.xml은 MyBatis에 대한 경로를 설정하는 부분이다.

 

<web.xml>

	<!-- The definition of the Root Spring Container shared by all Servlets 
		and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

 

코드 블럭안의 <param-value>가 MyBatis 경로를 설정하는 부분이다.

 

 

<root-context.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"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
	<!-- HikariCP(Connection Pull) 설정 객체 생성 - Connection 미리 생성 -->
	<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
		<!-- property : setter를 이용한 DI 적용 -->
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
		<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="java00" />
		<property name="password" value="java00" />
	</bean>
	
	<!-- HikariCP로 데이터 가져오기(DataSource) 객체 생성 -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<!-- 설정부분은 위에  생성한 설정 객체 사용 -->
		<!-- constructor-arg : 생성자를 이용한 DI 적용 -->
		<constructor-arg ref="hikariConfig" />
	</bean>
	
	<!-- MyBatis-Spring : MyBatis와 Spring을 연결해 주는 객체 : dataSource에서 con을 가져오는 것으로 만들어야 한다. 객체 생성 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- property : setter를 이용한 DI 적용 -->
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<mybatis-spring:scan base-package="com.webjjang"/>
	
	<!-- Root Context: defines shared resources visible to all other web components -->
	<!-- @Controller, @Service, @Repository, @Component, @RestController - 자동생성하기 위해서 찾는 위치를 지정한다. 위치와 아래 위치를 다 찾아서 생성과 DI를 해준다. -->
	<!-- URL과 상관이 없는 객체 -->
	<context:component-scan base-package="com.webjjang"></context:component-scan>
</beans>

 root-context.xml은 DB에 Connection 해서 데이터를 가져오고 

URL과 상관없는 객체(@service, @component, @repository)를 생성하기 위한 XML파일이다.

DB 계정 정보를 잘 입력해주고, 컴포넌트 스캔 부분의 패키지명을 잘 입력해야 한다.

 

 

<servlet-context.xml>

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

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<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>
	
	<!-- URL과 상관이 있는 객체 
		 base-package로 설정된 위치의 아래 찾아보기 -->
	<context:component-scan base-package="com.webjjang" />
	
	
	
</beans:beans>

servlet-context.xml은 URL과 관련이 있는 객체(@Controller)를 만들기 위한 XML 파일이다.

여기 역시, 컴포넌트 스캔부분을 잘 입력해야 한다.

 

여기까지가 MyBatis와 관련된 XML 파일에 대한 설명이다.

 

이제, Spring-MyBatis를 이용해서 게시판을 만들어볼텐데 전체의 흐름은 이렇다.

< Mapper - Service - Controller - JSP>

 

- Mapper : Spring-MyBatis에서 Mapper라는 이름으로 사용하는 것은 두가지가 있다.

 

 

<BoardMapper.java>

첫번째로, 추상메서드를 선언하는 인터페이스

package com.webjjang.board.mapper;

import java.util.List;

import com.webjjang.board.vo.BoardVO;
import com.webjjang.util.PageObject;

public interface BoardMapper {

	// DAO에서 작성한 메서드 형식으로 만들어준다.
	// Interface만 만들어두면 MyBatis 라이브러리에서 DAO를 대신 만들어준다.
	// 주고 받는 데이터 타입 정의, SQLㆍ처리 명령문이 필요하다. → BoardMapper.xml
	
	// 1. 리스트
	public List<BoardVO> list(PageObject pageObject) throws Exception;
	
	// 1-1. 전체 데이터 개수
	public long getTotalRow(PageObject pageObject) throws Exception;  
	
	// 2. 보기
	public BoardVO view(long no) throws Exception;
	
	// 2-1. 조회수 1 증가
	public int increase(long no) throws Exception;
	
	// 3. 글쓰기
	public int write(BoardVO vo) throws Exception;
	
	// 4. 수정
	public int update(BoardVO vo) throws Exception;
	
	// 5. 삭제
	public int delete(long no)throws Exception;
}

 

 

<BoardMapper.xml>

두번째로, SQL 문을 작성하는 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="com.webjjang.board.mapper.BoardMapper">

	<!-- 	// 1. 리스트(id와 메소드 이름을 맞춰준다.) -->
	<select id="list" resultType="com.webjjang.board.vo.BoardVO">
		select no, title, writer, writeDate, hit
		from (
			select rownum rnum, no, title, writer, writeDate, hit
			from(
				select no, title, writer, writeDate, hit
				from board
				<include refid="search"/>
				order by no desc
			)
		)
		where rnum between #{startRow} and #{endRow}
	</select>
	
	<!-- 	// 1-1. 전체 데이터 개수 -->
	<select id="getTotalRow" resultType="Long">
		select count(*) from board
		<include refid="search"/>
	</select>
	<!-- 검색 조건 처리를 위한 태그 - 부분 태그 : SQL : 동적쿼리 작성 -->
	<sql id="search">
		<if test="word != null and word != ''.toString">
		 where
		 	<if test="key == 't'.toString()">
		 		title like '%' || #{word} || '%'
		 	</if>
		 	<if test="key == 'c'.toString()">
		 		content like '%' || #{word} || '%'
		 	</if>
		 	<if test="key == 'w'.toString()">
		 		writer like '%' || #{word} || '%'
		 	</if>
		 	<if test="key == 'tc'.toString()">
		 		title like '%' || #{word} || '%'
		 		or content like '%' || #{word} || '%'
		 	</if>
		 	<if test="key == 'tw'.toString()">
		 		title like '%' || #{word} || '%'
		 		or writer like '%' || #{word} | | '%'
		 	</if>
		 	<if test="key == 'cw'.toString()">
		 		content like '%' || #{word} || '%'
		 		or writer like '%' || #{word} || '%'
		 	</if>
		 	<if test="key == 'tcw'.toString()">
		 		title like '%' || #{word} || '%'
		 		or content like '%' || #{word} || '%'
		 		or writer like '%' || #{word} || '%'
		 	</if>
		</if>
	</sql>
	<!-- 	// 2. 보기 -->
	<select id="view" resultType="com.webjjang.board.vo.BoardVO">
		select no, title, content, writer, writeDate, hit
		from board
		where no = #{no}
	</select>
		
	<!-- 	// 2-1. 조회수 1 증가 -->
	<update id="increase">
		update board
		set hit = hit + 1
		where no = #{no}
	</update>	
	
	<!-- 	// 3. 글쓰기 -->
	<insert id="write">
		insert into board(no, title, content, writer)
		values(board_seq.nextval, #{title}, #{content}, #{writer})
	</insert>
		
	<!-- 	// 4. 수정 -->
	<update id="update">
		update board
		set title = #{title}, content = #{content}, writer = #{writer}
		where no = #{no}
	</update>
		
	<!-- 	// 5. 삭제 -->
	<delete id="delete">
		delete from board
		where no = #{no}
	</delete>	
</mapper>

 

- Service : 이클립스에서 진행했던것과 거의 유사하므로, 상기시켜서 작성하면 된다.

 

 

<BoardController.java>

- Controller : Controller는 URL를 Mapping 해서 페이지를 이동시켜주는 부분이라고 보면 된다.

package com.webjjang.board.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.webjjang.board.service.BoardService;
import com.webjjang.board.vo.BoardVO;
import com.webjjang.util.PageObject;


@Controller //어노테이션 사용
@RequestMapping("/board")
public class BoardController {
	
	// 의존성 자동 주입(Dependency Inject) → 자동으로 하도록 지정하는 어노테이션 : @Autowired, @Inject
	@Autowired
	private BoardService service;

	// 1. list(버튼 눌러서 이동하는 페이지는 get방식)
	// 처리 결과를 request에 담는 대신, Spring에서는 model에 넣어주면 request에 담기도록 프로그램 돼있다..
	// Parameter로 모델 객체를 전달 받아서 사용한다.
	@GetMapping("/list.do")
	public String list(@ModelAttribute PageObject pageObject, Model model) throws Exception{
		// 페이지가 1보다 작으면 1페이지로 세팅해준다.
		if(pageObject.getPage() < 1) pageObject.setPage(1);
		System.out.println("BoardController.list().pageObject - " + pageObject);
		model.addAttribute("list", service.list(pageObject));
		// /WEB-INF/views/ + board/list + .jsp → servlet-context.xml에 설정
		return "board/list";
	}
	// 2. view
	// 처리 결과를 request에 담는 대신, Spring에서는 model에 넣어주면 request에 담기도록 프로그램 돼있다..
	// Parameter로 모델 객체를 전달 받아서 사용한다.
	@GetMapping("/view.do")
	public String view(long no, int inc, Model model) throws Exception {
		System.out.println("BoardController.view().no.inc - " + no + "," + inc);
		
		BoardVO vo = service.view(no, inc);
		// 글 내용 중, 줄 바꿈 처리 \n → <br>
		vo.setContent(vo.getContent().replace("\n", "<br>"));
		model.addAttribute("vo", vo);
		return "board/view";
	}
	// 3. writeForm
	@GetMapping("/write.do")
	public String writeForm() throws Exception{
		System.out.println("BoardController.writeForm()");
		return "board/write";
	}
	// 3-1. write
	@PostMapping("/write.do")
	public String write(BoardVO vo, int perPageNum) throws Exception{
		System.out.println("BoardController.write().vo - " + vo);
		service.write(vo);
		return "redirect:list.do?page=1&perPageNum=" + perPageNum;
	}
	// 4. updateForm
	@GetMapping("/update.do")
	public String updateForm(long no, Model model) throws Exception{
		System.out.println("BoardController.updateForm().no - " + no);
		model.addAttribute("vo", service.view(no, 0));
		return "board/update";
	}
	// 4-1. update
	@PostMapping("/update.do")
	public String update(PageObject pageObject, BoardVO vo) throws Exception {
		System.out.println("BoardController.update().vo - " + vo);
		service.update(vo);
		return "redirect:view.do?no=" + vo.getNo() + "&inc=0"
		+ "&page="+ pageObject.getPage() + "&perPageNum=" + pageObject.getPerPageNum();
	}
	// 5. delete
	@GetMapping("/delete.do")
	public String delete(long no, int perPageNum) throws Exception{
		System.out.println("BoardController.delete().no - " + no);
		service.delete(no);
		return "redirect:list.do?perPageNum=" + perPageNum;
	}
	
}

 

 

 이런식으로 Mapper, Service, Controller를 만들고 페이지단에 속하는 JSP 파일을 만들어주면 게시판 제작은 끝이난다.

 

오늘은 게시판에 검색기능 넣는법, 한 페이지에 몇 개의 글을 정렬할건지에 대해서 배웠다.

필요한 사람이 있다면 아래의 코드를 참고하여 JSP에 검색 기능을 잘 넣어보길 바란다.

 

 

<list.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="pageNav" tagdir="/WEB-INF/tags"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 리스트</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
	src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<style type="text/css">
.dataRow:hover {
	background: #eee;
	cursor: pointer;
}
</style>
<script type="text/javascript">
	$(function() {
		$(".dataRow")
				.click(
						function() {
							var no = $(this).find(".no").text();
							location = "view.do?no="
									+ no
									+ "&inc=1&page=${pageObject.page}&perPageNum=${pageObject.perPageNum}";
						});
		// perPageNum 데이터의 변경 이벤트 처리 → jQuery에 대한 이벤트
		$("#perPageNumSelect").change(function() {
			// 			alert("값 변경");
			$("#perPageNumForm").submit()
		});

	});
</script>
</head>
<body>
	<div class="container">
		<h2 style="text-align: center">게시판 리스트</h2>
		<div class="row" style="margin-bottom: 5px;">
			<!-- 검색에 대한 div -->
			<div class="col-md-8">
				<form class="form-inline">
				<input type="hidden" name="perPageNum" value="${pageObject.perPageNum }">
					<div class="input-group">
						<select name="key" class="form-control">
							<option value="t" ${(pageObject.key == "t")?"selected":"" }>제목</option>
							<option value="c" ${(pageObject.key == "c")?"selected":"" }>내용</option>
							<option value="w" ${(pageObject.key == "w")?"selected":"" }>작성자</option>
							<option value="tc" ${(pageObject.key == "tc")?"selected":"" }>제목+내용</option>
							<option value="tw" ${(pageObject.key == "tw")?"selected":"" }>제목+작성자</option>
							<option value="cw" ${(pageObject.key == "cw")?"selected":"" }>내용+작성자</option>
							<option value="tcw" ${(pageObject.key == "tcw")?"selected":"" }>전체</option>
						</select>
					</div>
					<div class="input-group">
						<input type="text" class="form-control" placeholder="Search"
							name="word" value="${pageObject.word }">
						<div class="input-group-btn">
							<button class="btn btn-default" type="submit">
								<i class="glyphicon glyphicon-search"></i>
							</button>
						</div>
					</div>
				</form>
			</div>
			<!-- 한 페이지 당 보여주는 데이터 갯수 -->
			<div class="col-md-4 text-right">
				<form action="list.do" class="form-inline" id="perPageNumForm">
					<input type="hidden" name="page" value="1"> <input
						type="hidden" name="key" value="${pageObject.key }"> <input
						type="hidden" name="word" value="${pageObject.word }">
					<div class="form-group">
						<label>글 <select name="perPageNum"
							class="form-control" id="perPageNumSelect">
								<option ${(pageObject.perPageNum == 5)?"selected":"" }>5</option>
								<option ${(pageObject.perPageNum == 10)?"selected":"" }>10</option>
								<option ${(pageObject.perPageNum == 15)?"selected":"" }>15</option>
								<option ${(pageObject.perPageNum == 20)?"selected":"" }>20</option>
						</select>
						개씩 보기</label>
					</div>
				</form>
			</div>
		</div>
		<table class="table">
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>작성자</th>
				<th>작성일</th>
				<th>조회수</th>
			</tr>
			<c:forEach items="${list }" var="vo">
				<tr class="dataRow">
					<td class="no">${vo.no }</td>
					<td>${vo.title }</td>
					<td>${vo.writer }</td>
					<td><fmt:formatDate value="${vo.writeDate }"
							pattern="yyyy-MM-dd" /></td>
					<td>${vo.hit }</td>
				</tr>
			</c:forEach>
			<tr>
				<td colspan="5" align="center"><pageNav:pageNav
						listURI="list.do" pageObject="${pageObject }"
						query="&key=${pageObject.key }&word=${pageObject.word }" /></td>
			</tr>
			<tr>
				<td colspan="5" align="center"><a href="write.do?perPageNum=${pageObject.perPageNum }"
					class="btn btn-primary">글등록</a></td>
			</tr>
		</table>
	</div>
</body>
</html>

 

list.jsp

 

 

 

 

댓글