상세 컨텐츠

본문 제목

Spring 블로그 만들기 - 2. DB 셋팅 및 접속 테스트 Part.2

개발/Spring 블로그 만들기

by 똘똘이박사 2019. 1. 18. 09:46

본문



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



이전 포스팅에서

데이터베이스를 생성하고, 데이터베이스 접속 관련 스프링 설정과 접속 테스트 까지 해보았습니다.

이번 포스팅에서는 이전 포스팅에서 다 못 다루었던 내용을 다뤄 보려고 합니다.


  1. 스프링 환경 설정(web.xml 수정)

  2. 데이터베이스 만들기

  3. 데이터베이스 접속 관련 dependency 추가(pom.xml 수정)

  4. 데이터베이스 설정 파일 추가(dataSource-context.xml)

  5. 데이터베이스 접속 테스트(junit)

  6. 테이블 만들기(tbl_board)

  7. VO 만들기(BoardVO)

  8. SQL 쿼리 만들기 (boardMapper)

  9. DAO 만들기 (BoardDAO, BoardDAOImpl)

  10. DAO 테스트(junit)



블로그 만들기 - 2. DB 셋팅 및 접속 테스트 Part.2


테이블 만들기

게시물을 저장할 테이블을 생성합니다.

create table tbl_board(

  bid      int auto_increment comment '일련번호' primary key,
  cate_cd  varchar(20)   not null comment '게시글 카테고리',
  title    varchar(200)  not null comment '제목',
  content  text          not null comment '게시글',
  tag      varchar(1000) null comment '태그',
  view_cnt int default 0 not null comment '카운트',
  reg_id   varchar(45)   not null comment '작성자',
  reg_dt   date          not null comment '작성일',
  edit_dt  date          not null comment '수정일'

); 


이제 게시판 리스트 조회, 게시물 조회(상세내역보기)/등록/수정/삭제 등의 기능을 만들어 봅니다.


기능 구현은 DB 쪽 부터 시작해서 화면 쪽으로 진행해 나갈 예정인데

화면을 구현하기 전에 프로그램에서 정상적으로 DB와 데이터를 주고 받는지  

확인하는 테스트 작업을 한 번 더 진행할 예정입니다.

따라서 VO를 먼저 생성하고 DAO->Service->Controller->View 순서로 개발을 할 예정입니다.

(경험상 화면을 먼저 구현하는 것보다 DB 쪽 부터 구현 (DAO -> Service -> Controller -> View) 하는 것이

추가 작업이 덜 들더군요. 물론 개발 초기에 완벽하게 개발명세가 나와있다면 이야기가 다르지만... 현실은 그렇지 못합니다.

또한 Controller 부터 먼저 작성하면 Controller 에서  아직 만들지 않은 Service들을 호출을 해야 하는데

이때 아직 만들지 않은 Service 들에 빨간 밑줄로 뜨면서 에러를 뿜어 내는 것을 볼 수 있습니다.)


우선 아래 그림과 같이 Controller, Service, DAO, Model 등의 자바 파일이 위치 할 패키지들을 먼저 생성해 줍니다.




가장 먼저 작업할 내용은 데이터를 담을 그릇(VO 또는 DTO)을 만드는 것입니다.

VO는 위에 만들어 놓은 com.freehoon.web.board.model 패키지 아래에 생성합니다.

그리고 VO의 내용은 테이블의 컬럼과 유사하게 만듭니다.

(이렇게 유사하게 만드는 이유는 데이터의 관리를 효율적으로 하기 위해서 입니다.)


BoardVO.java 신규 파일 생성

package com.freehoon.web.board.model;


public class BoardVO {

public int bid;

public String cate_cd;

public String title;

public String content;

public String tag;

public int view_cnt;

public String reg_id;

public String reg_dt;

public String edit_dt;

}


이 각각의 필드에 접근해 값을 가지고 오고, 값을 설정할 Getter와 Setter을 만들 차례 입니다.

