Study/spring

[spring] MyBatis를 사용하여 게시판 만들기

토기발 2022. 6. 2. 00:05

MyBatis

MyBatis는 흔히 SQL 매핑 프레임워크로 분류된다. JDBC 코드의 복잡하고 지루한 작업을 단축시킬 수 있다.

 

장점

  • 자동으로 Connection close() 기능
  • MyBatis 내부적으로 PreparedStatement 처리
  • #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리
  • 리턴 타입을 지정한느 경우 자동으로 객체 생성 및 ResultSet 처리
  • 기존의 SQL을 그대로 활용할 수 있음

출처: https://techhan.github.io/study/spring-07/

 

 

이전에 jsp로 프로젝트를 만들었을 때나 MyBatis를 배우기 전 스프링을 사용했을 때는 컨트롤러 파일을 기능마다 계속 만들어주어야 해서 복잡하고 보기도 불편했다. 

이러한 불편함을 해소하고 코드를 간단하게 만들어주는 MyBatis를 사용하여 다시 게시판을 만들어보기로 한다.

 

 

개인 공부용이기 때문에 틀린 부분이 있을 수 있습니다!

오류가 있다면 편하게 지적 부탁드립니다^_^~


 

 

먼저 mybatis 패키지를 만들고, 그 안에 db.properties 파일을 생성한다.

 

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:xe
username=mincho
password=mincho

드라이버, url, 오라클 이름과 패스워드를 기입한다.

 

 

다음으로 mybatis패키지에 Configuration.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>
	<properties resource="mybatis/db.properties" />
	
	<typeAliases>
		<typeAlias type="board.dto.BoardDTO" alias="boardDTO"/>
	</typeAliases>
	
	<environments default="development">
		 <environment id="development">
 			<transactionManager type="JDBC"/>
 			<dataSource type="POOLED">
 				<property name="driver" value="${driver}"/>
 				<property name="url" value="${url}"/>
 				<property name="username" value="${username}"/>
 				<property name="password" value="${password}"/>
 			</dataSource>
 		</environment>
 	</environments>
 	
	<mappers>
 		<mapper resource="mybatis/boardMapper.xml"/>
 	</mappers>
</configuration>

MyBatis 설정에 필요한 정보들을 입력한다.

코드에 작성한 대로 board.dto패키지에 BoardDTO파일을 작성하고, mybatis패키지에 boardMapper.xml을 생성한다.

 

 

 

