JSP로 모델1 방식의 회원제 게시판 만들기

[Must Have] 성낙현의 JSP 자바 웹 프로그래밍 중
골든래빗 출판사
성낙현 지음


Project 모델1 방식의 회원제 게시판 만들기
학습 목표
데이터베이스와 연동하여 데이터를 입력, 수정, 삭제, 조회할 수 있는 모델1 방식의 회원제 게시판을 제작합니다. 폼값 처리에는 내장 객체를 사용하고, 로그인은 session 영역을 사용합니다. 즉, 7장까지의 모든 내용을 통합하여 게시판을 구현함으로써 학습한 내용을 활용하는 법을 익히게 될 것입니다.
학습 순서
활용사례
게시판은 거의 모든 웹 사이트에서 사용한다고 해도 과언이 아닙니다. 게시판은 단순히 글 하나를 저장하는 기능 외에도 다양한 데이터를 저장하고 관리하는 기능도 제공합니다. 그래서 쇼핑몰의 상품 리스트, 재고 관리, 회원 관리 기능에서도 게시판을 활용하게 됩니다. 이처럼 게시판은 여러 형태의 데이터를 관리하기 위해 반드시 필요합니다. 지금부터 데이터 관리의 기본이 되는 게시판을 제작해보겠습니다.
8.1.1 회원제 게시판의 프로세스
다음은 우리가 이번 장에서 구현할 회원제 게시판의 프로세스입니다.
일반적으로 게시판은 첫 진입 시 게시물 목록을 보여줍니다. 목록에서 게시물의 제목을 클릭하여 상세 보기로 이동합니다. 내용을 확인하였다면 수정 혹은 삭제를 할 수 있습니다. 새로운 글을 작성하고 싶다면 목록에서 글쓰기 버튼을 눌러 쓰기 페이지로 이동할 수 있습니다.
하지만 우리가 구현하려는 것은 회원제 게시판입니다. 따라서 쓰기, 수정, 삭제의 경우에는 로그인한 이후에 처리할 수 있습니다. 로그인을 하더라도 본인이 작성한 게시물만 수정 혹은 삭제할 수 있습니다. 상세 보기는 비회원도 가능하도록 구현하겠습니다.
비회원(로그아웃) 상태 : 목록 보기, 상세 보기
회원(로그인) 상태 : 글쓰기, 수정하기, 삭제하기
각 기능을 처리한 후 페이지 이동은 다음과 같습니다.
글쓰기 후 : 목록으로 이동
수정 후 : 상세 보기로 이동
삭제 후 : 목록으로 이동
각 기능을 처리하는 파일 이름도 그림에 함께 표기해뒀습니다.
8.1.2 테이블 및 시퀀스 생성
게시판은 작성한 게시물을 DB에 저장한 후 관리해야 하므로 JDBC 프로그래밍은 필수입니다. DB에는 회원정보를 저장할 테이블과 게시물을 저장할 테이블을 생성해야 합니다. 우리는 5장에서 오라클에 두 개의 테이블을 이미 생성해두었습니다. 이 두 테이블을 정의서를 보면서 한 번 더 설명하도록 하겠습니다.
member 테이블은 회원정보를 저장할 테이블입니다. 게시판에 글을 쓰거나 수정, 삭제를 하기 전 회원인증을 위해 사용됩니다. 아이디 컬럼을 기본키primary key로 지정했습니다. 회원가입은 게시판의 글쓰기와 기능적으로 동일하므로 별도로 구현하지는 않고, 5장에서 삽입한 더미 데이터를 사용하도록 하겠습니다.
다음은 board 테이블을 보겠습니다.
사용자가 입력한 게시물을 저장하는 테이블입니다. 기본키로 사용되는 일련번호 컬럼에는 시퀀스를 통해 부여되는 순번을 입력하게 됩니다. 시퀀스sequence란 순차적으로 증가하는 순번을 생성해 중복되지 않는 정숫값을 반환하는 데이터베이스 객체입니다. 시퀀스도 5장에서 만든 것을 그대로 재활용하겠습니다.
또한 member 테이블의 id 컬럼과 board 테이블의 id 컬럼은 외래키foreign key로 엮어 있습니다. 만약 member 테이블에 없는 아이디로 게시물을 작성하려 하면 제약조건 위배로 에러가 날 것입니다. 회원으로 가입된 사람만 글을 쓸 수 있다는 뜻입니다. 테이블과 시퀀스를 생성하는 쿼리문은 5장을 참고하기 바랍니다.
8.2 모델1 구조와 모델2 구조(MVC 패턴)
이번 장에서는 회원제 게시판을 ‘모델1’ 방식으로 개발한다고 했습니다. 그렇다면 모델1 방식이란 무엇일까요? 또한 모델2 방식과 어떤 차이가 있을까요? 모델1과 모델2의 차이를 이해하기 위해서는 먼저 MVC 패턴에 대해 알아야 합니다.
8.2.1 MVC 패턴
웹 애플리케이션은 사용자의 요청을 받아 처리한 후 응답하는 구조입니다. MVC는 모델Model, 뷰View, 컨트롤러Controller의 약자로, 소프트웨어를 개발하는 방법론의 일종입니다. 데이터 처리를 담당하는 모델과 화면 출력을 담당하는 뷰, 그리고 이 둘을 제어하는 컨트롤러가 각자의 역할을 분담하여 사용자의 요청을 처리한 후 결과를 웹 브라우저에 출력하게 됩니다.
모델 : 업무 처리 로직(비즈니스 로직) 혹은 데이터베이스와 관련된 작업을 담당합니다.
: JSP 페이지와 같이 사용자에게 보여지는 부분을 담당합니다.
컨트롤러 : 모델과 뷰를 제어하는 역할을 합니다. 사용자의 요청을 받아서 그 요청을 분석하고, 필요한 업무 처리 로직(모델)을 호출합니다. 모델이 결괏값을 반환하면 출력할 뷰(JSP 페이지)를 선택한 후 전달합니다.
8.2.2 모델1 구조와 모델2 구조
이번에는 모델1과 모델2의 구조에 대해 알아보겠습니다. 웹은 사용자의 요청부터 시작된다고 했습니다. 다음 그림을 먼저 보겠습니다.
그림과 같이 모델1 방식에서는 사용자의 요청을 JSP가 받아 모델을 호출합니다. 모델이 요청을 처리한 후 결과를 반환하면 JSP를 통해 응답하게 됩니다. 즉 JSP에 뷰와 컨트롤러가 혼재되어 있습니다.
모델1 방식은 이런 구조 때문에 개발 속도가 빠르고 배우기 쉽다는 장점이 있지만, 뷰와 컨트롤러 두 가지 기능 모두를 JSP에서 구현해야 하므로 코드가 복잡해지고 유지보수가 어렵습니다.
하지만 모델2 방식은 앞에서 설명한 MVC 패턴을 그대로 사용합니다. JSP와 서블릿의 장점을 모두 취합하여 JSP는 뷰로 사용하고, 서블릿은 컨트롤러로 사용합니다. 다음은 모델2의 구조입니다.
보다시피 모델2 방식에서는 사용자의 요청을 컨트롤러인 서블릿이 받습니다. 서블릿은 사용자의 요청을 분석한 후 모델을 호출합니다. 모델로부터 데이터를 받아 뷰로 전달하면 최종적으로 사용자는 요청에 대한 응답을 받을 수 있게 됩니다.
모델, 뷰, 컨트롤러가 각자의 역할을 수행하므로 업무 분담이 명확해지고 코드가 간결해집니다. 자연스럽게 유지보수도 쉬워지죠. 하지만 구조가 복잡하여 익숙하지 않다면 개발 기간이 길어질 수 있어 규모가 작은 프로젝트에서는 적합하지 않을 수도 있습니다.
그렇다면 배우기 쉬운 모델1 방식을 선택해야 할까요? 아니면 유지보수가 쉬운 모델2 방식을 선택해야 할까요? 정답은 프로젝트에 따라 다를 수 있습니다. 우리는 두 가지를 모두 학습한 후 구현 방식에 따른 차이점을 알아보겠습니다. 그럼 이제부터 모델1 방식의 회원제 게시판을 제작해보겠습니다.
Step 1 8.3 목록 보기
게시판의 목록 페이지부터 제작하겠습니다. 이번 절에서는 페이지 개념 없이 전체 게시물을 한꺼번에 출력하는 형태로 제작하겠습니다. 페이지로 나누는 기능이 추가되면 코드가 다소 어려워지므로, 기본 기능들을 만들어본 후에 9장에서 페이지 기능을 추가하겠습니다.
8.3.1 DTO와 DAO 준비
먼저 board 테이블에 데이터를 저장하거나 전송하기 위한 DTO 클래스를 생성하겠습니다.
Note
DTO(Data Transfer Object)는 주로 데이터 저장이나 전송에 사용되는, 로직을 가지고 있지 않은 객체를 말합니다. 3.2절 ‘데이터 전송 객체(DTO) 준비’를 참고하세요.
예제 8-1 게시글 목록용 DTO Java Resources/src/model1/board/BoardDTO.java
package model1.board; ❶