위의 코드 이후에 그냥 타이핑 해도 상관은 없지만, 필드가 많아지면 시간이 많이 걸리고, 

오타로 인한 오류가 발생할 확률도 높아 집니다.

STS 에서는 이것을 자동으로 생성해 주는 기능을 제공합니다.

이 기능을 이용해 자동으로 Getter과 Setter을 셋팅해 보도록 합니다.


메뉴 [Source > Generate Getters and Setters...] 를 클릭합니다.



아래와 같이 Getter과 Setter을 설정할 수 있는 창이 열립니다.




각 필드별로 Getter만 생성한다던지, Setter만 생성 할 수 도 있습니다.

필요한 것을 골라 체크를 하면 됩니다.

Getter과 Setter을 생성할 필드를 선택 후 하단의 [Generate] 버튼을 클릭합니다.


그리고 각 필드에 현재 어떤 값이 셋팅 되어 있는지 쉽게 확인 할 수 있는 toString() 메소드도 자동으로 생성 할 수 있습니다.

메뉴에서 [Source > Generate toString()...] 을 클릭하고 팝업 창에서 [Generate] 버튼을 클릭하면 됩니다.



확인 할 값을 체크하고 [Generate]를 클릭합니다.



SQL 쿼리 만들기

SQL문은 이전에 만들어 두었던 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="com.freehoon.web.board.boardMapper">


<select id="getBoardList" resultType="com.freehoon.web.board.model.BoardVO">

SELECT

BID, CATE_CD, TITLE, CONTENT, TAG, VIEW_CNT, REG_ID, REG_DT, EDIT_DT

FROM

TBL_BOARD

</select>

<select id="getBoardContent" resultType="com.freehoon.web.board.model.BoardVO" parameterType="com.freehoon.web.board.model.BoardVO">

SELECT

BID, CATE_CD, TITLE, CONTENT, TAG, VIEW_CNT, REG_ID, REG_DT, EDIT_DT

FROM

TBL_BOARD

WHERE

BID = #{bid}

</select>

<insert id="insertBoard" parameterType="com.freehoon.web.board.model.BoardVO">

INSERT INTO TBL_BOARD (CATE_CD, TITLE, CONTENT, TAG, VIEW_CNT, REG_ID, REG_DT, EDIT_DT)

VALUES (

#{cate_cd}

, #{title}

, #{content}

, #{tag}

, 0

, #{reg_id}

, now()

, now()

)

</insert>

<update id="updateBoard" parameterType="com.freehoon.web.board.model.BoardVO">

UPDATE TBL_BOARD SET

CATE_CD = #{cate_cd}

, TITLE = #{title}

, CONTENT = #{content}

, TAG = #{tag}

, EDIT_DT = now()

WHERE

BID = ${bid}

</update>

<delete id="deleteBoard" parameterType="int">

DELETE FROM TBL_BOARD

WHERE BID = #{bid}

</delete>

<update id="updateViewCnt" parameterType="com.freehoon.web.board.model.BoardVO">

UPDATE TBL_BOARD SET

VIEW_CNT = VIEW_CNT + 1

WHERE

BID = #{bid}

</update>

</mapper>


여기서 잘 기억해 둬야 하는 부분은 mapper의 namespace 입니다.

이 namespace 는 다음에 작성할 DAO 구현체 쪽에서 원하는 mapper를 찾기 위해 사용됩니다. 

따라서 namespace 는 다른 mapper 들과 겹치지 않도록 작성합니다.

(다른 mapper들과 구분을 위해 보통 '패키지명(com.freehoon.web) + 프로그램명(board) + 파일명(boardMapper)' 조합을 많이 사용 합니다.

따라서 이 mapper의 이름을 com.freehoon.web.board.boardMapper 라고 지었습니다.)

하지만 이 방법만이 옳은 방법은 아닙니다. 

일단 너무긴 namespace 는 가독성이 떨어 지기 때문에 쉽게 읽고 찾기가 힘듭니다.