boardMapper.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="mybatis.boardMapper"> 
	<select id="listBoard" resultType="boardDTO">
		select * from springboard order by num desc
	</select>
	
	<insert id="insertBoard" parameterType="boardDTO">
		insert into springboard values(springboard_seq.nextval, #{writer},#{email},#{subject},#{passwd},sysdate,0,#{content},#{ip})
	</insert>
	
	<select id="getBoard" parameterType="int" resultType="boardDTO">
		select * from springboard where num = #{num}
	</select>
	
	<update id="plusReadcount" parameterType="int">
		update springboard set readcount=readcount+1 where num = #{num}
	</update>
	
	<delete id="deleteBoard" parameterType="int">
		delete from springboard where num = #{num}
	</delete>
	
	<update id="updateBoard" parameterType="boardDTO">
		update springboard set writer=#{writer}, email=#{email}, subject=#{subject}, content=#{content} where num=#{num}
	</update>
</mapper>

 

리스트/삽입/글내용보기/조회수/삭제/수정 기능을 mapper에 작성했다.

<select> 태그의 id 속성 값은 메서드의 이름과 동일하게 작성해야 한다. <select> 태그의 경우 resultType 속성은 인터페이스에 선언된 메서드의 리턴 타입과 동일하게 작성한다.

 sql문에서 ? 로 표시되는 부분은 ? 대신 #{} 안에 넣어 표기한다.

 

 

 

 

BoardMapper.java

package mybatis;

import java.io.IOException;
import java.io.Reader;
import java.util.List;

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 board.dto.BoardDTO;

public class BoardMapper {
	private static SqlSessionFactory sqlMapper;
	
	static {
		try {
			String resource = "Configuration.xml";
			Reader reader = Resources.getResourceAsReader(resource);
			sqlMapper = new SqlSessionFactoryBuilder().build(reader);
		}catch(IOException e) {
			throw new RuntimeException("DB 연결 오류 발생!!" + e.getMessage());
		}
	}
	
	public static List<BoardDTO> listBoard(){
		SqlSession sqlSession = sqlMapper.openSession();
		try {
			List<BoardDTO> list = sqlSession.selectList("listBoard");
			return list;
		}finally {
			sqlSession.close();
		}
	}
	
	public static int insertBoard(BoardDTO dto) {
		SqlSession sqlSession = sqlMapper.openSession();
		try {
			int res = sqlSession.insert("insertBoard", dto);
			sqlSession.commit();
			return res;
		}finally {
			sqlSession.close();
		}
	}
	
	public static BoardDTO getBoard(int num, String mode){
		SqlSession sqlSession = sqlMapper.openSession();
		try {
			if (mode.equals("content")) {
				sqlSession.update("plusReadcount", num);
				sqlSession.commit();
			}
			BoardDTO dto = sqlSession.selectOne("getBoard", num);
			return dto;
		}finally {
			sqlSession.close();
		}
	}
	
	public static int deleteBoard(int num, String passwd) {
		BoardDTO dto = getBoard(num, "password");
		if (!dto.getPasswd().equals(passwd)) {
			return -1;
		}
		SqlSession sqlSession = sqlMapper.openSession();
		try {
			int res = sqlSession.delete("deleteBoard", num);
			sqlSession.commit();
			return res;
		}finally {
			sqlSession.close();
		}
	}
	
	public static int updateBoard(BoardDTO dto) {
		BoardDTO dto2 = getBoard(dto.getNum(), "password");
		if (!dto.getPasswd().equals(dto2.getPasswd())) {
			return -1;
		}
		SqlSession sqlSession = sqlMapper.openSession();
		try {
			int res = sqlSession.update("updateBoard", dto);
			sqlSession.commit();
			return res;
		}finally {
			sqlSession.close();
		}
	}
}

mybatis패키지에 BoardMapper.java를 생성하고, 각 기능별 메소드를 작성한다.

return전에 commit을 써야 수정사항이 제대로 반영된다.

 

 

 

BoardDAOImpl.java 

package board.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import board.dto.BoardDTO;

public class BoardDAOImpl implements BoardDAO{

	private JdbcTemplate jdbcTemplate;
	
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 
		this.jdbcTemplate =jdbcTemplate; 
	}

	class MyRowMapper implements RowMapper<BoardDTO>{
		@Override
		public BoardDTO mapRow(ResultSet rs, int arg1) throws SQLException {
			BoardDTO dto = new BoardDTO();
			dto.setNum(rs.getInt("num"));
			dto.setWriter(rs.getString("writer"));
			dto.setEmail(rs.getString("email"));
			dto.setSubject(rs.getString("subject"));
			dto.setPasswd(rs.getString("passwd"));
			dto.setReg_date(rs.getString("reg_date"));
			dto.setReadcount(rs.getInt("readcount"));
			dto.setContent(rs.getString("content"));
			dto.setIp(rs.getString("ip"));
			return dto;
		}
	}
	
	@Override
	public List<BoardDTO> listBoard() {
		String sql = "select * from springboard";
		MyRowMapper mapper = new MyRowMapper();
		List<BoardDTO> list = jdbcTemplate.query(sql, mapper);
		return list;
	}

	@Override
	public BoardDTO getBoard(int num, String mode) {
		if (mode.equals("content")) {
			jdbcTemplate.update("update springboard set readcount=readcount+1 where num=?", num);
		}
		String sql = "select * from springboard where num = ?";
		MyRowMapper mapper = new MyRowMapper();
		BoardDTO dto = jdbcTemplate.queryForObject(sql, mapper, num);
		return dto;
	}

	@Override
	public int insertBoard(BoardDTO dto) {
		String sql = "insert into springboard values(springboard_seq.nextval, ?,?,?,?,sysdate,0,?,?)";
		Object[] values = new Object[] {dto.getWriter(), dto.getEmail(), dto.getSubject(), dto.getPasswd(), dto.getContent(), dto.getIp()};
		int res = jdbcTemplate.update(sql, values);
		return res;
	}

	private boolean isPassword(int num, String passwd) {
		BoardDTO dto = getBoard(num, "password");
		if (dto.getPasswd().equals(passwd)) return true;
		return false;
	}
	
	@Override
	public int deleteBoard(int num, String passwd) {
		if (isPassword(num, passwd)) {
			return jdbcTemplate.update("delete from springboard where num=?", num);
		}else {
			return -1;
		}
	}

	@Override
	public int updateBoard(BoardDTO dto) {
		if (isPassword(dto.getNum(), dto.getPasswd())) {
			String sql = "update springboard set writer=?, email=?, subject=?, content=? where num = ?";
			Object values[] = new Object[] {dto.getWriter(), dto.getEmail(), dto.getSubject(), dto.getContent(), dto.getNum()};
			return jdbcTemplate.update(sql, values);
		}else {
			return -1;
		}
	}

}

