목차
MVC2패턴으로 구현한 jsp & servlet 게시판
Mysql, java 1.7, apache tomcat 7 필요
CRUD 계시판구현 및 파일 업로드 처리
DB : 커넥션 풀 이용
파일 업로드 : cos 라이브러리
커넥션 풀 context 경로 : META-INF/context.xml
DB 테이블
create table board (
code int(5) not null primary key auto_increment,
name varchar(100),
price int(8),
filename varchar(50),
filesize integer,
filedate date,
content varchar(1000)
);
파일 다운로드
[startBootServlet.zip
다운로드](</backup-assets/tistory/35/001.zip>)
이 예제는 Java 1.7과 Tomcat 7을 사용하던 시기의 학습용 자료다. 압축 파일의 코드와 의존 라이브러리를 실행하기 전에 별도 개발 환경에서 확인해야 한다.
요청 처리 흐름
flowchart LR 브라우저 --> 서블릿[Servlet 컨트롤러] 서블릿 --> 모델[게시판 서비스·DAO] 모델 --> DB[(MySQL)] 서블릿 --> JSP[JSP 뷰] JSP --> 브라우저
MVC2에서는 서블릿이 요청을 받아 입력을 해석하고, 모델이 조회·저장 작업을 수행한 뒤, JSP가 결과를 출력한다. 목록 조회는 GET, 글 생성은 POST처럼 요청의 역할을 분리하면 흐름을 추적하기 쉽다. 저장 후 새로고침으로 중복 등록되지 않도록 성공 시 리다이렉트하는 방식도 고려한다.
원래 SQL은 name, price 컬럼이 있어 일반적인 게시글 제목·본문 구조와 다르다. 실제 게시판으로 바꾸려면 컬럼 의미와 제약 조건을 먼저 정해야 한다. DB 쿼리에는 준비된 문장의 바인딩 변수를 사용하고, 업로드 파일은 이름·크기·형식을 검증한 뒤 웹 공개 경로와 분리해 저장한다. 다운로드 응답에서는 저장된 원본 경로를 사용자 입력으로 직접 조합하지 않는다.
목록·작성·상세의 책임을 나누기
기존 압축 파일은 Java 1.7, Tomcat 7, 당시 라이브러리 조합을 담은 역사적 학습 자료다. 현재 서비스에 바로 배포하는 절차로 취급하지 않는다. 현대 Jakarta Servlet 프로젝트로 같은 게시판을 새로 만든다면 요청별 책임부터 정한다.
| 요청 | Servlet의 일 | 서비스·DAO의 일 | JSP의 일 |
|---|---|---|---|
GET /posts | 페이지 번호 검증 | 정렬 기준과 범위로 목록 조회 | 이스케이프된 제목 표시 |
GET /posts/{id} | 숫자 ID 검증 | 글 조회와 공개 권한 확인 | 본문·첨부 표시 |
POST /posts | 로그인·CSRF·입력 길이 검사 | 글 저장 | 성공 뒤 리다이렉트된 화면 표시 |
POST /posts/{id}/delete | 작성자 인증·CSRF 검사 | 소유권과 삭제 처리 | 결과 메시지 표시 |
이 표의 URL은 설계 예시다. 서블릿 매핑을 실제로 구현할 때는 pathInfo 파싱과 존재하지 않는 ID 처리까지 정한다. 조회 화면에서 삭제 기능을 GET 링크로 만들면 크롤러나 미리 보기 요청만으로 상태가 바뀔 수 있으므로 상태 변경에는 POST 같은 요청을 사용한다.
sequenceDiagram
participant B as 브라우저
participant C as Servlet
participant S as PostService
participant DB as DB
participant V as JSP
B->>C: POST /posts
C->>C: 인증·입력·CSRF 검사
C->>S: createPost(command)
S->>DB: INSERT (바인딩 변수)
DB-->>S: 생성된 ID
S-->>C: ID
C-->>B: 303 See Other: /posts/{id}
B->>C: GET /posts/{id}
C->>V: request 속성 전달
V-->>B: HTML
성공 후 새 URL로 이동하는 Post/Redirect/Get 흐름은 브라우저 새로고침이 같은 POST를 다시 보내는 문제를 줄인다. 그래도 네트워크 재시도나 중복 클릭은 가능하므로 중복 생성이 업무상 문제라면 요청 식별자나 서버 측 중복 방지 규칙을 추가한다. JSP는 SQL을 실행하지 않고 서블릿이 전달한 화면 데이터를 렌더링한다.
첨부 파일 업로드를 붙일 때
Servlet의 @MultipartConfig로 요청과 파일 크기의 상한을 먼저 둔다. 예를 들어 파일 하나는 5MiB, 전체 요청은 6MiB로 제한할 수 있다.
@WebServlet("/posts")
@MultipartConfig(
maxFileSize = 5L * 1024 * 1024,
maxRequestSize = 6L * 1024 * 1024
)
public class PostServlet extends HttpServlet {
// doGet / doPost에서 요청을 구분하고 서비스에 위임
}
Part.getSubmittedFileName()은 클라이언트가 보낸 이름이라 저장 경로로 직접 쓰지 않는다. 저장 이름은 서버가 UUID처럼 새로 만들고, 업로드 디렉터리는 웹 공개 경로 밖에 둔다. 크기뿐 아니라 허용 파일 형식, 실제 내용, 사용자별 업로드 권한을 검사한다. 다운로드 요청에서는 DB에 저장한 파일 ID로 경로를 조회하고 그 글을 읽을 권한이 있는지 다시 확인한다.
DB INSERT와 파일 저장은 하나의 DB 트랜잭션으로 자동 묶이지 않는다. 파일을 쓴 뒤 INSERT가 실패하면 고아 파일을 지우거나 나중에 정리하는 절차가 필요하다. 반대로 DB를 먼저 저장하고 파일 쓰기가 실패하면 게시글 상태를 실패로 바꿀 수 있어야 한다. 이 순서를 명시하지 않으면 재시도할 때 같은 첨부가 여러 개 남는다.
원래 테이블의 price와 name은 상품 예제에 가까운 컬럼이다. 게시판이라면 title, body, author_id, created_at의 의미와 NOT NULL 제약, 길이 상한을 먼저 정한다. 요청 문자열을 이어 붙여 SQL을 만들지 않고 PreparedStatement의 바인딩 변수를 쓴다. JSP에서는 사용자 제목·본문을 HTML로 바로 연결하지 않고 이스케이프한다.
검증은 “게시글이 한 번 보인다”에서 끝나지 않는다. 빈 제목, 긴 본문, 권한 없는 삭제, 크기 초과 업로드, 잘못된 형식, 같은 POST 재전송, DB 실패 뒤 파일 잔존을 각각 확인한다. 원본 압축 파일을 참고할 때도 먼저 의존성과 업로드 경로를 검토하고 별도 개발 환경에서만 다룬다.