데이터베이스 설계부터 비즈니스 로직, JSP 구현까지
작업의 진행상황은 github을 확인해주세요
GitHub : https://github.com/dopal2?tab=repositories
egov : https://github.com/dopal2/egovegovproject : 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;

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>


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);
}
}

4. JSP 화면 구현 및 트러블슈팅
목록, 상세조회, 작성 폼을 위한 3개의 JSP 파일을 생성했습니다. 이 과정에서 경로 문제가 발생했으나 설정을 통해 해결했습니다.
- 원인:
dispatcher-servlet.xml에 정의된 InternalResourceViewResolver의 Prefix 경로와 실제 파일 위치 불일치- 해결:
/WEB-INF/jsp/ 하위의 정확한 위치로 파일을 재배치하여 정상 호출 확인






5. 최종 테스트 결과
현재 페이징 등 부가 기능을 제외한 핵심 CRUD 기능이 완벽하게 동작합니다.
- 작성자 연동: SSO 세션에 저장된 사용자 정보가 게시글 작성자로 자동 매핑됩니다.
- 데이터 무결성: 작성자 본인만 수정/삭제가 가능하도록 서버 단에서 검증 로직이 작동합니다.
📝 Next Step
이제 데이터 조작 시 예외 상황이 발생해도 안전하게 원점으로 되돌리는 트랜잭션(Transaction) 테스트를 준비 중입니다.
실무 개발자의 필수 역량인 데이터 안정성 확보 과정을 정리하는 시간을 갖겠습니다.
'Java > 전자정부프레임워크' 카테고리의 다른 글
| [#eGovFrame] 09. SSO 기초: 서버 인스턴스 간 세션 공유 설정 (0) | 2026.02.21 |
|---|---|
| [#eGovFrame] 08. 인증서 발급 과제 목표 설계 및 테스트 시나리오 (0) | 2026.02.20 |
| [#eGovFrame] 07. MySQL DB 연동 및 한글 인코딩 트러블슈팅 (0) | 2026.02.20 |
| [#eGovFrame] 06. 템플릿 프로젝트 구조 분석 및 테스트 리포트 (0) | 2026.02.19 |
| [#eGovFrame] XX. 프로젝트 버전 관리 및 GitHub 연동 가이드 (0) | 2026.02.19 |
댓글