본문 바로가기
05_Server (04. JSP 프로그래밍 구현)

[2-1] 공지사항서비스 기본세팅

by moca7 2024. 9. 10.

 

 

 

ㅁ 공지사항서비스
- 공지사항서비스_목록조회요청
- 공지사항서비스_작성요청
- 공지사항서비스_수정요청
- 공지사항서비스_삭제요청

 

 

 

 

 

ㅁ 개발은 화면설계, 화면구현 후 각 화면별로 어떤 쿼리가 실행될지 예상해서 쿼리도 미리 작성할 수 있어야 한다.

- 이게 제일 좋은 방식이다. 쿼리를 먼저 작성해두면 이 페이지에서 요청을 보낼 때 어떤 데이터가 필요한지 알 수 있다.

 

 

 

ㅁ 패키지, 클래스, xml 파일을 만든다.

 

 

 

 

 

ㅁ 공지사항 서비스에서는 작성, 수정, 삭제, 조회 기능을 넣는다. 그에 맞는 쿼리를 먼저 작성해본다.

 

 

- 아래는 /webApp/src/main/java/db/mappers/notice-mapper.xml이다.

 

- notice-mapper.xml 문서의 유형을 프로퍼티스 유형으로 해야 한다.

- member-mapper.xml 파일에서 상단의 <!DOCTYPE properties ~~>를 복사해온다.

 

 

 

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>

</properties>
 

 

- dtd는 현재 이 프로퍼티스 유형의 xml에 적합한 태그가 쓰이고 있는지를 내부적으로 유효성 체크해준다.

- 최상위 태그로 <properties>도 만든다.

 

 

 

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>

    <entry key="selectNoticeList">
        SELECT
                       NOTICE_NO
                     , NOTICE_TITLE
                     , NOTICE_CONTENT
                     , USER_ID
                     , REGIST_DATE
           FROM
                       NOTICE N
             JOIN MEMBER ON (NOTICE_WRITER = USER_NO)
         WHERE
                       N.STATUS = 'Y'
         ORDER
                 BY NOTICE_NO DESC
    </entry>
 
   
    <entry key="insertNotice">
        INSERT
            INTO NOTICE
          (
            NOTICE_NO
          , NOTICE_TITLE
          , NOTICE_CONTENT
          , NOTICE_WRITER
          )
          VALUES
          (
            SEQ_NNO.NEXTVAL
          , ?
          , ?
          , ?
          )
    </entry>
 
   
    <entry key="selectNoticeByNo">
        SELECT
                       NOTICE_NO
                     , NOTICE_TITLE
                     , NOTICE_CONTENT
           FROM
                       NOTICE
        WHERE
                       NOTICE_NO = ?
    </entry>
 
   
    <entry key="updateNotice">
        UPDATE
                       NOTICE
               SET
                       NOTICE_TITLE = ?
                     , NOTICE_CONTENT = ?
         WHERE
                       NOTICE_NO = ?
    </entry>
 
   
    <entry key="deleteNotice">
        UPDATE
                       NOTICE
               SET
                       STATUS = 'N'
         WHERE
                       NOTICE_NO = ?
    </entry>
   
   

</properties>
 

 

 

- 회원서비스 때는 요청을 보내고 그때마다 실행할 쿼리문을 작성했지만 

원래는 화면설계, 화면구현, db설계, db구축이 끝나면 미리 실행시킬 쿼리를 다 작성하고 쿼리테스트를 해주는게 좋다.

- 쿼리테스트는 실행할 sql문을 작성해보고, sql developer의 워크시트에 복붙해서 잘 실행되는지 확인해보면 된다.

 

- "selectNoticeList"에서 USER_ID는 NOTICE 테이블이 아닌 MEMBER 테이블에 있어서 조인했다.

이때 STATUS 컬럼이 NOTICE에도 있고 MEMBER에도 있어서 어느 테이블의 컬럼인지 표시했다.

- "insertNotice"에서 REGIST_DATE, STATUS는 기본값이 있어서 insert 하지 않았다.

- "selectNoticeByNo"에서 공지사항 수정페이지에 보여질 제목과 내용을 조회해온다.

글번호는 사실 화면에 노출되지 않지만, PK 컬럼은 항상 조회하는 습관을 들여야 한다.

언제 글번호가 필요할지 모르기 때문이다.

 

 

 

 

 

 

 

ㅁ VO객체인 Notice.java를 작성한다.

 

- 아래는 /webApp/src/main/java/com/br/web/notice/model/vo/Notice.java다.

 
package com.br.web.notice.model.vo;