BoardDAOImpl은 BoardDAO인터페이스를 상속받아서 작성했다.

기존에 connection을 사용했을 때보다 코드가 많이 줄어들었다.

 

 

springboard-servlet.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 http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<context:annotation-config />
	
	<bean id="viewResolver"
			class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="WEB-INF/" />
		<property name="suffix" value=".jsp" />
	</bean>

	<bean id="dataSource" 
			class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
		<property name="username" value="mincho"/>
		<property name="password" value="mincho"/>
	</bean>
	
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<bean id="boardDAO" class="board.dao.BoardDAOImpl">
		<property name="jdbcTemplate" ref="jdbcTemplate"/>
	</bean>

bean에 db설정 및 jdbcTemplate, DAO를 등록한다.

<context:annotation-config /> 는

namespaces에서 context를 체크해야 설정할 수 있다.

 

 

 

 

BoardController.java 

package board.controller;

import java.util.*;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import board.dao.BoardDAO;
import board.dto.BoardDTO;
import mybatis.BoardMapper;

@Controller
public class BoardController {
	
	@RequestMapping("/list_board.do")
	public String listBoard(HttpServletRequest req) {
		//List<BoardDTO> list = boardDAO.listBoard();
		List<BoardDTO> list = BoardMapper.listBoard();
		req.setAttribute("listBoard", list);
		return "board/list";
	}
	
	@RequestMapping(value="write_board.do", method=RequestMethod.GET)
	public String writeFormBoard() {
		return "board/writeForm";
	}
	
	@RequestMapping(value="write_board.do", method=RequestMethod.POST)
	public String writeProBoard(HttpServletRequest req, BoardDTO dto) {
		dto.setIp(req.getRemoteAddr());
		//int res = boardDAO.insertBoard(dto);
		int res = BoardMapper.insertBoard(dto);
		if (res>0) {
			req.setAttribute("msg", "게시글 등록 성공!! 게시글 목록페이지로 이동합니다.");
			req.setAttribute("url", "list_board.do");
		}else {
			req.setAttribute("msg", "게시글 등록 실패!! 게시글 등록페이지로 이동합니다.");
			req.setAttribute("url", "write_board.do");
		}
		return "forward:message.jsp";
	}
	
	@RequestMapping("/content_board.do")
	public String contentBoard(HttpServletRequest req, @RequestParam int num) {
		//BoardDTO dto = boardDAO.getBoard(num, "content");
		BoardDTO dto = BoardMapper.getBoard(num, "content");
		req.setAttribute("getBoard", dto);
		return "board/content";
	}
	
