본문 바로가기
Java/전자정부프레임워크

[#eGovFrame] 10. 커스텀 게시판 구축 및 CRUD 실습

by dopal2 2026. 2. 22.
반응형

데이터베이스 설계부터 비즈니스 로직, JSP 구현까지 

작업의 진행상황은 github을 확인해주세요

GitHub : https://github.com/dopal2?tab=repositories

egov : https://github.com/dopal2/egov
egovproject : https://github.com/dopal2/egovproject

기존 템플릿의 기능을 분석한 결과에 따라, 이제 직접 커스텀 게시판을 구축해 볼 차례입니다. DB 테이블 생성부터 MyBatis 연동, 그리고 본인 글만 수정/삭제가 가능한 권한 로직까지 포함하여 Tomcat A 환경에서 실습을 진행했습니다.


1. 데이터베이스 테이블 설계 (MySQL)

게시글 관리를 위한 기초가 되는 테이블입니다. 카테고리 기능과 비밀글 설정을 포함하며, 작성자 ID는 이전 포스팅에서 구축한 TEST_USER 테이블과 외래키로 연결했습니다.

CREATE TABLE TEST_BOARD (
    BOARD_IDX   INT AUTO_INCREMENT PRIMARY KEY,    -- 게시글 고유 번호
    TITLE       VARCHAR(200) NOT NULL,             -- 제목
    CONTENT     TEXT NOT NULL,                     -- 내용
    WRITER_ID   VARCHAR(20) NOT NULL,              -- 작성자 ID      
    CATEGORY    VARCHAR(20) DEFAULT 'GENERAL',     -- 카테고리
    IS_SECRET   CHAR(1) DEFAULT 'N',               -- 비밀글 여부
    REG_DATE    DATETIME DEFAULT CURRENT_TIMESTAMP, -- 등록일
    CONSTRAINT FK_BOARD_WRITER FOREIGN KEY (WRITER_ID) 
    REFERENCES TEST_USER(USER_ID) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
00. 테스트 테이블 생성

 


2. 자바 모델(VO) 및 MyBatis Mapper

데이터 전송을 위한 BoardVO 객체와 실제 쿼리가 실행되는 Mapper XML을 작성합니다. 수정/삭제 시 WRITER_ID를 대조하는 조건절이 핵심입니다.

<mapper namespace="TestBoardMapper">
    <!-- 게시글 등록 -->
    <insert id="insertBoard" parameterType="egovframework.egovtest.vo.BoardVO">
        INSERT INTO TEST_BOARD (TITLE, CONTENT, WRITER_ID, CATEGORY, IS_SECRET, REG_DATE) 
        VALUES (#{title}, #{content}, #{writerId}, #{category}, #{isSecret}, NOW())
    </insert>

    <!-- 게시글 목록 조회 -->
    <select id="selectBoardList" resultType="egovframework.egovtest.vo.BoardVO">
        SELECT BOARD_IDX as boardIdx, TITLE, WRITER_ID as writerId, CATEGORY, 
               IS_SECRET as isSecret, DATE_FORMAT(REG_DATE, '%Y-%m-%d') as regDate
        FROM TEST_BOARD ORDER BY BOARD_IDX DESC
    </select>
    
    <!-- 상세 조회, 수정, 삭제 쿼리 구현 완료 -->
</mapper>
01. testboardmapper 생성
02. BoardVO 생성

3. Service, DAO, Controller 레이어 구축

비즈니스 로직의 분리를 위해 표준 아키텍처 규칙을 준수하여 생성했습니다.

  • Service & DAO: 인터페이스와 구현체(Impl)를 분리하여 CRUD 메서드 정의
  • Controller: @RequestMapping을 활용해 목록(list), 쓰기(write), 수정(update), 상세(view) 요청을 처리하고 비즈니스 메서드 호출

- Controller

@Controller
@RequestMapping(value = "/testboard")
public class TestBoardController {

    @Resource(name = "TestBoardSerivce")
    private TestBoardService testBoardService;

    @RequestMapping(value = "/")
    public String root() {
        return "redirect:list.do";
    }

    @RequestMapping(value = "list.do")
    public ModelAndView list(HttpSession session, BoardVO boardVO) throws Exception {
        ModelAndView model = new ModelAndView("egov/list");
        UserVO loginUser = (UserVO) session.getAttribute("loginUser");
        List<BoardVO> list = testBoardService.selectBoardList(boardVO);

        model.addObject("loginUser", loginUser);
        model.addObject("list", list);
        return model;
    }

    @RequestMapping(value = "view.do")
    public ModelAndView view(HttpSession session, int idx) throws Exception {
        ModelAndView model = new ModelAndView("egov/view");
        UserVO loginUser = (UserVO) session.getAttribute("loginUser");
        BoardVO boardVO = testBoardService.selectBoardDetail(idx);

        model.addObject("loginUser", loginUser);
        model.addObject("boardVO", boardVO);
        return model;
    }

    @RequestMapping(value = "write.do")
    public ModelAndView write(HttpSession session) throws Exception {
        ModelAndView model = new ModelAndView("egov/write");
        UserVO loginUser = (UserVO) session.getAttribute("loginUser");

        model.addObject("loginUser", loginUser);
        return model;

    }

    @RequestMapping(value = "update.do")
    public ModelAndView update(HttpSession session, int idx) throws Exception {
        ModelAndView model = new ModelAndView("egov/write");
        UserVO loginUser = (UserVO) session.getAttribute("loginUser");
        BoardVO boardVO = testBoardService.selectBoardDetail(idx);

        model.addObject("loginUser", loginUser);
        model.addObject("boardVO", boardVO);
        return model;
    }

    @RequestMapping(value = "action.do")
    public String action(HttpSession session, String mode, BoardVO boardVO) throws Exception {
    	
    	System.out.println(mode);
    	System.out.println(boardVO.getTitle());

        UserVO loginUser = (UserVO) session.getAttribute("loginUser");

        if (mode == null || mode.isEmpty())
            return "redirect:/testboard/"; // 잘못된 접근

        if (loginUser == null)
            return "redirect:/testboard/"; // 로그인 정보 없을 때 실행불가

        if (mode.equals("write")) {
            boardVO.setWriterId(loginUser.getId());
            if (boardVO.getIsSecret() == null)
                boardVO.setIsSecret("N");
            testBoardService.insertBoard(boardVO);
        }
        if (mode.equals("update")) {
            if (!boardVO.getWriterId().equals(loginUser.getId()))
                return "redirect:/testboard/";
            if (boardVO.getIsSecret() == null)
                boardVO.setIsSecret("N");
            testBoardService.updateBoard(boardVO);
        }
        if (mode.equals("delete")) {
            if (!boardVO.getWriterId().equals(loginUser.getId()))
                return "redirect:/testboard/";
            testBoardService.deleteBoard(boardVO);
        }
        return "redirect:/testboard/";
    }
}

- Service

public interface TestBoardService {
	// 게시글 등록
    void insertBoard(BoardVO vo) throws Exception;

    // 게시글 목록 조회
    List<BoardVO> selectBoardList(BoardVO vo) throws Exception;

    // 게시글 상세 조회
    BoardVO selectBoardDetail(int boardIdx) throws Exception;

    // 게시글 수정
    void updateBoard(BoardVO vo) throws Exception;

    // 게시글 삭제
    void deleteBoard(BoardVO vo) throws Exception;
}

- ServiceImpl

@Service("TestBoardSerivce")
public class TestBoardServiceImpl implements TestBoardService {

	@Resource(name="TestBoardDAO")
    private TestBoardDAO testBoardDAO;
	
	public void insertBoard(BoardVO vo) throws Exception {
		testBoardDAO.insertBoard(vo);
	}

	public List<BoardVO> selectBoardList(BoardVO vo) throws Exception {
		return testBoardDAO.selectBoardList(vo);
	}

	public BoardVO selectBoardDetail(int boardIdx) throws Exception {
		return testBoardDAO.selectBoardDetail(boardIdx);
	}

	public void updateBoard(BoardVO vo) throws Exception {
		testBoardDAO.updateBoard(vo);
	}

	public void deleteBoard(BoardVO vo) throws Exception {
		testBoardDAO.deleteBoard(vo);
	}
}

- DAO

public interface TestBoardDAO {
	// 게시글 등록
    void insertBoard(BoardVO vo) throws Exception;

    // 게시글 목록 조회
    List<BoardVO> selectBoardList(BoardVO vo) throws Exception;

    // 게시글 상세 조회
    BoardVO selectBoardDetail(int boardIdx) throws Exception;

    // 게시글 수정
    void updateBoard(BoardVO vo) throws Exception;

    // 게시글 삭제
    void deleteBoard(BoardVO vo) throws Exception;
}

- DAOIml

@Repository("TestBoardDAO")
public class TestBoardDAOImpl implements TestBoardDAO {

    @Resource(name="egov.sqlSessionTemplate") 
    private SqlSessionTemplate sqlSession;

    // Mapper XML에 정의한 namespace와 ID를 조합합니다.
    private static final String Namespace = "TestBoardMapper.";

    public void insertBoard(BoardVO vo) throws Exception {
        sqlSession.insert(Namespace + "insertBoard", vo);
    }

    public List<BoardVO> selectBoardList(BoardVO vo) throws Exception {
        return sqlSession.selectList(Namespace + "selectBoardList", vo);
    }

    public BoardVO selectBoardDetail(int boardIdx) throws Exception {
        return sqlSession.selectOne(Namespace + "selectBoardDetail", boardIdx);
    }

    public void updateBoard(BoardVO vo) throws Exception {
        sqlSession.update(Namespace + "updateBoard", vo);
    }

    public void deleteBoard(BoardVO vo) throws Exception {
        sqlSession.delete(Namespace + "deleteBoard", vo);
    }
}
03. 파일 구성

4. JSP 화면 구현 및 트러블슈팅

목록, 상세조회, 작성 폼을 위한 3개의 JSP 파일을 생성했습니다. 이 과정에서 경로 문제가 발생했으나 설정을 통해 해결했습니다.

🛠️ Troubleshooting: 404 경로 오류
- 원인: dispatcher-servlet.xml에 정의된 InternalResourceViewResolver의 Prefix 경로와 실제 파일 위치 불일치
- 해결: /WEB-INF/jsp/ 하위의 정확한 위치로 파일을 재배치하여 정상 호출 확인
04. 목록

 

05. 등록

 

06. 조회

 

07. 수정

 

08. 수정확인
09. 삭제
10. 삭제 확인

 


5. 최종 테스트 결과

현재 페이징 등 부가 기능을 제외한 핵심 CRUD 기능이 완벽하게 동작합니다.

  • 작성자 연동: SSO 세션에 저장된 사용자 정보가 게시글 작성자로 자동 매핑됩니다.
  • 데이터 무결성: 작성자 본인만 수정/삭제가 가능하도록 서버 단에서 검증 로직이 작동합니다.

📝 Next Step

이제 데이터 조작 시 예외 상황이 발생해도 안전하게 원점으로 되돌리는 트랜잭션(Transaction) 테스트를 준비 중입니다.

실무 개발자의 필수 역량인 데이터 안정성 확보 과정을 정리하는 시간을 갖겠습니다.

 

반응형

댓글