import java.sql.Date;

public class Notice {
   
    private int noticeNo;
    private String noticeTitle;
    private String noticeContent;
    private String noticeWriter; // 작성요청시 회원번호 | 조회요청시 아이디
    private Date registDt;
    private String status;
   
    public Notice() {}

    public Notice(int noticeNo, String noticeTitle, String noticeContent, String noticeWriter, Date registDt,
            String status) {
        super();
        this.noticeNo = noticeNo;
        this.noticeTitle = noticeTitle;
        this.noticeContent = noticeContent;
        this.noticeWriter = noticeWriter;
        this.registDt = registDt;
        this.status = status;
    }

    public Notice(int noticeNo, String noticeTitle, String noticeContent, String noticeWriter, Date registDt) {
        super();
        this.noticeNo = noticeNo;
        this.noticeTitle = noticeTitle;
        this.noticeContent = noticeContent;
        this.noticeWriter = noticeWriter;
        this.registDt = registDt;
    }

    public int getNoticeNo() {
        return noticeNo;
    }

    public void setNoticeNo(int noticeNo) {
        this.noticeNo = noticeNo;
    }

    public String getNoticeTitle() {
        return noticeTitle;
    }

    public void setNoticeTitle(String noticeTitle) {
        this.noticeTitle = noticeTitle;
    }

    public String getNoticeContent() {
        return noticeContent;
    }

    public void setNoticeContent(String noticeContent) {
        this.noticeContent = noticeContent;
    }

    public String getNoticeWriter() {
        return noticeWriter;
    }

    public void setNoticeWriter(String noticeWriter) {
        this.noticeWriter = noticeWriter;
    }

    public Date getRegistDt() {
        return registDt;
    }

    public void setRegistDt(Date registDt) {
        this.registDt = registDt;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Notice [noticeNo=" + noticeNo + ", noticeTitle=" + noticeTitle + ", noticeContent=" + noticeContent
                + ", noticeWriter=" + noticeWriter + ", registDt=" + registDt + ", status=" + status + "]";
    }
   
}

 

 

- 하나의 공지사항에 대한 데이터를 담을 수 있는 클래스를 작성한다.

- Date는 java.sql.Date로 import 해야 db로부터 DATE 타입의 컬럼을 조회해서 담을 수가 있다.

- 필드는 sql developer에서 SERVER 계정의 NOTICE 테이블의 열 탭에서 COLUMN_NAME을 복사해와서 따서 작성한다.

- NOTICE_WRITER 컬럼(공지사항글작성자번호)은 MEMBER 테이블의 회원번호만 들어올 수 있게 외래키 제약조건이 걸려 있다. 

- 필드 구성이 끝났으면 기본 생성자, 매개변수 생성자, 게터세터, toString 메소드를 만든다.

 

 

- NOTICE_WRITER에는 '1', '2'가 들어있다. 그런데 int형으로 선언하지 않고 String으로 선언한다. 

- noticeWriter 변수에 두 가지 경우에 데이터를 다르게 담을 예정이다.

db에 insert할 때는 데이터를 담아가야 하기 때문에 회원번호를 담고,

db에 select할 때는 조인해서 작성자의 아이디를 담아서 가져온다. 그 때는 문자열이 담겨야 한다. 

- 이렇게 한 필드를 두 용도로 쓰지 않고, 회원번호를 담는 필드, 회원아이디를 담는 필드 두개로 쪼개도 된다.

 

 

 

 

 

ㅁ com.br.web.notice.model.service의 NoticeService.java 초기세팅 

 

 
package com.br.web.notice.model.service;

import java.sql.Connection;
import java.util.List;

import com.br.web.notice.model.dao.NoticeDao;
import com.br.web.notice.model.vo.Notice;

import static com.br.web.common.template.JDBCTemplate.getConnection;
import static com.br.web.common.template.JDBCTemplate.close;
import static com.br.web.common.template.JDBCTemplate.commit;
import static com.br.web.common.template.JDBCTemplate.rollback;

public class NoticeService {
   
    private NoticeDao nDao = new NoticeDao();
   
}

 

- dao 객체를 전역 변수로 미리 만들어 놓는다. 

 

 

 

 

 

 com.br.web.notice.model.dao의 NoticeDao.java 초기세팅

 

 
package com.br.web.notice.model.dao;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import com.br.web.notice.model.vo.Notice;

import static com.br.web.common.template.JDBCTemplate.close;

public class NoticeDao {
   