	@RequestMapping(value="update_board.do", method=RequestMethod.GET)
	public String updateFormBoard(HttpServletRequest req, @RequestParam int num) {
		//BoardDTO dto = boardDAO.getBoard(num, "update");
		BoardDTO dto = BoardMapper.getBoard(num, "update");
		req.setAttribute("getBoard", dto);
		return "board/updateForm";
	}
	
	@RequestMapping(value="update_board.do", method=RequestMethod.POST)
	public String updateProBoard(HttpServletRequest req, BoardDTO dto) {
		int res = BoardMapper.updateBoard(dto);
		//int res = boardDAO.updateBoard(dto);
		if (res > 0) {
			req.setAttribute("msg", "게시글 수정 성공!! 게시글 목록페이지로 이동합니다.");
			req.setAttribute("url", "list_board.do");
		}else if (res < 0) {
			req.setAttribute("msg", "비밀번호가 틀렸습니다. 다시 입력해 주세요.");
			req.setAttribute("url", "update_board.do?num=" + dto.getNum());
		}else {
			req.setAttribute("msg", "게시글 수정 실패!! 게시글 보기페이지로 이동합니다.");
			req.setAttribute("url", "content_board.do?num=" + dto.getNum());
		}
		return "forward:message.jsp";
	}
	
	@RequestMapping(value="delete_board.do", method=RequestMethod.GET)
	public String deleteFormBoard() {
		return "board/deleteForm";
	}
	
	@RequestMapping(value="delete_board.do", method=RequestMethod.POST)
	public String deleteProBoard(HttpServletRequest req, 
								@RequestParam Map<String, String> params) {
		String num = params.get("num");
		String passwd = params.get("passwd");
		//int res = boardDAO.deleteBoard(Integer.parseInt(num), passwd);
		int res = BoardMapper.deleteBoard(Integer.parseInt(num), passwd);
		if (res > 0) {
			req.setAttribute("msg", "게시글 삭제 성공!! 게시글 목록페이지로 이동합니다.");
			req.setAttribute("url", "list_board.do");
		}else if (res < 0) {
			req.setAttribute("msg", "비밀번호가 틀렸습니다. 다시 입력해 주세요.");
			req.setAttribute("url", "delete_board.do?num=" + num);
		}else {
			req.setAttribute("msg", "게시글 삭제 실패!! 게시글 보기페이지로 이동합니다.");
			req.setAttribute("url", "content_board.do?num=" + num);
		}
		return "forward:message.jsp";
	}

}

이전에는 기능별로 파일을 모두 분리하여 작업했지만 이제는 코드 양이 줄어들어 한 파일에 작성한다.

기존에 DAO에 연결했던 부분들을 모두 주석처리하고 BoardMapper에 있는 메소드를 호출했다.

 


어노테이션과 파라미터 타입

 

 

@Controller

컨트롤러 클래스에 @Controller 어노테이션을 작성한다. 해당 어노테이션이 적용된 클래스는 "Controller"임을 나타나고, bean으로 등록되며 해당 클래스가 Controller로 사용됨을 Spring Framework에 알린다.

 

@RequestMapping
해당 어노테이션이 선언된 클래스의 모든 메소드가 하나의 요청에 대한 처리를 할경우 사용한다. 예를들어, "/student" 요청에 대해 공통적으로 처리해야될 클래스라는 것을 의미한다. 또한, 요청 url에 대해 해당 메소드에서 처리해야 되는 경우에도 사용된다.  이러한 @RequestMapping에 대한 모든 매핑 정보는 Spring에서 제공하는 HandlerMapping Class가 가지고 있다.

 

@RequestParam

HTTP GET 방식으로 전달되는 URL 의 parameter 값을 가져올때 사용되며, 변수 앞에 작성해주면 된다.

 

출처:  https://develop-log-sj.tistory.com/29

 


 

뷰 부분은 jsp때와 달라진 것이 없어서 생략..!