public class BoardDTO {
    //멤버 변수 선언 ❷
    private String num;
    private String title;
    private String content;
    private String id;
    private java.sql.Date postdate; private String visitcount; private String name; 3

    // 게터·세터 ❸
    public String getNum() {
        return num; 
    }
    public void setNum(String num) {
         this.num = num;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public java.sql.Date getPostdate() {
        return postdate;
    }
    public void setPostdate(java.sql.Date postdate) {
        this.postdate = postdate;
    }
    public String getVisitcount() {
        return visitcount;
    }
    public void setVisitcount(String visitcount) {
        this.visitcount = visitcount;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
패키지는 model1.board로 선언합니다.
멤버 변수는 board 테이블의 컬럼과 동일하게 작성합니다. 자바빈즈 규약에 따라 접근 지정자는 private으로 지정합니다. 자료형은 특별한 이유가 없다면 String으로 선언하면 됩니다.
board 테이블에는 작성자의 아이디만 저장되므로 목록 출력 시 이름은 출력할 수 없습니다. 따라서 이름을 출력해야 한다면 member 테이블과 조인Join을 사용해야 합니다. 이때 name 컬럼을 사용하게 됩니다. 이와 같이 DTO는 필요한 경우 다른 테이블의 컬럼을 멤버 변수로 추가할 수 있습니다.
멤버 변수를 선언하였다면 게터와 세터 메서드는 [Source][Generate Getters and Setters...] 메뉴를 이용해 자동으로 생성할 수 있습니다.
DTO 클래스는 별다른 로직을 가지고 있지 않으므로 더 설명할 게 없습니다. 이어서 DAO 클래스를 생성해보겠습니다.
Note
DAO(Data Access Object)는 데이터베이스에 접근하여 CRUD 작업을 수행하기 위한 객체입니다. 6.3.2절 ‘DB 연동’을 참고하세요.
예제 8-2 게시글 목록 CRUD용 DAO Java Resources/src/model1/board/BoardDAO.java
package model1.board; ❶

import javax.servlet.ServletContext;
import common.JDBConnect;

public class BoardDAO extends JDBConnect { ❷
    public BoardDAO(ServletContext application) {
        super(application); ❸
    }
}
예제 8-1의 BoardDTO 클래스와 같은 패키지입니다.
5장에서 생성한 JDBConnect 클래스를 상속한 후, 생성자에서는 부모 클래스의 생성자를 호출합니다. 부모 클래스인 JDBConnect에는 총 3개의 생성자를 정의하였는데, 그 중 application 내장 객체를 받는 생성자를 이용했습니다. 이 생성자는 매개변수로 받은 application 내장 객체를 통해 web.xml에 정의해둔 오라클 접속 정보를 직접 가져와 DB에 연결해줍니다.
이로써 오라클에 연결하기 위한 준비가 끝났습니다. 이제 본격적으로 목록을 출력하기 위한 코드를 작성해보겠습니다.
8.3.2 JSP 페이지 구현
우선 여러분의 이해를 돕기 위해 목록을 실행한 화면을 먼저 보겠습니다.
그림과 같이 게시판 목록에서는 다음과 같은 기능을 제공합니다.
• ❶은 6.3.4절에서 만든 공통 링크입니다.
• ❷의 검색폼을 통해 제목이나 내용으로 게시물을 검색합니다.
• ❸ 제목을 클릭하면 상세 보기 페이지로 이동합니다.
• ❹ [글쓰기] 버튼을 클릭하면 쓰기 페이지로 이동합니다.
목록에 출력할 게시물을 얻어오기 위한 메서드를 작성하도록 하겠습니다. [예제 8-2]에서 작성한 BoardDAO 클래스에 메서드를 추가하면 됩니다. 여기서 추가할 메서드는 2개입니다.
selectCount() : board 테이블에 저장된 게시물의 개수를 반환합니다. 목록에서 번호를 출력하기 위해 사용됩니다.
selectList() : board 테이블의 레코드를 가져와서 반환합니다. 이 메서드가 반환한 ResultSet 객체로부터 게시물 목록을 반복하여 출력하게 됩니다.
게시물 개수 세기
selectCount() 메서드부터 추가해보죠.
예제 8-3 DAO에 selectCount() 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...
import java.util.Map; ❶

public class BoardDAO extends JDBConnect {
    ... 생성자 생략 ...

    // 검색 조건에 맞는 게시물의 개수를 반환합니다.
    public int selectCount(Map map) { ❷
        int totalCount = 0; // 결과(게시물 수)를 담을 변수
           
        // 게시물 수를 얻어오는 쿼리문 작성
        String query = "SELECT COUNT(*) FROM board"; 
        if (map.get("searchWord") != null) { ❸❹
             query += " WHERE " + map.get("searchField") + " " ❸
                    + " LIKE '%" + map.get("searchWord") + "%'"; ❸
        }

        try { ❺
            stmt = con.createStatement(); // 쿼리문 생성 ❻
            rs = stmt.executeQuery(query); // 쿼리 실행 ❼
            rs.next(); // 커서를 첫 번째 행으로 이동 ❽
            totalCount = rs.getInt(1); // 첫 번째 컬럼 값을 가져옴 ❾
        }
        catch (Exception e) {
            System.out.println("게시물 수를 구하는 중 예외 발생");
            e.printStackTrace();
        }
        return totalCount; ❿
    }
}
은 selectCount() 코드에서 사용하는 외부 클래스를 임포트한 것입니다. 필요한 클래스의 임포트문은 Ctrl + Shift + O 키를 누르면 이클립스가 자동으로 추가해주므로, 앞으로는 따로 언급하지 않겠습니다.
게시물 개수를 알려주는 메서드를 정의합니다. 매개변수로 받은 Map<String, Object> 컬렉션에는 게시물 검색을 위한 조건(검색어)이 담겨 있습니다. 뒤에서 좀 더 자세히 설명하겠습니다.
게시물의 개수를 얻어오는 쿼리문을 작성합니다. 이때 SQL이 제공하는 COUNT(*) 함수를 사용합니다. if문을 써서 검색어가 있는 경우, 즉 Map 컬렉션에 “searchWord” 키로 저장된 값이 있을 때만 WHERE절을 추가하도록 했습니다.
Note
SQL 예약어(SELECT, WHERE 등)는 대소문자를 구분하지 않습니다만, 가독성을 높이기 위해 대문자로 구분해 썼습니다.
JDBC 프로그래밍은 기본적으로 예외처리를 해야 해서 try/catch문으로 감쌌습니다. 정적 쿼리문을 실행하기 위해 Statement 객체를 생성한 후 쿼리를 실행합니다. 결과는 ResultSet 객체로 반환됩니다.
ResultSet 객체는 행 단위로 저장되며, 현재 행을 가리키는 커서cursor를 통해 값을 읽어오는 구조입니다. next()로 커서를 최초 행으로 이동시킨 다음 게시물 개수를 추출합니다. COUNT(*) 함수가 반환하는 값은 정수이므로 getInt()를 사용했습니다. 이때 숫자 1은 SELECT절에 명시된 컬럼의 인덱스를 의미합니다.
Note
DB에서 인덱스는 1부터 시작합니다.
마지막으로 추출한 값을 반환하면 모든 처리가 끝납니다. 이 값이 JSP로 반환되게 됩니다.
방금 추가한 selectCount()는 결과적으로 다음 쿼리문을 실행한 결과를 반환합니다.
SELECT COUNT(*) FROM board;
만약 사용자가 다음 그림과 같이 검색 항목으로 ‘제목’을 선택한 후 “홍길동”이라고 입력하고 [검색하기] 버튼을 누른다면 쿼리문에는 WHERE절이 추가되어야 합니다.
WHERE절이 추가된 쿼리문은 다음과 같습니다.
SELECT COUNT(*) FROM board WHERE title like '%홍길동%';
그러면 like를 사용해 title 컬럼에서 검색어로 입력한 단어가 포함된 레코드를 찾게 됩니다. 이 쿼리문을 실행한 후 결과를 얻는 코드가 ~입니다. 해당 부분만 다시 자세히 살펴볼까요?
stmt = con.createStatement(); // 쿼리문을 실행하기 위해 Statement 객체 생성 
rs = stmt.executeQuery(query); // SELECT 쿼리문을 실행. 실행 결과는
                       // ResultSet 객체로 반환 
rs.next(); // 커서를 이동시켜 결괏값이 있는지 확인
totalCount = rs.getInt(1); // ResultSet 객체의 1번 인덱스의 결과를 정수로 추출 
JSP에서 입력한 검색어가 어떤 과정을 거쳐서 selectCount()에 인수로 전달되는지는 아직 설명하지 않았습니다. 이 부분은 JSP에서 검색폼을 만들어본 후인 예제 8-6에서 설명하겠습니다.
게시물 목록 가져오기
다음은 게시물을 가져오는 메서드를 작성할 차례입니다. 이번에도 같은 파일에 selectList()라는 이름으로 메서드를 추가하겠습니다.
예제 8-4 DAO에 selectList() 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...

public class BoardDAO extends JDBConnect {
    ... 생략 ...
    public int selectCount(Map map) { ... 생략 ... }

    // 검색 조건에 맞는 게시물 목록을 반환합니다.
    public List selectList(Map map) { ❶ 
        List bbs = new Vector();
            // 결과(게시물 목록)를 담을 변수 ❷

        String query = "SELECT * FROM board "; ❸ 
        if (map.get("searchWord") != null) { ❹
             query += " WHERE " + map.get("searchField") + " "
                    + " LIKE '%" + map.get("searchWord") + "%' ";
        }

        query += " ORDER BY num DESC "; ❺
        
        try {
            stmt = con.createStatement(); // 쿼리문 생성 ❻
            rs = stmt.executeQuery(query); // 쿼리 실행 ❼
   
            while (rs.next()) { // 결과를 순화하며... ❽
                // 한 행(게시물 하나)의 내용을 DTO에 저장
                BoardDTO dto = new BoardDTO(); ❾

                dto.setNum(rs.getString("num")); // 일련번호 ❾
                dto.setTitle(rs.getString("title")); // 제목 ❾
                dto.setContent(rs.getString("content")); // 내용 ❾
                dto.setPostdate(rs.getDate("postdate")); // 작성일 ❾
                dto.setId(rs.getString("id")); // 작성자 아이디 ❾
                dto.setVisitcount(rs.getString("visitcount")); // 조회수 ❾

                bbs.add(dto); // 결과 목록에 저장 ❿
            }
        }
        catch (Exception e) {
            System.out.println("게시물 조회 중 예외 발생");
            e.printStackTrace();
        }

        return bbs; ⓫
    }
}
연결된 데이터베이스로부터 게시물 목록을 가져오는 메서드를 정의합니다. selectCount()와 똑같이 검색 조건을 매개변수로 받습니다.
테이블에서 레코드를 가져올 때는 항상 List 계열의 컬렉션에 저장합니다. List 컬렉션에는 데이터가 순서대로 저장되어 인덱스를 통해 가져올 수 있기 때문입니다. 여기서는 Vector를 사용했지만 ArrayList나 LinkedList 등 List 계열의 컬렉션이라면 모두 동일하게 사용할 수 있습니다.
목록을 가져오기 위한 쿼리문을 작성합니다. 검색어가 있다면 WHERE절을 추가하는 부분은 selectCount() 메서드와 똑같습니다. 그런데 마지막에 게시물 정렬을 위한 ORDER BY절이 추가되었습니다. 게시판 목록은 항상 최근 게시물이 상단에 출력되므로 일련번호 컬럼 num을 내림차순(DESC)으로 정렬한 것입니다.
Statement 객체를 생성하여 쿼리문을 실행합니다. 결과는 ResultSet 객체에 저장되어 반환됩니다.
next() 메서드로 ResultSet에 저장된 행이 있는지 확인합니다. 있다면 true를 반환하고, 커서를 첫 번째 행으로 이동시킵니다. while문과 함께 써서 더 이상 행이 존재하지 않을 때까지 중괄호 안의 작업을 반복하게 됩니다.
while문 안에서는 하나의 행(게시물 하나)의 내용을 DTO 객체에 저장한 후 이 DTO를 List 컬렉션에 담습니다. 이렇게 while 루프를 끝까지 돌고 나면 쿼리문으로 받아온 게시물 목록이 모두 List 컬렉션인 bbs에 저장되게 됩니다.
마지막으로 쿼리 결과를 모두 담은 List 컬렉션을 JSP로 반환합니다.
게시물 목록 출력하기
이번에는 목록을 화면에 출력해주는 JSP를 만들 차례입니다. 이번에 작성할 파일은 JSP 파일이므로 WebContents/08Board 폴더에 추가해주세요. 코드가 길어 두 번으로 나눠 설명하겠습니다. 먼저 DAO를 통해 필요한 정보를 얻어오는 부분입니다.
예제 8-5 게시물 목록을 출력하는 JSP(앞부분) WebContent/08Board/List.jsp
<%@ page import="java.util.List"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%> <%
// DAO를 생성해 DB에 연결 ❶
BoardDAO dao = new BoardDAO(application);

// 사용자가 입력한 검색 조건을 Map에 저장 ❷
Map param = new HashMap();

String searchField = request.getParameter("searchField");
String searchWord = request.getParameter("searchWord");
if (searchWord != null) {
     param.put("searchField", searchField);
     param.put("searchWord", searchWord);
}
int totalCount = dao.selectCount(param); // 게시물 수 확인 ❸ 
List boardLists = dao.selectList(param); // 게시물 목록 받기 ❹ 
dao.close(); // DB 연결 닫기
%>




회원제 게시판


... 생략(예제 8-6) ...

가장 먼저 BoardDAO 객체를 생성하는데, DAO 생성 과정에서 DB와의 연결이 완료됩니다.
그다음 사용자가 검색폼에서 입력한 내용을 Map 컬렉션에 저장합니다. DAO의 메서드를 호출할 때 이 컬렉션을 매개변수로 전달할 것입니다. 저장되는 과정은 뒤에서 설명하겠습니다.
이제 DAO가 제공하는 메서드를 호출해 게시물의 개수와 게시물 목록을 가져옵니다. 모두에서 에서 만든 검색 조건을 매개변수로 전달했으므로 조건에 맞는 게시물만 찾아줍니다.
이어서 예제 8-5에서 생략한 <body></body> 안쪽의 코드를 보겠습니다.
예제 8-5 게시물 목록을 출력하는 JSP(뒷부분) WebContent/08Board/List.jsp
... 생략 ...

     
    

목록 보기(List)

❹ ❺ ❻ <% if (boardLists.isEmpty()) { //게시물이 하나도 없을 때 ❼ %> <% } else { //게시물이 있을 때 ❽ int virtualNum = 0; // 화면상에서의 게시물 번호 for (BoardDTO dto : boardLists) { virtualNum = totalCount--; // 전체 게시물 수에서 시작해 1씩 감소 %> <% } } %>
번호 제목 작성자 조회수 작성일
등록된 게시물이 없습니다^^*
<%= virtualNum %> <%= dto.getTitle() %> <%= dto.getId() %> <%= dto.getVisitcount() %> <%= dto.getPostdate() %>
가장 위에는 검색폼이 옵니다. 전송 방식은 get 방식이고, action 속성을 지정하지 않았으므로 submit하면 폼값이 현재 페이지로 전송됩니다. 검색 항목(searchField)은 제목과 내용 중 선택할 수 있습니다. 그래서 검색어가 없을 때와 있을 때를 구분하여 처리하게 됩니다.
검색폼 아래에는 검색목록이 표 형태로 출력됩니다. 표의 첫 줄은 각 컬럼의 이름이 오고, 그 아래에 드디어 목록의 내용이 나옵니다.
List 컬렉션에 저장된 내용이 하나도 없다면 간략하게 “등록된 게시물이 없습니다^^*”라고 출력하고, 게시물이 있다면 for문을 돌면서 하나씩 출력합니다. 게시물 정보는 모두 DTO 객체로부터 얻습니다. 특히 제목은 상세 보기 페이지로 이동하기 위한 링크가 추가됩니다. 상세 보기는 일련번호를 통해 게시물을 조회하므로 num을 매개변수로 전달하고 있습니다.
화면의 맨 아래에는 [글쓰기] 버튼이 자리합니다.
완성입니다. 이제 List.jsp를 실행하면 이번 절 처음에 보여드린 다음 화면이 나타날 것입니다.
그런데 게시글이 하나뿐이니 목록을 다 가져온 것인지 확인할 수 없습니다. 확인해보기 위해 더미 데이터를 몇 개 더 추가하겠습니다.
To Do 01 윈도우 + R 키를 눌러 실행창을 띄웁니다.
To Do 02 실행창이 뜨면 “cmd”를 입력하고 enter 키를 누르면 명령 프롬프트가 실행됩니다.
To Do 03 명령 프롬프트에서 sqlplus 명령을 실행하여 musthave 계정으로 접속합니다.
To Do 04 다음 SQL문을 실행하여 더미 데이터를 추가합니다.
WebContent/08Board/더미데이터추가.sql
INSERT INTO board VALUES (seq_board_num.nextval, '지금은 봄입니다', '봄의왈츠', 'musthave', sysdate, 0);
INSERT INTO board VALUES (seq_board_num.nextval, '지금은 여름입니다', '여름향기', 'musthave', sysdate, 0);
INSERT INTO board VALUES (seq_board_num.nextval, '지금은 가을입니다', '가을동화', 'musthave', sysdate, 0);
INSERT INTO board VALUES (seq_board_num.nextval, '지금은 겨울입니다', '겨울연가', 'musthave', sysdate, 0);
commit;
Note
더미 데이터를 추가한 후 마지막에 반드시 commit을 실행해야 합니다.
이제 웹 브라우저로 돌아와 목록 페이지에서 새로고침( F5 )해보면 다음과 같이 방금 입력한 게시물들도 잘 출력됩니다.
게시물 검색하기
이번에는 검색을 해보겠습니다. 검색폼에 ‘제목’을 선택한 후 “겨울”이라고 입력하고 [검색하기] 버튼을 누릅니다.
그러면 검색 항목과 검색어가 주소표시줄 뒷부분에 쿼리스트링을 통해 전송되게 됩니다. 그러면 예제 8-5의 JSP 코드에서 요청 객체의 getParameter()로 매개변수를 받아서, Map 컬렉션에 저장한 후, DAO로 전달해 조건에 맞는 결과만 가져옵니다. 그러면 앞의 그림과 같이 해당 게시물만 출력되는 것이죠. 여러분은 ‘내용’ 항목도 선택해 검색해보시기 바랍니다.
이로써 페이지 처리 없는 게시판 목록이 완성되었습니다. 다음은 글쓰기를 제작해보겠습니다.
Step 2 8.4 글쓰기
회원제 게시판에서는 반드시 로그인 후에야 쓰기 페이지로 진입할 수 있습니다. 로그인이 안 된 상태라면 로그인 페이지로 이동해야 합니다. 로그인 기능은 6장에서 제작한 LoginForm.jsp를 그대로 사용하겠습니다.
8.4.1 로그인하지 않았다면 로그인 폼으로 이동
먼저 로그인 정보가 없을 때 로그인 페이지로 이동시키는 페이지를 작성해보겠습니다.
예제 8-7 로그인하지 않았다면 로그인 폼으로 이동 WebContent/08Board/IsLoggedIn.jsp
<%@ page import="utils.JSFunction"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> 

<%
if (session.getAttribute("UserId") == null) { ❶
    JSFunction.alertLocation("로그인 후 이용해주십시오.", ❷
            "../06Session/LoginForm.jsp", out); ❷
    return; 3 }
%>
session 영역에 “UserId”라는 속성값이 있는지 확인합니다. 값이 null이면 로그인하지 않았다는 뜻입니다.
로그인하지 않았다면 경고창을 띄운 후, 로그인 페이지로 이동합니다. utils 패키지의 JSFunction 클래스는 4장에서 작성하였습니다.
특정한 조건에서 실행을 멈추고 싶을 때는 반드시 return 문을 써줘야 합니다.
예제 8-7은 회원제 게시판의 프로세스에서 로그인 확인이 필요한 모든 페이지에서 include해 포함시킬 용도로 만들었으니 따로 실행해볼 필요는 없습니다. 이처럼 공통적으로 사용하는 코드는 include해 사용하면 편리합니다. 코드를 수정해야 할 때도 한 군데만 수정하면 되므로 유지보수도 쉬워지고요.
8.4.2 글쓰기 페이지 구현
글쓰기 페이지를 작성해보겠습니다.
예제 8-8 글쓰기 페이지 WebContent/08Board/Write.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%> 
회원제 게시판




회원제 게시판 - 글쓰기(Write)

제목
내용
글쓰기 페이지는 로그인해야 진입할 수 있으므로 예제 8-7에서 작성한 IsLoggedIn.jsp를 삽입했습니다.
의 자바스크립트 함수는 폼에 필수 항목인 ‘제목’과 ‘내용’이 입력되어 있는지 확인합니다. 값이 비어 있다면 경고창을 띄우고 포커스를 이동시킨 후, 실패를 뜻하는 false를 반환합니다.
폼의 이름(writeFrm), 전송 방식(post), 폼값을 전송할 경로(WriteProcess.jsp)를 지정합니다. 특히 [작성완료] 버튼을 눌렀을 때, 즉 submit 이벤트가 발생했을 때 에서 작성한 validateForm() 함수를 호출하는 코드입니다. 검증 함수가 false를 반환되면 폼값을 전송하지 않습니다.
는 글의 제목 입력란, 은 내용 입력란입니다. 각각의 name 속성값은 board 테이블의 컬럼 이름과 동일하게 설정했습니다. 마지막으로 [작성 완료] 버튼을 추가했습니다.
예제 8-5를 실행해 제목과 내용을 입력하고 [작성 완료] 버튼을 누르면 WriteProcess.jsp로 폼값이 전송됩니다. 이 폼값을 받아 데이터베이스에 추가하려면 먼저 DAO 클래스에 메서드를 추가해야 합니다.
8.4.3 DAO에 글쓰기 메서드 추가
model1.board.BoardDAO.java 파일을 열어서 다음과 같이 insertWrite() 메서드를 추가합니다.
예제 8-9 DAO에 글쓰기 메서드 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...

public class BoardDAO extends JDBConnect {
    ... 생략 ...

    // 게시글 데이터를 받아 DB에 추가합니다. ❶
    public int insertWrite(BoardDTO dto) {
        int result = 0;
        try {
            // INSERT 쿼리문 작성 ❷
            String query = "INSERT INTO board ( "
                + " num,title,content,id,visitcount) "
                + " VALUES ( "
                + " seq_board_num.NEXTVAL, ?, ?, ?, 0)"; ❸

            psmt = con.prepareStatement(query); // 동적 쿼리 ❹
            psmt.setString(1, dto.getTitle()); 
            psmt.setString(2, dto.getContent()); ❺
            psmt.setString(3, dto.getId());
            result = psmt.executeUpdate(); ❻
        }
        catch (Exception e) {
            System.out.println("게시물 입력 중 예외 발생"); 
            e.printStackTrace();
        }

        return result; ❼
    }
}
insertWrite() 메서드는 BoardDTO 타입의 매개변수를 받은 후 데이터를 insert합니다. 그리고 insert에 성공한 행의 개수를 정수로 반환합니다.
에서 INSERT 쿼리문을 작성합니다. 제목, 내용, 아이디를 모두 인파라미터(?)로 설정하고 에서 사용자가 입력한 값을 대입합니다. 이번에는 인파라미터가 있는 동적 쿼리이므로 PreparedStatement 객체를 생성했습니다.
INSERT 쿼리를 실행하면 성공적으로 추가한 행의 개수를 돌려줍니다. 이 값을 JSP로 반환합니다.
8.4.4 글쓰기 처리 페이지 작성
사용자가 글을 입력할 글쓰기 페이지와 글 내용을 데이터베이스에 저장해줄 DAO 객체가 준비되었으니, 이제 이 둘을 연결해주기만 하면 끝입니다.
예제 8-10 글쓰기 처리 페이지 WebContent/08Board/WriteProcess.jsp
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%> ❶

<%
//폼값 받기 ❷
String title = request.getParameter("title"); 
String content = request.getParameter("content");

// 폼값을 DTO 객체에 저장 ❸
BoardDTO dto = new BoardDTO();
dto.setTitle(title);
dto.setContent(content); 
dto.setId(session.getAttribute("UserId").toString()); ❹

// DAO 객체를 통해 DB에 DTO 저장 ❺
BoardDAO dao = new BoardDAO(application); 
int iResult = dao.insertWrite(dto); dao.close();

// 성공 or 실패?
if (iResult == 1) { 
    response.sendRedirect("List.jsp"); ❻
} else {
    JSFunction.alertBack("글쓰기에 실패하였습니다.", out); ❼
}
%>
로그인 확인을 위해 인클루드합니다. 만약 글쓰기 페이지에서 세션 유지 시간이 지날 동안 동작이 없었다면 로그인이 해제될 수 있기 때문입니다.
이후의 로직은 간단합니다. 전송된 폼값을 DTO 객체에 담아 앞 절에서 작성한 insertWrite( ) 메서드를 호출해 DB에 저장합니다. 곧바로 이해되지 않을 코드는 뿐일 겁니다. session 영역에 저장돼 있는 사용자 아이디까지 DTO에 담은 이유는 무엇일까요? board 테이블의 id 컬럼은 member 테이블의 id 컬럼과 외래키로 설정되어 있으므로, id가 빈값이면 INSERT할 때 제약조건 위배로 오류가 발생하기 때문입니다.
마지막으로 입력 결과에 따라 성공하면 목록 페이지로 이동하고, 실패하면 경고창을 띄운 후 이전 페이지, 즉 글쓰기 페이지로 이동합니다.
8.4.5 동작 확인
필요한 구성요소가 다 갖춰졌으니 의도한 대로 잘 동작하는지 테스트해볼 차례입니다.
To Do 01 Write.jsp를 실행합니다(혹은 목록 보기 화면에서 [글쓰기] 버튼을 눌러도 됩니다). 글쓰기 페이지로 진입하는 순간 다음과 같이 경고창이 뜰 것입니다.
To Do 02 [확인] 버튼을 누르면 로그인 페이지로 이동합니다.
로그인 페이지는 6장에서 작성한 LoginForm.jsp입니다.
To Do 03 아이디와 패스워드에 “musthave”와 “1234”를 각각 입력 후 [로그인하기] 버튼을 누릅니다.
To Do 04 로그인에 성공하면, 상단 공통 링크의 [게시판(페이징X)] 링크를 클릭합니다.
To Do 05 목록 보기 화면 나타날 겁니다. 오른쪽 아래의 [글쓰기] 버튼을 클릭합니다. 이제는 로그인이 된 상태이므로 글쓰기 페이지로 진입할 수 있습니다.
To Do 06 ‘내용’ 항목만 입력 후 [작성 완료] 버튼을 눌러봅니다. 그러면 “제목을 입력하세요.”란 경고 창이 뜹니다.
To Do 07 ‘제목’까지 채운 후 [작성 완료] 버튼을 눌러보겠습니다.
그러면 ‘목록 보기’ 페이지(List.jsp)로 이동하며, 새로 작성한 게시물이 등록된 것을 볼 수 있습니다.
게시물을 등록하였으니 다음은 쓴 내용을 볼 차례입니다. 다음 절에서는 ‘상세 보기’ 페이지를 작성해보겠습니다.
8.5 상세 보기
상세 보기는 사용자가 선택한 게시물 하나를 조회하여 보여주는 기능입니다. 내용을 보려면 목록에서 원하는 게시물의 제목을 클릭하면 됩니다. 이때 게시물의 일련번호(num 컬럼)를 매개변수로 전달하고, 이를 이용해 데이터베이스에서 게시물 내용을 가져올 것입니다. 또한 게시물을 조회하면 조회수(visitcount 컬럼)를 증가시켜야 합니다.
목록에서 게시물을 클릭하면 다음 그림과 같이 게시물의 일련번호가 매개변수로 전달되는 것을 볼 수 있습니다. 이 코드는 이미 목록 보기 페이지(List.jsp)에서 구현해두었습니다.
8.5.1 DAO 준비
그러면 먼저 게시물을 조회하기 위한 메서드를 DAO에서 추가하도록 하겠습니다.
예제 8-11 DAO에 게시물 조회 메서드 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...
 public class BoardDAO extends JDBConnect {
    ... 생략 ...

    // 지정한 게시물을 찾아 내용을 반환합니다.
    public BoardDTO selectView(String num) { ❶
        BoardDTO dto = new BoardDTO();

        // 쿼리문 준비 ❷
        String query = "SELECT B.*, M.name " ❸
                + " FROM member M INNER JOIN board B " ❹ 
                + " ON M.id=B.id "
                + " WHERE num=?";

        try {
            psmt = con.prepareStatement(query);
            psmt.setString(1, num); // 인파라미터를 일련번호로 설정 ❺
            rs = psmt.executeQuery(); // 쿼리 실행 ❻

            // 결과 처리
            if (rs.next()) { ❼
                dto.setNum(rs.getString(1)); ❽
                dto.setTitle(rs.getString(2)); ❽
                dto.setContent(rs.getString("content")); ❽
                dto.setPostdate(rs.getDate("postdate")); ❽
                dto.setId(rs.getString("id")); ❽
                dto.setVisitcount(rs.getString(6)); ❽
                dto.setName(rs.getString("name")); ❽
            }
        }
        catch (Exception e) {
            System.out.println("게시물 상세보기 중 예외 발생");
            e.printStackTrace();
        }
        return dto; ❾
    }
}
JSP에서 매개변수로 전달한 일련번호를 받습니다.
게시물을 가져오기 위한 SELECT 쿼리문을 작성합니다. 이때 member 테이블과 조인(INNER JOIN)을 걸어줍니다. board 테이블에는 작성자의 아이디만 저장되므로 이름을 출력하기 위해서는 member 테이블과의 조인이 필요하기 때문입니다. board 테이블의 모든 컬럼과 member 테이블의 name 컬럼을 가져옵니다.
이어서 인파라미터를 일련번호로 설정한 다음 쿼리문을 실행합니다.
ResultSet 객체로 반환된 행을 next() 메서드로 확인하고, DTO 객체에 저장합니다. 값을 추출하기 위한 get 계열 메서드에는 인덱스와 컬럼명을 혼합해서 사용하였습니다.
마지막으로 이렇게 완성한 DTO를 반환합니다.
계속해서 조회수를 1 증가시키는 메서드를 추가하겠습니다. 이번 메서드는 간단합니다.
예제 8-12 DAO에 조회수 증가 메서드 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...

public class BoardDAO extends JDBConnect {
    ... 생략 ...

    // 지정한 게시물의 조회수를 1 증가시킵니다.
    public void updateVisitCount(String num) { ❶ 
        // 쿼리문 준비 ❷
        String query = "UPDATE board SET "
                     + " visitcount=visitcount+1 "
                     + " WHERE num=?";
        try {
            psmt = con.prepareStatement(query);
            psmt.setString(1, num); // 인파라미터를 일련번호로 설정 ❸
            psmt.executeQuery(); // 쿼리 실행 ❹
        }
        catch (Exception e) {
            System.out.println("게시물 조회수 증가 중 예외 발생");
            e.printStackTrace();
        }
    } 
}
조회수를 증가시킬 게시물의 일련번호를 매개변수로 받습니다.
UPDATE 쿼리문을 작성한 후 인파라미터를 일련번호로 설정하고 쿼리를 실행합니다.
Note
일반적으로 UPDATE와 같이 기존 행에 영향을 주는 쿼리문은 executeUpdate() 메서드를 사용합니다. 하지만 UPDATE가 적용된 행의 개수를 알 필요가 없다면 처럼 executeQuery()를 사용해도 무방합니다. 즉, 두 메서드 모두 반환값만 다를 뿐 쿼리는 똑같이 실행해줍니다.
이상으로 상세 보기에 필요한 DAO 수정을 모두 마쳤습니다.
8.5.2 상세 보기 화면 작성
앞 절에서 보강한 DAO 메서드들을 이용해서 다음 그림과 같은 상세 보기 화면을 작성하겠습니다.
코드를 볼까요? 코드가 제법 길지만 지금까지 보아온 예제들과 같은 구성이라 친숙할 것입니다.
예제 8-13 상세 보기 페이지 WebContent/08Board/View.jsp
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%> 
<%
String num = request.getParameter("num"); // 일련번호 받기 ❶

BoardDAO dao = new BoardDAO(application); // DAO 생성 ❷
dao.updateVisitCount(num); //조회수 증가 ❸
BoardDTO dto = dao.selectView(num); // 게시물 가져오기 ❹
dao.close(); // DB 연결 해제
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"> <title>회원제 게시판</title> <script>
function deletePost() {
    // 생략(8.7절 참고)
}
</script>
</head>
<body>
<jsp:include page="../Common/Link.jsp" /> <!-- 공통 링크 -->
<h2>회원제 게시판 - 상세 보기(View)</h2> 
<form name="writeFrm">
    <input type="hidden" name="num" value="<%= num %>" /> 
    <table border="1" width="90%">
        <tr> ❺
            <td>번호</td> ❺
            <td><%= dto.getNum() %></td>  ❺
            <td>작성자</td> ❺
            <td><%= dto.getName() %></td> ❺
        </tr> ❺
        <tr> ❺
            <td>작성일</td> ❺
            <td><%= dto.getPostdate() %></td>  ❺
            <td>조회수</td> ❺
            <td><%= dto.getVisitcount() %></td> ❺
        </tr> ❺
        <tr> ❺
            <td>제목</td> ❺
            <td colspan="3"><%= dto.getTitle() %></td>  ❺
        </tr> ❺
        <tr>  ❺
            <td>내용</td> ❺
            <td colspan="3" height="100"> ❺
            <%= dto.getContent().replace("\r\n", "<br/>") %></td> ❺❻
        </tr> ❺
        <tr>
            <td colspan="4" align="center">
                <% ❼
                if (session.getAttribute("UserId") != null ❼
                    && session.getAttribute("UserId").toString().equals( ❼
                                                        dto.getId())) { ❼
                %> ❼
                <button type="button" ❼
                    onclick="location.href='Edit.jsp?num=<%= dto.getNum() %>';"> ❼
                    수정하기</button> ❼
                <button type="button" onclick="deletePost();">삭제하기</button> ❼
                <% ❼
                } ❼
                %> ❼
                <button type="button" onclick="location.href='List.jsp';"> 
                    목록 보기
                </button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
일련번호 매개변수를 받습니다.
DAO 객체를 생성한 후 앞 절에서 작성한 두 메서드를 호출하여 조회수를 1 증가시키고 게시물 가져오기를 실행합니다.
번호, 작성자, 제목 등 DTO 객체에 저장된 내용을 출력합니다. 특히 이때 에서 replace() 메서드로 엔터키를
태그로 변경하여, 웹 브라우저상에서 줄바꿈이 적용되도록 했습니다.
부분은 수정, 삭제를 위한 버튼을 출력합니다. 단, 작성자 본인에게만 노출하기 위해 다음 두 조건을 만족할 때만 버튼이 보이도록 했습니다.
1 session 영역에 속성값이 있는가? 즉, 로그인한 상태인가?
2 로그인(세션) 아이디와 DTO 객체에 저장된 아이디가 일치하는가? 즉, 작성자 본인인가?
Note
View.jsp를 단독으로 직접 실행하면 일련번호(num)를 건네받지 못해 500 에러가 발생합니다. 그러니 결과를 확인 하려면 반드시 목록 보기에서 게시물 제목을 클릭해서 실행해주세요.
STEP 4 8.6 수정하기
수정하기는 상세 보기와 글쓰기를 합쳐놓은 형태라고 보면 됩니다. 먼저 본인이 작성했던 글을 DB에서 가져와서 글쓰기 폼에 채워서 보여주고, 내용을 수정해 전송하면 수정한 내용으로 DB를 갱신하면 됩니다.
8.6.1 수정 폼 화면 작성
수정 폼을 먼저 작성할 텐데요, 기존의 글쓰기 파일(Write.jsp)을 복사-붙여넣기한 후 몇 가지만 수정하면 됩니다(Write.jsp와 다른 부분을 음영 처리하였습니다). 상세 보기는 앞 절에서 작성한 부분이므로 바로 사용할 수 있습니다.
예제 8-14 수정하기 폼 WebContent/08Board/Edit.jsp
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%> ❶
<%
String num = request.getParameter("num"); // 일련번호 받기
BoardDAO dao = new BoardDAO(application); // DAO 생성 ❷
BoardDTO dto = dao.selectView(num); // 게시물 가져오기
String sessionId = session.getAttribute("UserId").toString(); // 로그인 ID 얻기 ❸ 
if (!sessionId.equals(dto.getId())) { // 본인인지 확인 ❸
    JSFunction.alertBack("작성자 본인만 수정할 수 있습니다.", out); ❸
    return; ❸
} ❸

 dao.close(); // DB 연결 해제 %>




회원제 게시판




회원제 게시판 - 수정하기(Edit)

제목
내용
수정하기 페이지에서도 로그인한 상태인지 확인하기 위해 IsLoggedIn.jsp를 인클루드합니다.
그다음은 Write.jsp에서 달라진 부분 위주로 설명하겠습니다.
로그인한 상태라면 먼저 상세 보기 때와 마찬가지로 게시물을 가져옵니다. 그런 다음 로그인 한 사용자가 게시물 작성자 본인이 맞는지 확인합니다. 본인이 아니라면 경고창을 띄우고 뒤로 이동합니다.
부터 수정용 form 태그입니다. 수정 처리는 EditProcess.jsp에서 합니다. form 태그 바로 아래에 hidden 속성의 input 태그가 추가되는데요, 선택된 게시물의 일련번호를 EditProcess.jsp에 그대로 전달하는 역할을 합니다.
마지막으로 기존 게시물의 제목과 내용을 각각의 입력폼에 미리 채워넣습니다. input 태그에서는 value 속성을 이용했고 textarea 태그에서는 여는 태그와 닫는 태그 사이의 콘텐츠 부분에 값을 삽입했습니다. input 태그와는 다르게 시작 태그와 종료 태그 사이에 텍스트가 들어가므로 반드시 공백space 없이 작성해야 합니다.
그럼 상세 보기 페이지에서 [수정하기] 버튼을 눌러보겠습니다. 만약 로그인이 해제되었다면 반드시 로그인을 하셔야 합니다. 수정하기 페이지는 다음과 같습니다.
8.6.2 DAO 준비
게시물을 수정하는 메서드를 BoardDAO 클래스에 추가하겠습니다. 계속 반복되는 패턴이므로 이제 눈에 많이 익을 것입니다.
예제 8-15 DAO에 수정하기 메서드 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...

public class BoardDAO extends JDBConnect {
    ... 생략 ...

    // 지정한 게시물을 수정합니다.
    public int updateEdit(BoardDTO dto) { ❶
        int result = 0;

        try {
            // 쿼리문 템플릿 ❷
            String query = "UPDATE board SET "
                + " title=?, content=? "
                + " WHERE num=?";

            // 쿼리문 완성 ❸
            psmt = con.prepareStatement(query); 
            psmt.setString(1, dto.getTitle()); 
            psmt.setString(2, dto.getContent()); 
            psmt.setString(3, dto.getNum());

            // 쿼리문 실행 ❹
            result = psmt.executeUpdate();
        }
        catch (Exception e) {
            System.out.println("게시물 수정 중 예외 발생"); 
            e.printStackTrace();
        }

        return result; // 결과 반환 ❺ 
    }
}
이번 메서드가 매개변수로 받는 DTO 객체에는 수정할 내용이 담겨 있습니다.
그다음은 지금까지와 같은 패턴입니다. UPDATE 쿼리문을 작성하는데, 이때 제목, 내용, 일련번호를 인파라미터로 입력한 후 쿼리를 실행합니다. 반환하는 값은 업데이트된 행의 개수입니다.
8.6.3 수정 처리 페이지 작성
앞 절에서 DAO에 추가한 메서드를 이용해서 수정 처리 페이지를 작성하겠습니다.
예제 8-16 수정 처리 페이지 WebContent/08Board/EditProcess.jsp
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%> ❶
<%
// 수정 내용 얻기
String num = request.getParameter("num"); ❷
String title = request.getParameter("title"); ❷
String content = request.getParameter("content"); ❷

// DTO에 저장 ❷
BoardDTO dto = new BoardDTO(); ❷
dto.setNum(num); ❷
dto.setTitle(title); ❷
dto.setContent(content); ❷

// DB에 반영
BoardDAO dao = new BoardDAO(application); ❸
int affected = dao.updateEdit(dto); ❸
dao.close();

// 성공/실패 처리
if (affected == 1) {
    // 성공 시 상세 보기 페이지로 이동
    response.sendRedirect(“View.jsp?num=” + dto.getNum()); ❹
}
else {
    // 실패 시 이전 페이지로 이동
    JSFunction.alertBack(“수정하기에 실패하였습니다.”, out); ❺
}
%>
로그인한 상태인지 확인하기 위해 IsLoggedIn.jsp를 인클루드합니다.
그다음 로직은 아주 직관입니다. 먼저 폼값을 받은 후 DTO 객체에 저장하고, DAO 객체를 생성해 updateEdit() 메서드를 호출합니다. 이때 문제없이 수정했다면 1이 반환됩니다. 수정에 성공하면 상세 보기 페이지로 이동하고, 실패하면 이전 페이지로 이동합니다.
8.6.4 동작 확인
지금까지 작성한 수정 기능을 테스트해보겠습니다. 수정하기 페이지를 테스트하려면 ‘목록 보기’ → ‘상세 보기’ → ‘수정하기’ 단계를 거쳐야 합니다.
To Do 01 목록 보기(List.jsp)를 실행합니다.
Note
로그인이 되어 있지 않다면 공통 링크의 [로그인] 클릭 → 로그인(musthave/1234) 공통 링크의 [게시판(페이징X)]를 클릭합니다.
To Do 02 수정하려는 글의 ‘제목’을 클릭해 상세 보기 페이지로 이동합니다.
To Do 03 아래쪽의 [수정하기] 버튼을 클릭합니다.
To Do 04 제목과 내용을 적당히 수정 후 [작성 완료] 버튼을 클릭합니다.
수정이 잘 되었다면 다음과 같이 상세 보기 페이지로 이동합니다.
그림과 같이 수정은 잘 처리되셨나요? 이어서 삭제 기능을 추가해보겠습니다.
Step 5 8.7 동작 확인
삭제는 쓰기나 수정과는 다르게 별도의 폼이 필요하지 않습니다. 회원제 게시판이므로 글을 작성한 본인인지만 확인되면 바로 삭제 처리를 하면 됩니다.
삭제의 시작은 상세 보기 페이지입니다. 로그인 상태에서 본인이 작성한 글을 보면 [수정하기] 버튼과 함께 [삭제하기] 버튼도 표시되는데, 이 버튼을 누르면 삭제 처리용 JSP로 삭제 요청을 보내게 할 것입니다.
8.7.1 삭제하기 버튼에 삭제 요청 로직 달기
먼저 [삭제하기] 버튼 클릭 시 삭제 요청을 보내는 자바스크립트 코드를 상세 보기 페이지(View.jsp)에 추가하겠습니다. 추가한 부분은 음영으로 표시했습니다. 상세 보기 페이지에서 삭제를 위한 부분을 먼저 설명하도록 하겠습니다.
예제 8-17 View.jsp에 삭제하기 자바스크립트 코드 추가 WebContent/08Board/View.jsp
... 생략 ...



 회원제 게시판 


 

회원제 게시판 - 상세 보기(View)

❻ ... 생략 ... ❼ ... 생략 ...
[삭제하기] 버튼을 클릭하면 onclick=“deletePost( );” 코드에 의해 의 deletePost() 함수가 실행됩니다. 앞서 상세 보기 설명 때는 필요가 없어서 본문 부분을 비워뒀던 함수입니다.
코드는 간단합니다. 정말 삭제할 것인지 한번 더 확인한 후, 삭제하겠다고 하면 폼에 전송 방식과 전송경로를 설정한 후 실제로 전송합니다. 이때 hidden 타입으로 정의한 일련번호가 함께 전송됩니다.
8.7.2 DAO 준비
삭제 처리를 위한 메서드를 BoardDAO 클래스에 추가하겠습니다.
예제 8-18 DAO에 삭제하기 메서드 추가 Java Resources/src/model1/board/BoardDAO.java
... 생략 ...

 public class BoardDAO extends JDBConnect {
... 생략 ...

    // 지정한 게시물을 삭제합니다.
    public int deletePost(BoardDTO dto) { ❶
        int result = 0;
        try {
            // 쿼리문 템플릿
            String query = "DELETE FROM board WHERE num=?"; ❷

            // 쿼리문 완성
            psmt = con.prepareStatement(query); 
            psmt.setString(1, dto.getNum()); ❸

            // 쿼리문 실행
            result = psmt.executeUpdate(); ❹
        }
        catch (Exception e) {
            System.out.println("게시물 삭제 중 예외 발생"); 
            e.printStackTrace();
        }

        return result; // 결과 반환 
    }
}
삭제할 게시물의 일련번호와 로그인 아이디를 담은 DTO 객체를 매개변수로 받습니다. DELETE 쿼리문을 작성하고 인파라미터로 일련번호를 설정한 후 쿼리를 실행합니다. DELETE 쿼리는 삭제한 행의 개수를 반환합니다.
8.7.3 삭제 처리 페이지
DAO에 메서드를 추가하였으므로 이를 이용해서 삭제 처리 페이지를 작성하도록 하겠습니다.
예제 8-19 DAO에 삭제하기 메서드 추가 WebContent/08Board/DeleteProcess.jsp
<%@ page import="model1.board.BoardDAO"%>
<%@ page import="model1.board.BoardDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="./IsLoggedIn.jsp"%> ❶
<%
String num = request.getParameter("num"); // 일련번호 얻기 ❷

BoardDTO dto = new BoardDTO(); // DTO 객체 생성 ❸
BoardDAO dao = new BoardDAO(application); // DAO 객체 생성 ❸
dto = dao.selectView(num); // 주어진 일련번호에 해당하는 기존 게시물 얻기 ❸

// 로그인된 사용자 ID 얻기
String sessionId = session.getAttribute("UserId").toString(); ❹

int delResult = 0;

if (sessionId.equals(dto.getId())) { // 작성자가 본인인지 확인 ❺
    // 작성자가 본인이면...
    dto.setNum(num);
    delResult = dao.deletePost(dto); // 삭제!!! ❻
    dao.close();

    // 성공/실패 처리
    if (delResult == 1) {
        //성공 시 목록 페이지로 이동 ❼
        JSFunction.alertLocation("삭제되었습니다.", "List.jsp", out); 
    } else {
        //실패 시 이전 페이지로 이동 ❽
        JSFunction.alertBack("삭제에 실패하였습니다.", out); 
    }
}
else {
    // 작성자 본인이 아니라면 이전 페이지로 이동 ❾
    JSFunction.alertBack("본인만 삭제할 수 있습니다.", out);

    return; 
}
%>
삭제 처리 페이지도 로그인 상태에서만 접근할 수 있도록 했습니다.
이어서 각종 데이터를 준비합니다. 요청 객체로부터 폼값, 정확하게는 일련번호를 받습니다. DTO, DAO 객체를 생성한 후 selectView() 메서드를 호출하여 기존 게시물을 가져옵니다.
그리고 session 영역에 저장된 로그인 아이디를 가져옵니다.
로그인된 아이디와 게시물 작성자가 같은지 확인하여 작성자 본인이 맞으면 deletePost() 메서드를 호출하여 게시물을 삭제합니다. 삭제에 성공하면 목록 페이지로 이동하고 실패라면 뒤로 이동합니다.
작성자 본인이 아닌 경우에도 뒤로 이동합니다.
8.7.4 동작 확인
To Do 01 목록 보기(List.jsp)를 실행합니다.
Note
로그인이 되어 있지 않다면 공통 링크의 [로그인] 클릭 → 로그인(musthave/1234) 공통 링크의 [게시판(페이징X)]를 클릭합니다.
To Do 02 삭제하려는 글의 ‘제목’을 클릭해 상세 보기 페이지로 이동합니다.
To Do 03 [삭제하기] 버튼을 클릭합니다. 그러면 정말로 삭제할지 다시 한번 묻는 창이 뜹니다.
To Do 04 [확인] 버튼을 클릭합니다. 삭제가 완료됐다는 창이 뜹니다.
To Do 05 [확인] 버튼을 클릭합니다. 목록 보기 화면으로 이동하며, 방금 삭제한 게시물은 목록에서 사라졌음을 확인할 수 있습니다.
이로써 게시판의 기본 기능은 모두 완성되었습니다. 하지만 아직 한 가지 더 처리할 것이 남았습니다. 바로 목록이 길 때 페이지 단위로 나눠 보여주는 기능인데, 잠시 쉬었다가 다음 장에서 이어서 진행하겠습니다.
학습 마무리
이번 장에서는 모델1 방식의 회원제 게시판을 제작해보았습니다. 게시판은 단순히 글을 작성하는 일뿐만 아니라 데이터를 관리하는 용도로도 사용할 수 있습니다. 즉, 이번에 제작한 게시판은 여러분이 앞으로 여러 형태의 관리 프로그램을 제작할 때 가장 기본적인 모델이 될 것입니다.
핵심 요약
목록 보기와 상세 보기는 로그인 없이 접근할 수 있습니다.
글쓰기는 로그인 후 할 수 있습니다.
수정과 삭제는 로그인 후 본인이 작성한 게시물에 한해서만 가능합니다.
로그인 시 session 내장 객체와 session 영역을 사용합니다.
데이터베이스 연결을 위한 설정값은 web.xml에 저장해두면 코드 수정 없이 데이터베이스 연결 정보를 수정할 수 있습니다. 이 점은 서비스 운영 시 큰 도움을 줍니다.
게시판은 사용자가 입력한 내용을 쿼리에 반영해 실행합니다. 그래서 동적 쿼리를 뜻하는 PreparedStatement 인터페이스를 주로 사용합니다.
경고창(alert), 페이지 이동(location.href)과 같이 자주 사용하는 자바스크립트 메서드는 별도의 유틸리티 클래스로 만들어두면 재사용성이 높아집니다.

성낙현
월드컵으로 뜨거웠던 2002년부터 웹 개발을 시작하여 에듀웰 모바일 웹 기획 및 개발, 마이닥터 웹/앱 개발(앱스토어 의료부문 1위), 국민건강보험 건강마라톤대회 앱 개발 등 다양한 앱 개발과 프로젝트를 두루 경험하였다. 현재는 한국소프트웨어인재개발원에서 JAVA 개발자 과정을 강의하고 있다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내