따라서 간단하게 서비스 명으로 줄여 사용 할 수도 있습니다.

이 샘플 게시판에서는 긴 namespace를 사용 하였으나, 사용자에 맞게 쉬운 namespace를 쓰는 방법을 추천드립니다.



DAO 작성하기

이따 나올 Service 에서도 마찬가지지만 DAO를 만들때는 interface를 만들고

이 interface를 구현체를 만드는 형식으로 진행할 예정입니다.

interface를 만들지 않고 바로 구현체를 만들어 사용하여도 상관이 없습니다.

(개인적으로 그 방법이 좋다고는 생각하지 않지만, 개발 일정이 짧은 프로젝트에서는 그런 방식으로 개발을 진행하고 있습니다.)


DAO는 'com.freehoon.web.board.dao' 패키지에 만듭니다.


BoardDAO.java (interface) 신규 파일 생성

package com.freehoon.web.board.dao;


import java.util.List;


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


public interface BoardDAO {


public List<BoardVO> getBoardList() throws Exception;

public BoardVO getBoardContent(int bid) throws Exception;

public int insertBoard(BoardVO boardVO) throws Exception;

public int updateBoard(BoardVO boardVO) throws Exception;

public int deleteBoard(int bid) throws Exception;

public int updateViewCnt(int bid) throws Exception;

}


BoardDAOImpl.java 신규 파일 생성

package com.freehoon.web.board.dao;


import java.util.List;


import javax.inject.Inject;


import org.apache.ibatis.session.SqlSession;

import org.springframework.stereotype.Repository;


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


@Repository

public class BoardDAOImpl implements BoardDAO {

@Inject

private SqlSession sqlSession;


@Override

public List<BoardVO> getBoardList() throws Exception {

return sqlSession.selectList("com.freehoon.web.board.boardMapper.getBoardList");

}


@Override

public BoardVO getBoardContent(int bid) throws Exception {

return sqlSession.selectOne("com.freehoon.web.board.boardMapper.getBoardContent", bid);

}


@Override

public int insertBoard(BoardVO boardVO) throws Exception {

return sqlSession.insert("com.freehoon.web.board.boardMapper.insertBoard", boardVO);

}


@Override

public int updateBoard(BoardVO boardVO) throws Exception {

return sqlSession.update("com.freehoon.web.board.boardMapper.updateBoard", boardVO);

}


@Override

public int deleteBoard(int bid) throws Exception {

return sqlSession.insert("com.freehoon.web.board.boardMapper.deleteBoard", bid);

}


@Override

public int updateViewCnt(int bid) throws Exception {

return sqlSession.update("com.freehoon.web.board.boardMapper.updateViewCnt", bid);

}

}


SqlSession 객체를 통해 boardMapper 에 작성해 놓은 SQL문을 실행 할 수 있습니다.

SqlSession 객체가 지원하는 몇 가지 메소드가 있습니다.

메소드명을 보면 용도를 금방 알 수 있습니다.


<T> T selectOne(String statement, Object parameter)

<E> List<E> selectList(String statement, Object parameter)

<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)

int insert(String statement, Object parameter)

int update(String statement, Object parameter)

int delete(String statement, Object parameter)

(참조 : http://www.mybatis.org/mybatis-3/ko/java-api.html)


이제 마지막 테스트를 진행해 봅니다.

DAO 에서 제대로 쿼리를 호출해 실행되는지 확인해 봅니다.

src/test/java > com.freehoon.web 에 새로운 테스트 파일을 만듭니다.

BoardDAOTest.java

package com.freehoon.web;


import java.util.List;


import javax.inject.Inject;


import org.junit.Ignore;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


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

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


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = {

"classpath:spring/root-context.xml",

"classpath:spring/dataSource-context.xml"

})