    private Properties prop = new Properties();   //   생성만 하면 텅 빈 상태다.
 
   
    public NoticeDao() {
 
        try {
            prop.loadFromXML(new FileInputStream(NoticeDao.class.getResource("/db/mappers/notice-mapper.xml").getPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }


}

 

 

- 쿼리문을 읽어들인 Properties 객체는 NoticeDao 클래스 내의 모든 메소드에서 사용해야하기 때문에 전역변수로 선언한다.

- 기본생성자로 쿼리문(notice-mapper.xml)을 Properties 객체로 읽어들인다.

- loadFromXml() 메소드를 이용해서 xml 파일로부터 데이터를 읽어온다.

이때 그 파일과 직접적으로 연결되는 FileInputStream 객체가 필요하다.

 

- FileInputStream에는 읽어들이고자 하는 파일의 물리적인 경로를 제시한다.

근데 읽어들이는 파일은 src/main/java의 db.mappers 안의 notice-mapper.xml이 아니고,

실제 배포되는 webApp\src\main\webapp\WEB-INF\classes\db\mappers 안의 notice-mapper.xml이다.

- NoticeDao.class.getResource() 메소드의 괄호 안의 경로 중 "슬래시"가 곧 "classes" 폴더를 가리킨다. 

현재 작업중인 곳은 NoticeDao.class 파일이다.

슬래시는 이 파일이 컴파일된 클래스 파일의 루트 폴더인 classes 폴더를 가리킨다.

(읽어들여야 하는 파일은 지금 작업중인 파일이 아니라, 동기화된 실제 배포되는 파일이다)

 

- getResource() 메소드에 의해 notice-mapper.xml 파일이 찾아지고,

이 파일의 물리적인 경로를 알아내고자 한다면 getPath() 메소드까지 호출해야 한다.

- getResource() 메소드에 경로를 오타 등으로 잘못제시하면 null이 반환되고, null pointer exception이 발생한다.

 

- NoticeDao 측의 메소드가 실행되기 전에 매번 NoticeDao 객체가 먼저 생성이 되기 때문에

notice-mapper.xml 파일에 기술되어 있는 key-value 세트는 매번 실시간으로 읽어들여진다.

 

 

 

 

 

ㅁ 공지사항 화면구현 살펴보기

 

 

 

- src/main/webapp/view/notice에 화면 구현하며 만들었던 html 파일들이 있다.

아직 jsp로 바꾸고 기능을 입히기 전의 html 파일들이다.

- 지금 html 파일들은 그냥 더블클릭해서 열어봐도 되지만, 

jsp로 변환하고 기능구현이 들어갔다면 더블클릭으로 열수 없다.

항상 서버에 url 요청을 해서 페이지를 열어야 한다.

 

 

 

 

- "공지사항목록페이지.html"을 브라우저로 열어봤다.

- 글번호, 글제목, 작성자 번호(회원번호)가 아닌 작성자(회원 id), 작성일, 글내용이 필요하다.

db의 NOTICE 테이블에 저장된 컬럼은 회원번호가 저장되어 있다. 회원id 데이터는 없다.

 

- 화면구현한 것을 보고 쿼리문에 어떤 데이터가 필요한지 파악하고 쿼리문을 작성한다.

(+ db에 구축한 테이블의 컬럼들도 본다)

- 별도의 정렬기준을 제시하지 않으면 그 테이블에 존재하는 순서대로 조회된다.

그런데 화면 구현된 바로는 글번호가 높은 순으로 보여지고 있다. (최신글 순으로 보여진다)

 

 

 

 

- "공지사항수정페이지.html"을 브라우저로 열어봤다. 

- 공지사항 수정, 등록 페이지는 관리자만 가능하게 할 예정이다. 

 

- 수정 페이지에는 기존의 공지사항 제목과 내용이 보여져야 한다.

그래서 쿼리문에 "selectNoticeByNo"로 수정 페이지 진입시 필요한 데이터를 조회해온다.

전체 공지사항 게시글이 아닌 특정 공지사항 게시글 하나만을 조회해서 가져올 수 있게끔 한다.

- 글번호는 사실 화면에 노출되지 않지만, PK 컬럼은 항상 조회하는 습관을 들여야 한다.

언제 글번호가 필요할지 모르기 때문이다.

 

 

 

 

- "공지사항작성페이지.html"을 브라우저로 열어봤다.

- 여기엔 제목과 내용밖에 없지만 db의 NOTICE 테이블도 참고해서 insert할 때 필요한 다른 컬럼들도 쿼리문에 작성한다.