public class BoardDAOTest {

private static final Logger logger = LoggerFactory.getLogger(BoardDAOTest.class);

@Inject

private BoardDAO boardDAO;

@Test 

public void testGetBoardList() throws Exception {

List<BoardVO> boardList = boardDAO.getBoardList();

logger.info("\n Board List \n ");

if(boardList.size() > 0) {

for(BoardVO list : boardList) {

logger.info(list.title);

}

} else {

logger.info("데이터가 없습니다.");

}

}

@Test @Ignore

public void testGetBoardContent() throws Exception {

BoardVO boardVO = boardDAO.getBoardContent(1);

logger.info("\n Board List \n ");

if(boardVO != null) {

logger.info("글번호 : " + boardVO.getBid() );

logger.info("글제목 : " + boardVO.getTitle() );

logger.info("글내용 : " + boardVO.getContent() );

logger.info("글태그 : " + boardVO.getTag() );

logger.info("조회수 : " + boardVO.getView_cnt() );

logger.info("작성자 : " + boardVO.getReg_id() );

logger.info("작성일 : " + boardVO.getReg_dt() );

logger.info("수정일 : " + boardVO.getEdit_dt() );

} else {

logger.info("데이터가 없습니다.");

}

}

@Test @Ignore 

public void testInsertBoard() throws Exception {

BoardVO boardVO = new BoardVO();

boardVO.setCate_cd("1");

boardVO.setTitle("첫번째 게시물 입니다.");

boardVO.setContent("첫번째 게시물입니다.");

boardVO.setTag("1");

boardVO.setReg_id("1");

int result = boardDAO.insertBoard(boardVO);

logger.info("\n Insert Board Result " +result);

if(result == 1) {

logger.info("\n 게시물 등록 성공 ");

} else {

logger.info("\n 게시물 등록 실패");

}

}

@Test @Ignore 

public void testUpdateBoard() throws Exception {

BoardVO boardVO = new BoardVO();

boardVO.setBid(1);

boardVO.setCate_cd("1");

boardVO.setTitle("첫번째 게시물 입니다-수정 합니다.");

boardVO.setContent("첫번째 게시물입니다-수정합니다.");

boardVO.setTag("1-1");

int result = boardDAO.updateBoard(boardVO);

logger.info("\n Update Board Result \n ");

if(result > 0) {

logger.info("\n 게시물 수정 성공 ");

} else {

logger.info("\n 게시물 수정 실패");

}

}

@Test   @Ignore

public void tesDeleteBoard() throws Exception {

int result = boardDAO.deleteBoard(1);

logger.info("\n Delete Board Result \n ");

if(result > 0) {

logger.info("\n 게시물 삭제 성공 ");

} else {

logger.info("\n 게시물 삭제 실패");

}

}


@Test @Ignore

public void testUpdateViewCnt() throws Exception {

int result = boardDAO.updateViewCnt(1);

logger.info("\n Update View Count Result \n ");

if(result > 0) {

logger.info("\n 게시물 조회수 업데이트 성공 ");

} else {

logger.info("\n 게시물 조회수 업데이트 실패");

}

}

}


여기에 처음 보는 어노테이션 @Ignore 가 등장합니다.

@Ignore 는 해당 테스트 메소드를 실행시키지 않는 기능을 합니다.

spring test를 몇 번 해보면 알겠지만

여러 메소드가 있을때 실행 순서를 특정 지을 수 없습니다.

따라서 위와 같은 경우 수정이나 삭제 같은 기능이 입력 보다 먼저 실행될 가능성이 있습니다.

입력되지도 않은 데이터를 수정하거나 삭제 할 수 없기 때문에 테스트가 정상적으로 될리 없습니다.

따라서 수고 스럽더라도 조회(리스트) -> 입력 -> 조회(상세)-> 수정 -> 삭제 순으로

@Ignore 를 순차적으로 변경해 가면서 테스트를 해보는 것이 좋습니다.


리스트 조회 테스트 결과


게시물 입력 테스트 결과

DB 툴을 이용해 확인해 본 결과 입니다.


게시물 상세 조회 테스트 결과


게시물 수정 테스트 결과


게시물 삭제 테스트 결과











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


반응형

관련글 더보기