Java - JDBC MVC 예제!

데이터 객체

DTO(data transfer object)

  • 웹브라우저(클라이언트)
  • 웹서버 비지니스 로직(미들웨어)
  • DB서버

위 프로세스 간에 데이터를 전달하는 데이터 전송 객체, 데이터를 주고받을때 DTO를 사용한다.

javaBeans Data

데이터의 효율적인 관리를 위한 Java클래스
데이터 표현부분, 처리부분으로 나뉜다.

빈즈 규칙
기본 생성자가 반드시 존재해야한다. (인자가없는 디폴트 생성자)
속성을 접급한고 꺼내올수있는 getter setter 메서드를 구성한다.
속성을 지정(private)해야하는데 속성 이름은 보통 html의 form태그안에서 input 태그의 name속성 값과 같게 정한다.

POJO(Plain Old Java Object)

오래된 방식의 간단한 자바 오브젝트
Java *EE 등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 “무거운” 객체를 만들게 된 것에 반발해서 사용되게 된 용어이다.(위키)

*EE : Enterprise Edition 기업형 환경, 무겁고 느린 의미…
즉 POJO는 JAVA EE에서 분산처리하기 위해, 무겁고 느린 환경에서 간단하게 데이터를 처리하기 위한 객체를 POJO라 한다.
POJO를 DTO라 불러도 상관 없다(DTO가 더 넓은 개념)

VO(Value Object)

Data에 쓰고 읽기 위한 객체, 포괄적인 의미로 데이터를 관리하는 객체는 모두 VO에 포함된다 할 수 있다.
DTO, java Beans, POJO 등을 VO에 포함된다 할 수 있다.

DAO(data access object)

DAO는 받은 DTO를 가지고 DB와 연동하여 CRUD 를 위한 데이터 접근 객체.

예제

EMP테이블과 DEPT테이블을 사용해서 다음과 같은 화면을 출력하고 싶다.

부서번호: 10, 부서명: OPERATIONS, 지역: BOSTON, 사원수: 3
 empno=7369, ename=SMITH, job=CLERK, mgr=0, hiredate=1980-12-17, sal=800, deptno=20
 empno=7566, ename=JONES, job=MANAGER, mgr=0, hiredate=1981-04-02, sal=2975, deptno=20
 empno=7902, ename=FORD, job=ANALYST, mgr=0, hiredate=1981-12-03, sal=3000, deptno=20

부서번호: 20, 부서명: RESEARCH, 지역: DALLAS, 사원수: 3
 ...

...

dept테이블의 데이터를 저장할 deptDTO,
emp테이블의 데이터를 저장할 empDTO를 만들자. desc dept;

이름     널?       유형 
------ -------- ------------ 
DEPTNO NOT NULL NUMBER(2)    
DNAME           VARCHAR2(14) 
LOC             VARCHAR2(13) 

desc emp

이름       널?       유형           
-------- -------- ------------ 
EMPNO    NOT NULL NUMBER(4)    
ENAME             VARCHAR2(10) 
JOB               VARCHAR2(9)  
MGR               NUMBER(4)    
HIREDATE          DATE         
SAL               NUMBER(7,2)  
COMM              NUMBER(7,2)  
DEPTNO            NUMBER(2)    

위 칼럼들을 저장할 DTO클래스를 만들면 된다.

DeptDTO 클래스 정의

public class DeptDTO {
    int deptno;
    String dname;
    String loc;
    int empCnt;


    @Override
    public String toString() {
        return "DeptDTO [deptno=" + deptno + ", dname=" + dname + ", loc=" + loc + ", empCnt=" + empCnt + "]";
    }
    
    public String getDname() {
        return dname;
    }
    public void setDname(String dname) {
        this.dname = dname;
    }
    ...
}

EmpDTO 클래스 정의

public class EmpDTO {
    private int empno;
    private String ename;
    private String job; 
    private int mgr;
    private Date hiredate;
    private int sal;
    private int comm;
    private int deptno;
    
    
    
    @Override
    public String toString() {
        return "EmpDTO [empno=" + empno + ", ename=" + ename + ", job=" + job + ", mgr=" + mgr + ", hiredate="
                + hiredate + ", sal=" + sal + ", deptno=" + deptno + "]";
    }
    public Date getHiredate() {
        return hiredate;
    }
    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
    ...
}

예제대로 출력하기 위해서 Subquery Cursor를 사용하면 편할듯 하다.

https://kouzie.github.io/database/DB-14일차/#cursorsubquery

SELECT deptno, dname,loc, CURSOR(
    SELECT ROWNUM, empno, ename, job,  sal+nvl(comm, 0) sal
    FROM emp e
    WHERE deptno = d.deptno
) users
FROM dept d
ORDER BY deptno;

DB출력값

10    ACCOUNTING    NEW YORK    {<ROWNUM=1,EMPNO=7782,ENAME=CLARK,JOB=MANAGER,SAL=2450>,<ROWNUM=2,EMPNO=7839,ENAME=KING,JOB=PRESIDENT,SAL=5000>,<ROWNUM=3,EMPNO=7934,ENAME=MILLER,JOB=ARTIST,SAL=1300>,}
20    RESEARCH    DALLAS    {<ROWNUM=1,EMPNO=7369,ENAME=SMITH,JOB=CLERK,SAL=800>,<ROWNUM=2,EMPNO=7566,ENAME=JONES,JOB=MANAGER,SAL=2975>,<ROWNUM=3,EMPNO=7902,ENAME=FORD,JOB=ANALYST,SAL=3000>,}
30    SALES    CHICAGO    {<ROWNUM=1,EMPNO=7499,ENAME=ALLEN,JOB=SALESMAN,SAL=1900>,<ROWNUM=2,EMPNO=7521,ENAME=WARD,JOB=SALESMAN,SAL=1750>,<ROWNUM=3,EMPNO=7654,ENAME=MARTIN,JOB=SALESMAN,SAL=2650>,<ROWNUM=4,EMPNO=7698,ENAME=BLAKE,JOB=MANAGER,SAL=2850>,<ROWNUM=5,EMPNO=7844,ENAME=TURNER,JOB=SALESMAN,SAL=1500>,<ROWNUM=6,EMPNO=7900,ENAME=JAMES,JOB=CLERK,SAL=950>,}
40    OPERATIONS    BOSTON    {}

먼저 sql쿼리를 수행해줄 StringBuffer를선언하고 결과값을 받을 HashMapArrayList,
데이터 받는걸 도와줄 DTO 객체를 선언!

public class Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append(" SELECT deptno, dname, loc, CURSOR( ");
        sb.append(" SELECT ROWNUM, empno, ename, job, hiredate, sal+nvl(comm, 0) sal");
        sb.append(" FROM emp e ");
        sb.append(" WHERE deptno = d.deptno) users ");
        sb.append(" FROM dept d ");
        sb.append(" ORDER BY deptno ");
        //HashMap을 사용해서 dept와 ArrayList emp 을 넣어서 만들어보자.
        HashMap<DeptDTO, ArrayList<EmpDTO>> hmap = new HashMap<>();

        ArrayList<EmpDTO> arr_edto = null;
        DeptDTO ddto = null;
        EmpDTO edto = null;
        ...

Conncetion, Statement를 사용해서 쿼리를 수행하고 값을 받아오자.

...
Connection conn = DBConn.getConnection();
Statement stmt = null;
try {
    stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(sb.toString());
    while (rs.next()) 
    {
        ddto = new DeptDTO();
        ddto.setDeptno(rs.getInt("deptno"));
        ddto.setDname(rs.getString("dname"));
        ddto.setLoc(rs.getString("loc"));

        ResultSet rs2 = (ResultSet) rs.getObject("users");
        if(rs2.next())
        {
            arr_edto = new ArrayList<>();
            do
            {
                edto = new EmpDTO();
                edto.setEmpno(rs2.getInt("empno"));
                edto.setEname(rs2.getString("ename"));
                edto.setHiredate(rs2.getDate("hiredate"));
                edto.setSal(rs2.getInt("sal"));
                edto.setJob(rs2.getString("job"));
                edto.setDeptno(ddto.getDeptno());
                arr_edto.add(edto);
            }while(rs2.next());
            ddto.setEmpCnt(arr_edto.size());
        }
        hmap.put(ddto, arr_edto);
        arr_edto = null;
    }
} 
catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
finally {
    try {
        stmt.close();
        conn.close();
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
...

마지막의 서브쿼리 커서로 수행된 결과를 가져올땐 getObject 함수를 사용하면 된다.
ResultSet rs2 = (ResultSet) rs.getObject("users"); 로 형변환 해서 기존의 ResultSet처럼 똑같이 사용하면 됨.

주의할점은 40번 부서의 경우 users로 가져오는 값이 없다. if과 rs2.next() 함수로 가져온 값이 null인지 확인하고
아닐경우 do..while문으로 ArrayList에 값을 집어넣자.

참고: ResultSet의 next()함수라 get…() 함수를 사용할땐 항상 Connection이 열려있어야 한다.
그렇지 않을경우 예외 발생 java.sql.SQLRecoverableException: 접속 종료
conn.close()함수로 강제로 끊으면 rs의 모든 함수를 즉시 사용불가능되지만 서비스에서 내리면 조금 출력하다가 예외 발생.
참고: https://stackoverflow.com/questions/25493837/java-cant-use-resultset-after-connection-close

출력부분은 간단하다, HashMap으로 부터 entry를 가져와서 출력

...
Set<Entry<DeptDTO, ArrayList<EmpDTO>>> eset = hmap.entrySet();
Iterator<Entry<DeptDTO, ArrayList<EmpDTO>>> eit = eset.iterator();
while (eit.hasNext()) {
    Entry<DeptDTO, ArrayList<EmpDTO>> entry = eit.next();
    System.out.println(entry.getKey());
    try {
        Iterator<EmpDTO> arrit = entry.getValue().iterator();
        while (arrit.hasNext()) {
            EmpDTO empDTO = arrit.next();
            System.out.print("  ");
            System.out.println(empDTO);
        }
        System.out.println();
    } catch (NullPointerException e) {
        System.out.println("사원이없습니다.");
        System.out.println();
    }
}

마찬가지로 40번부서의 경우 getValue()에서 null에러가 뜸으로 예외가 발생 할 수 있다.

출력값

DeptDTO [deptno=40, dname=OPERATIONS, loc=BOSTON, empCnt=0]
사원이없습니다.

DeptDTO [deptno=20, dname=RESEARCH, loc=DALLAS, empCnt=3]
  EmpDTO [empno=7369, ename=SMITH, job=CLERK, mgr=0, hiredate=1980-12-17, sal=800, deptno=20]
  EmpDTO [empno=7566, ename=JONES, job=MANAGER, mgr=0, hiredate=1981-04-02, sal=2975, deptno=20]
  EmpDTO [empno=7902, ename=FORD, job=ANALYST, mgr=0, hiredate=1981-12-03, sal=3000, deptno=20]

DeptDTO [deptno=10, dname=ACCOUNTING, loc=NEW YORK, empCnt=3]
  EmpDTO [empno=7782, ename=CLARK, job=MANAGER, mgr=0, hiredate=1981-06-09, sal=2450, deptno=10]
  EmpDTO [empno=7839, ename=KING, job=PRESIDENT, mgr=0, hiredate=1981-11-17, sal=5000, deptno=10]
  EmpDTO [empno=7934, ename=MILLER, job=ARTIST, mgr=0, hiredate=1982-01-23, sal=1300, deptno=10]

DeptDTO [deptno=30, dname=SALES, loc=CHICAGO, empCnt=6]
  EmpDTO [empno=7499, ename=ALLEN, job=SALESMAN, mgr=0, hiredate=1981-02-20, sal=1900, deptno=30]
  EmpDTO [empno=7521, ename=WARD, job=SALESMAN, mgr=0, hiredate=1981-02-22, sal=1750, deptno=30]
  EmpDTO [empno=7654, ename=MARTIN, job=SALESMAN, mgr=0, hiredate=1981-09-28, sal=2650, deptno=30]
  EmpDTO [empno=7698, ename=BLAKE, job=MANAGER, mgr=0, hiredate=1981-05-01, sal=2850, deptno=30]
  EmpDTO [empno=7844, ename=TURNER, job=SALESMAN, mgr=0, hiredate=1981-09-08, sal=1500, deptno=30]
  EmpDTO [empno=7900, ename=JAMES, job=CLERK, mgr=0, hiredate=1981-12-03, sal=950, deptno=30]

순서대로 출력을 원한다면 HashMap이 아닌 LinkedHashMap을 사용하자.

//HashMap<DeptDTO, ArrayList<EmpDTO>> hmap = new HashMap<>();
LinkedHashMap<DeptDTO, ArrayList<EmpDTO>> hmap = new LinkedHashMap<>();

MVC (모델-뷰-컨트롤러) 패턴

모델-뷰-컨트롤러(Model–View–Controller, MVC)는 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴이다.(위키)

Model이 처리
View가 출력
Controller는 총괄적인 역할을 한다.

MVC 패턴을 사용하면 오히려 개발속도는 더디지만 나중에 유지보수를 생각하면 사용하는게 더 효율적이다.

예로 게시글을 MVC패턴으로 만든다면 다음과 같다.

게시글 목록 보겠다고 요청이 들어오면
Controller가 요청을 받으면 Model에게 데이터 처리를 맞긴다.

ControllerModel로부터 데이터를 받으면 View에게 출력할 화면을 만들라고 일처리를 맞긴다.

View가 이쁘게 출력할수 있도록 구성이 끝나면 컨트롤러가 마지막으로 이 데이터를 요청자에게 반환한다.

tbl_board 테이블을 MVC패턴으로 구성해보자.


게시판 MVC 패턴으로 구성하기

tbl_board 은 다음처럼 생겼다.

image1

테이블 생성은 https://kouzie.github.io/database/DB-11일차/#게시판-테이블-설계하기

게시판 기능을 나름 MVC형태로 구현하려면
총 6개의 클래스가 필요하다.

  1. MyBoardDTO
  2. IMyBoardDAO
  3. MyBoardDAOImpl
  4. MyBoardService
  5. MyBoardController
  6. mainClass(메인함수)

MyBoardDTO는 tbl_board 테이블에서 데이터를 받아오기 위한 객체.

IMyBoardDAO안에는 게시글에서 할수있는 작업을 인터페이스로 선언한다.
게시글하면 떠오르는 다음 작업들이 기본적으로 정의되어야 한다.

게시글 작성
게시글 목록
게시글 수정
게시글 삭제
게시글 조회수 증가
게시글 보기
총게시글 수 반환
총페이지 수 반환

이런 기본적인 작업은 인터페이스에서 정의하고 이를 구현하는 클래스를 정의하는 것이 좋다.

MyBoardImplIMyBoardDAO 구현한 DB와 연결 및 각종 작업별로 데이터를 가져오는 클래스이다.

MyBoardService 클래스는 DAO의 보조적인 역할을 하는 클래스이다.
굳이 없어도 되지만 있으면 좋은?
게시글에서 글을 클릭하면 하는 행동은 게시글 상세출력 하나로 볼 수 있지만 실제로 DAO안에선 조회수 증가, 실제글 출력 2가지 작업이 수행된다.

만약 로그를 추가한다던가 다른 특이작업을 한다면 한 행동안에 2개 이상의 작업이 추가될 수 있다.(물론 DAO에 추가 정의해야함)

이 여러개의 작업이 하나의 행동으로, 하나의 트랜잭션으로 묶어 동작해야 하는데 MyBoardService가 이를 도와준다.

MyBoardController는 말 그대로 컨트롤러 역할을 하는 클래스, 단 지금은 View의 역할도 같이 해준다.

mainClass는 클라이언트 역할을 해줄 조회/추가/삭제/수정 요청을 하는 클래스



Sequence Diagram

그럼 흐름은 다음과 같이 된다.

MainClass에서 조회/추가/삭제/수정 신청을 하면

MyBoardController가 요청을 받는다. 받은 요청을 MyBoardService객체에게 전달

MyBoardService는 요청을 받아 함수를 호출하고 이 함수안에선 MyBoardDAOImpl의 함수를 호출하게 된다.

MyBoardDAOImpl는 DB서버에서 데이터를 처리(조회의 경우 조회수올리고 데이터 SELECT)하고 MyBoardService에게 결과를 리턴한다.

MyBoardService는 리턴결과를 다시 MyBoardController에게 보낸다.

MyBoardControllerMainClass에게 출력할 데이터를 보낸다.
(View의 역할을 MyBoardController이 해주는 걸로….)

이런 흐름을 Sequence Diagram이라 하는데 그림을 그리면 협엽하기 좋다.

image1

https://blog.naver.com/ajh794/221000273308


MyBoardDTO 정의

DTO 개념은 DB로 부터 읽어온 레코드를 저장할 객체임으로 다음과 같이 정의하자.

간편하게 출력하기 위해 toString()도 오버라이딩 하자.

public class MyBoardDTO{
    private int no;
    private int seq;
    private String name;
    private String password;
    private String content;
    private String subject;
    private String email;
    private int cnt;
    
    @Override
    public String toString() {
        
        return String.format("%2d  %4d %20s %8s %15s  %tF  %d", no, seq, subject, name, email, regDate, cnt);
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    ...

DTO 클래스 설명은 생략….

https://kouzie.github.io/jdbc/JDBC.-3일차/


MainClass 정의

public class Mainclass {
    public static void main(String[] args) {
        Connection conn = DBConn.getConnection();
        //이 컨트롤 객체는 MyBoardDAOImpl가 사용할것.
        
        IMyBoardDAO boardDAO = new MyBoardDAOImpl(conn);
        //데이터를 받고 이동시킬 dao클래스 생성
        
        MyBoardService boardService = new MyBoardService(boardDAO);
        
        MyBoardController controller = new MyBoardController(boardService);
        //이 컨트롤러에 조회/추가/삭제/수정 전달
        
        controller.start();
    }
}

진행 흐름을 보면
MyBoardDAOImplConnection가 필요하다.
MyBoardServiceMyBoardDAOImpl가 필요하고
ControllerMyBoardService가 필요하고

이런 관계를 DI (has-a) 라고한다. (의존성관계)

https://kouzie.github.io/java/java-DI-관계,-interface,-익명클래스,-Downcasting,-instanceof/#

그럼 가장 큰 가지에 속한는 Controller부터 정의해보자.


Controller 정의


public class MyBoardController {
    private int selectNumber;
    private Scanner sc = null;
    private MyBoardService myBoardService = null;

    public MyBoardController() {
        this.sc = new Scanner(System.in);
    }

    public MyBoardController(MyBoardService boardService) {
        this();
        this.myBoardService = boardService;
    }
    //기본생성자와 매개변수가 있는 생성자를 같이 쓰기!
    
    public void start() {
        while (true) {
            printMenu();
            selectMenu();
            processMenu();
        }
    }
    private void selectMenu() {
        System.out.print("선택: ");
        //유효성 검사..생략
        this.selectNumber = this.sc.nextInt();
    }

    private void printMenu() {
        String[] menus = {"새글쓰기", "목록보기", "글보기", "글수정"
                , "글삭제", "글검색", "종료"};

        System.out.println("==================================================메뉴==================================================");
        for (int i = 0; i < menus.length; i++) {
            String string = menus[i];
            System.out.printf(i+1 + ". %s \t ", string);
        }
        System.out.println();
    }
    
    private void exit() {
        DBConn.close();
        System.out.println("프로그램을 종료합니다....");
        System.exit(-1);
    }
    
    private void processMenu() {
        switch (this.selectNumber) {
        case 1: //새글쓰기
            insertNewBoard();
            break;
        case 2: //목록보기
            selectAllBoard();
            break;
        case 3: //글보기
            selectDetailBoard();
            break;
        case 4: //글수정

            break;
        case 5: //글삭제

            break;
        case 6: //글검색

            break;
        case 7: //종료
            exit();
            break;
        default:
            System.out.println("[1~7] 선택...");
            break;
        }
    }
    ...
}

출력값

==================================================메뉴==================================================
1. 새글쓰기      2. 목록보기      3. 글보기      4. 글수정      5. 글삭제      6. 글검색      7. 종료      
선택: 7
프로그램을 종료합니다....

Controller의 큰 틀만 보면 1~7까지 입력하면 각종 작업을 수행해주는 객체이다.

물론 자신이 직접 수행하지 않고 MyBoardService 에게 하청을 주고
MyBoardService는 또 MyBoardDAOImple에게 하청을 준다.

일단 새글쓰기(insertNewBoard)와 목록보기(selectAllBoard), 글보기(selectDetailBoard) 기능만 구현해보자.

selectAllBoard

    ...
    private void selectAllBoard() {
        System.out.print("페이지 번호 입력: ");
        this.curPage = sc.nextInt();
        ArrayList<MyBoardDTO> blist = this.myBoardService.selectService(curPage, pageSize);
        if(blist == null)
        {
            System.out.println("출력할 게시글이 없습니다.");            
            return;
        }
        Iterator<MyBoardDTO> bit = blist.iterator();
        System.out.println("--------------------------------게시판-----------------------------------");
        System.out.println("-------------------------------------------------------------------------");
        
        while (bit.hasNext()) {
            MyBoardDTO mdto = (MyBoardDTO) bit.next();
            System.out.println(mdto);
        }
        
        System.out.println("-------------------------------------------------------------------------");

        System.out.print("\t\t");
        for (int i = 1; i <= 10; i++) {
            if(this.curPage == i)
                System.out.printf("  [%d]", i);
            else
                System.out.printf("%3d", i);
        }
        System.out.println();
        System.out.println("-------------------------------------------------------------------------");
        System.out.println("계속 하려면 엔터....");
        stop(); //System.in.read 로 대기
    }
    ...

모든 게시글 출력 함수myBoardService 로부터 DTO객체ArrayList로 받아서 게시글 목록으로 출력해주는 함수.

페이징 처리는 지금은 보여주기 식으로 작성….



insertNewBoard

    ...
    private void insertNewBoard() {
        System.out.print("이름 입력: ");
        String name = this.sc.nextLine();
        
        System.out.print("비밀번호 입력: ");
        String password = this.sc.nextLine();
        
        System.out.print("이메일 입력: ");
        String email = this.sc.nextLine();
        
        System.out.print("제목 입력: ");
        String subject = this.sc.nextLine();
        
        System.out.print("내용 입력: ");
        String content = this.sc.nextLine();
        //tag와 userip는 자동으로 들어간다.
        
        MyBoardDTO bdto = new MyBoardDTO();
        bdto.setName(name);
        bdto.setPassword(password);
        bdto.setEmail(email);
        bdto.setSubject(subject);
        bdto.setContent(content);
        
        int resultCnt = this.myBoardService.insertService(bdto);
        if(resultCnt > 0)
            System.out.println("새글쓰기 완료!");
    }
    ...

게시글 입력 함수MyBoardDTO 객체에 내용을 담아서 myBoardService에게 INSERT하라고 명령 내리는 함수.



selectDetailBoard

    private void selectDetailBoard() {
        System.out.print("보고싶은 게시글 번호(seq) 입력: ");
        int seq = this.sc.nextInt();
        MyBoardDTO mdto = this.myBoardService.selectDetailBoard(seq);
        if(mdto == null)
        {
            System.out.println("해당 게시글이 존재하지 않습니다...");
            return;
        }
        else
        {
            System.out.println("============================");
            System.out.printf("글번호: %d \n", mdto.getSeq());
            System.out.printf("조회수: %d \n", mdto.getCnt());
            System.out.printf("날짜: %s \n", mdto.getRegDate());
            System.out.printf("글쓴이: %s \n", mdto.getName());
            System.out.printf("글제목: %s \n", mdto.getSubject());
            System.out.printf("이메일: %s \n", mdto.getEmail());
            System.out.printf("글내용: %s \n", mdto.getContent());
            System.out.println("============================");
        }
        stop();
    }
}

게시글 보기 함수 는 seq값을 입력받아 myBoardService에게 DTO객체를 달라고 명령하는 함수.

보다싶이 Controller는 정말 Service객체에게 명령만 내리기 때문에 중요하지 않다.


MyBoardService 정의

명령을 내리는 Controller와 실제 명령을 처리하는 DAO 사이의 중간다리 역할을 하는게 Serivce 객체이다.

public class MyBoardService {
    private IMyBoardDAO boardDAO = null;

    public MyBoardService(IMyBoardDAO boardDAO) {
        this.boardDAO  = boardDAO;
    }

    public ArrayList<MyBoardDTO> selectService(int curPage, int pageSize) {
        ArrayList<MyBoardDTO> blist = null;
        try {
            blist = this.boardDAO.select(curPage, pageSize);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return blist; 
    }

    public MyBoardDTO selectDetailBoard(int seq) {
        MyBoardDTO boardDTO = null;
        try {
            //1. 조회수 증가
            this.boardDAO.increaseCnt(seq);
            //2. SELECT 게시글
            boardDTO = this.boardDAO.selectDetail(seq);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return boardDTO;
    }

    public int insertService(MyBoardDTO boardDTO) {
        int resultCnt = 0;
        try {
            resultCnt = this.boardDAO.insert(boardDTO);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return resultCnt;
    }

    public int deleteService(int seq, String password) {
        
        int resultCnt = 0;
        if(this.boardDAO.checkPassword(seq, password))
        
        resultCnt = this.boardDAO.delete(seq);
        return 0;
    }
}

MyBoardServiceController에게 받은 작업을 DAO객체에게 떠넘긴다.

굳이 Controller가 직접 DAO객체에게 명령내리지 않고
Service객체를 거치는 이유는 다음과 같다.

게시글 상세보기를 예로 들어보자.
Controller가 게시글 상제 조회하라고 게시글 번호와 함께 명령을 내리면
Service는 받은 글번호를 DAO에게 넘겨주며 DTO객체를 요청한다.

DTO객체를 요청받은 MyBoardDAOImpl DAO객체는 총 2가지 작업을 수행해야 한다.

  1. 해당 게시글 조회수 증가
  2. 해당 게시글 DTO객체 반환

MyBoardService.selectDetailBoard 메서드를 보면 위 2가지 작업에 해당하는
boardDAO.increaseCnt, boardDAO.selectDetail 메서드를 호출한다.

위에서 말했듯이 Controll객체가 바로 boardDAO.increaseCntboardDAO.selectDetail를 호출하지 않고 Service객체를 거쳐가는 이유는
하나의 행동을 하나의 트랜잭션으로 묶어야 하기 때문이다.

조회수 증가가 성공했다 하더라도 게시글 출력이 실패한다면 증가된 조회수도 다시 rollback하는 트랜잭션 작업이 필요하다.

즉 하나의 행동에 여러 작업이 추가되더라도 수월한 트랜잭션 처리를 하기 위해 Service객체를 두는 것 이다.

유지보수 측면에서도 만약 로그저장 같은 작업이 추가되더라도 DAO객체에만 해당 기능을 하는 함수를 정의하면
ServiceControlller에는 더이상 수정할 작업이 별로 없기 때문에 유지보수가 수월하다.

참고: MVC에서 예외처리는 어디서 할지 상황에 따라 정해야한다.
Controller, Service, DAO중에서 MyBoardService에서 처리하도록 하자.

MyBoardDAOImpl 정의

MyBoardDAOImpl를 정의하기 전에
먼저 IMyBoard Interface를 작성해보자.

위에서 말했듯이 IMyBoard Interface는 게시글에서 수행되는 기본적인 작업을 정의하기 위해 만든 Interface이다.

가장 기본적인 행동은 Interface로 틀을 잡고 가면 나중에 유지보수하기 좋다.

import java.sql.SQLException;
import java.util.ArrayList;

public interface IMyBoard {
//    게시글 작성
    int insert(MyBoardDTO dto) throws SQLException; 
    //1이면 성공, 0이면 실패, 컨트롤러에서 예외를 처리하기 때문에 예외를 던진다.
    
//    게시글 목록 반환
    ArrayList<MyBoardDTO> select(int curPage, int pageSize) throws SQLException;
    //매개변수로 현재 보고싶은 page를 입력

//    게시글 조회수 증가
    int increaseCnt(int seq) throws SQLException;
    
//    게시글 보기
    MyBoardDTO selectDetail(int seq) throws SQLException;
}

일단 목록보기, 상세보기, 게시글작성
3개만 만들도록 하자.

위 3개 인터페이스 메서드를 구현하는 MyBoardDAOImpl 클래스 정의

이 클래스가 실제 DB와 연결해서 sql쿼리를 실행하고 결과값을 받는 클래스이다.
하청의 하청의 하청….

package MyBoard;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public class MyBoardDAOImpl implements IMyBoardDAO{
    private Connection connection = null;
    private PreparedStatement pstmt = null;
    private ResultSet rs = null;
    public MyBoardDAOImpl(Connection conn) {
        this.connection = conn;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }
    
    @Override
    public ArrayList<MyBoardDTO> select(int curPage, int pageSize) throws SQLException {
        ArrayList<MyBoardDTO> blist = null;
        StringBuffer sql = new StringBuffer();
        sql.append(" WITH temp AS( ");
        sql.append(" SELECT ROWNUM AS no, temp.* ");
        sql.append(" FROM ");
        sql.append(" ( ");
        sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
        sql.append("    FROM tbl_board ");
        sql.append("    ORDER BY seq desc ");
        sql.append("    )temp ");
        sql.append(" ) ");
        sql.append(" SELECT temp.* FROM temp ");
        sql.append(String.format(" WHERE temp.no BETWEEN ? AND ? "));
        this.pstmt = this.connection.prepareStatement(sql.toString());
        int start = (curPage - 1)* pageSize + 1 ;
        int end = curPage * pageSize;
        this.pstmt.setInt(1,  start);
        this.pstmt.setInt(2,  end);
        
        this.rs = this.pstmt.executeQuery();
        if(this.rs.next())
        {
            MyBoardDTO mdto = null;
            blist = new ArrayList<>();
            do {
                mdto = new MyBoardDTO();
                mdto.setNo(rs.getInt("no"));
                mdto.setSeq(rs.getInt("seq"));
                mdto.setName(rs.getString("name"));
                mdto.setEmail(rs.getString("email"));
                mdto.setSubject(rs.getString("subject"));
                mdto.setCnt(rs.getInt("cnt"));
                mdto.setRegDate(rs.getDate("regdate"));
                
                blist.add(mdto);
            } while (this.rs.next());
        }
        this.rs.close();
        this.pstmt.close();
        return blist;
    }

    @Override
    public int increaseCnt(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" UPDATE tbl_board ");
        sql.append(" SET cnt = cnt+1 ");
        sql.append(" WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        int resultCnt = this.pstmt.executeUpdate();
        this.pstmt.close();
        return resultCnt;
    }

    @Override
    public MyBoardDTO selectDetail(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" SELECT seq, name, email, subject, content, cnt, regdate ");
        sql.append(" FROM tbl_board ");
        sql.append(" WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        this.rs = this.pstmt.executeQuery();
        MyBoardDTO mdto  = null;
        if(rs.next())
        {
            mdto = new MyBoardDTO();
            mdto.setSeq(rs.getInt("seq"));
            mdto.setName(rs.getString("name"));
            mdto.setEmail(rs.getString("email"));
            mdto.setSubject(rs.getString("subject"));
            mdto.setContent(rs.getString("content"));
            mdto.setRegDate(rs.getDate("regdate"));
            mdto.setCnt(rs.getInt("cnt"));
        }
        this.rs.close();
        this.pstmt.close();
        return mdto;
    }
}

Connection객체로 연결해서 PreparedStatement로 쿼리를 수행해서 출력을 위해 DTO객체로 반환하거나
Service로부터 DTO객체를 전달받아 INSERT 또는 UPDATE 한다.

출력값

==================================================메뉴==================================================
1. 새글쓰기      2. 목록보기      3. 글보기      4. 글수정      5. 글삭제      6. 글검색      7. 종료      
선택: 2
페이지 번호 입력: 5
--------------------------------게시판-----------------------------------
-------------------------------------------------------------------------
61   515     AUFEnDWyPMxrvcUZ    YXKAD WNGTr@origio.net  2019-03-07  0
62   514         rcOEWteGjDhr    OPGUB IKbEw@origio.net  2019-03-07  0
63   513       qtrvaPNyvhugEy    FXIFH YVJsC@origio.net  2019-03-07  0
64   512      GZgJSOXbZlTXajv    PBUXG cHXsx@origio.net  2019-03-07  0
65   511    hmgFBHwBYgUvkoDUk    KHXTJ axSxm@origio.net  2019-03-07  0
66   510  EEgWROVjNMvrWVseKtJ    CCTTW cmsXw@origio.net  2019-03-07  0
67   509  nqDsOyMgWHuOzoEVRJP    CJNPX pxNZD@origio.net  2019-03-07  0
68   508           wEaixChLoD    CETQB whElL@origio.net  2019-03-07  0
69   507       oHHDVSvPLXKgkH    YUFBJ tJfEz@origio.net  2019-03-07  0
70   506       qMJJsRPhXHfcTx    GLNKA GTefh@origio.net  2019-03-07  0
71   505   OENpAwFbFywiVfsbAK    ZTDIP VobVf@origio.net  2019-03-07  0
72   504 QVWxGeGRXijfJoxFOGrE    MDDNX FAPey@origio.net  2019-03-07  0
73   503       PuoWVralUsXXuI    HIHDA KuGtf@origio.net  2019-03-07  0
74   502       fOCOYkfYtWmrUG    KOENJ xhiBh@origio.net  2019-03-07  0
75   501        SLAUvqFBAfHvt    RDAVB xfuWZ@origio.net  2019-03-07  0
-------------------------------------------------------------------------
          1  2  3  4  [5]  6  7  8  9 10
-------------------------------------------------------------------------
계속 하려면 엔터....

==================================================메뉴==================================================
1. 새글쓰기      2. 목록보기      3. 글보기      4. 글수정      5. 글삭제      6. 글검색      7. 종료      
선택: 3
보고싶은 게시글 번호(seq) 입력: 515
============================
글번호: 515 
조회수: 1 
날짜: 2019-03-07 
글쓴이: YXKAD 
글제목: AUFEnDWyPMxrvcUZ 
이메일: WNGTr@origio.net 
글내용: KMsAdMzeNGXEl 
============================
계속 하려면 엔터....

조회수 증가와 함께 상세 글보기 작업이 수행되었다.

게시글 목록 조회 순서

  1. Controller.selectAllBoard (요청,출력 담당)
  2. myBoardService.selectService (게시판 리스트 요청)
  3. boardDAO.select (실제 sql쿼리 수행, 게시판 리스트(ArrayList) 반환)


게시글 상세 조회 순서

  1. Controller.selectDetailBoard (요청,출력 담당)
  2. myBoardService.selectDetailBoard (MyBoardDTO 요청)
  3. MyBoardDAOImpl.increaseCnt, MyBoardDAOImpl.selectDetail (조회수증가, MyBoardDTO 반환)

게시글 수정, 삭제는 비밀번호를 체크하고 쿼리를 수행할 뿐 다를점이 별로 없다.


페이징 처리

현재 자신이 전체페이지중 몇페이지를 보고있는지 알 수 있도록 페이징 처리를 해보자.

페이징 처리: https://kouzie.github.io/database/DB-11일차/#페이징-처리시-알아야할-정보



먼저 페이징 처리를 하려면 총 페이지 수, 현재페이지, 와 한 페이지에 얼만큼의 게시글을 띄울지를 알아야 한다.

현재페이지와 한페이지에 띄울 게시글 수는 클라이언트가 정해서 주는것이고
한페이지에 띄울 게시글 수를 통해 총 페이지 수를 서버에서 얻어와야 한다.

boardDAO.getNumberOfPages(pageSize) 총페이지 수를 반환하는 함수를 정의!

@Override
public int getNumberOfPages(int pageSize) throws SQLException {
    StringBuffer sql = new StringBuffer();
    int numberOfPages = 0;
    sql.append(" SELECT CEIL(COUNT(*) / ?) numberOfPages FROM tbl_board ");
    this.pstmt = this.connection.prepareStatement(sql.toString());
    this.pstmt.setInt(1, pageSize);
    this.rs = this.pstmt.executeQuery();
    if(rs.next())
    {
        numberOfPages = rs.getInt("numberOfPages");
    }
    this.rs.close();
    this.pstmt.close();
    return numberOfPages;
}

입력된 pageSize에 따라 총 612개의 게시글이 있다면 pageSize가 10일때 62개의 페이지가 생긴다고 numberOfPages 정수값을 반환한다.



위에서 임시방편으로 다음과 같이 페이징처리를 하였다.

System.out.println("-------------------------------------------------------------------------");

System.out.print("\t\t");
for (int i = 1; i <= 10; i++) {
    if(this.curPage == i)
        System.out.printf("  [%d]", i);
    else
        System.out.printf("%3d", i);
}
System.out.println();
System.out.println("-------------------------------------------------------------------------");

출력값

-------------------------------------------------------------------------
          [1]  2  3  4  5  6  7  8  9 10
-------------------------------------------------------------------------

11페이지로 넘어가면 11~20이 표시되도록,
그리고 버튼같은걸 추가시키도록 해보자.

  ◀ [11] 12 13 14 15 16 17 18 19 20 ▶

이런식으로



띄어줘야할 페이지리스트를 문자열로 반환하는 myBoardService.pagingBlockService 함수를 정의헤보자!

public String pagingBlockService(int curPage, int pageSize, int pageBLocks) {
    StringBuffer pageServiceStr = new StringBuffer();
    int pageBlockStart;
    int pageBlockEnd;
    try {
        int numberOfPages = this.boardDAO.getNumberOfPages(pageSize); //총 페이지수 반환

        pageBlockStart = (curPage-1)/pageSize * pageSize + 1;
        pageBlockEnd = (curPage-1)/pageSize * pageSize + pageSize;

        if(pageBlockEnd > numberOfPages)
            pageBlockEnd = numberOfPages;
        if( pageBlockStart != 1 ) 
            pageServiceStr.append("  ◀");
        for (int j = pageBlockStart; j <= pageBlockEnd; j++) {
            if(curPage == j)
                pageServiceStr.append(String.format(" [%d]", j));
            else
                pageServiceStr.append(String.format("%3d", j));
        }
        if( pageBlockEnd !=  numberOfPages) 
            pageServiceStr.append(" ▶");
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return pageServiceStr.toString(); 
    }

중요한건 첫페이지와 끝페이지 수를 구하는 공식이다.

pageBlockStart = (curPage-1)/pageSize * pageSize + 1;
pageBlockEnd = (curPage-1)/pageSize * pageSize + pageSize;

이부분이다.

현재 12페이지를 가리키고 있다면

  ◀ 11 [12] 13 14 15 16 17 18 19 20 ▶

이런식으로 나와야 하는데 시작값 11과 끝값 20을 구하는 공식이 바로 위의
pageBlockStartpageBlockEnd 이다.

한페이지 출력 게시글 수가 10 일때 (pageSize = 10)

만약 현제 페이지가 14일 경우
pageBlockStart(첫페이지) 숫자는 (14-1)/10 * 10 + 1 = 11 이 된다.
pageBlockEnd(끝페이지) 숫자는 (14-1)/10 * 10 + 10 = 20 이 된다.



테스트 코드

public static void main(String[] args) throws SQLException {
    int pageSize = 10; //한페이지에 띄울 게시글 수
    int numberOfPages = 23; //총 페이지 수
    
    int pageBlockStart = 0;
    int pageBlockEnd = 0;
    for (int i = 1; i <= numberOfPages; i++) {
        pageBlockStart = (i-1)/pageSize * pageSize + 1;
        pageBlockEnd = (i-1)/pageSize * pageSize + pageSize;
        System.out.print(i+ "\t" + pageBlockStart + ".." + pageBlockEnd + "\t\t");
        if(pageBlockEnd > numberOfPages)
            pageBlockEnd = numberOfPages;
        if( pageBlockStart != 1 ) System.out.print("  ◀");
        for (int j = pageBlockStart; j <= pageBlockEnd; j++) {
            if(i == j)
                System.out.printf("[%d]", j);
            else
                System.out.printf("%3d", j);
        }
        if( pageBlockEnd !=  numberOfPages) System.out.print("▶");
        System.out.println();
    }
}

현재 페이지에 [] 를 붙이고 ◀ ▶버튼을 붙이는 디테일 작업을 해주었다.

출력값

1    [1]  2  3  4  5  6  7  8  9 10 ▶ 
2      1[2]  3  4  5  6  7  8  9 10 ▶ 
3      1  2[3]  4  5  6  7  8  9 10 ▶ 
4      1  2  3[4]  5  6  7  8  9 10 ▶ 
5      1  2  3  4[5]  6  7  8  9 10 ▶ 
6      1  2  3  4  5[6]  7  8  9 10 ▶ 
7      1  2  3  4  5  6[7]  8  9 10 ▶ 
8      1  2  3  4  5  6  7[8]  9 10 ▶ 
9      1  2  3  4  5  6  7  8[9] 10 ▶ 
10      1  2  3  4  5  6  7  8  9[10] ▶ 
11     ◀ [11] 12 13 14 15 16 17 18 19 20 ▶ 
12     ◀  11[12] 13 14 15 16 17 18 19 20 ▶ 
13     ◀  11 12[13] 14 15 16 17 18 19 20 ▶ 
14     ◀  11 12 13[14] 15 16 17 18 19 20 ▶ 
15     ◀  11 12 13 14[15] 16 17 18 19 20 ▶ 
16     ◀  11 12 13 14 15[16] 17 18 19 20 ▶ 
17     ◀  11 12 13 14 15 16[17] 18 19 20 ▶ 
18     ◀  11 12 13 14 15 16 17[18] 19 20 ▶ 
19     ◀  11 12 13 14 15 16 17 18[19] 20 ▶ 
20     ◀  11 12 13 14 15 16 17 18 19[20] ▶ 
21     ◀ [21] 22 23
22     ◀  21[22] 23
23     ◀  21 22[23]

페이징 처리를 Controller에 적용!

출력값

==================================================메뉴==================================================
1. 새글쓰기      2. 목록보기      3. 글보기      4. 글수정      5. 글삭제      6. 글검색      7. 종료      
선택: 2
페이지 번호 입력: 13
--------------------------------게시판-----------------------------------
-------------------------------------------------------------------------
121   455      dOZWFdCxWbmVYEb    BWUJG PCCOn@origio.net  2019-03-07  0
122   454          BHTltLZUZSi    OTZRP Alkkt@origio.net  2019-03-07  0
123   453          QXRLWwPYxhO    MQBPP csTGS@origio.net  2019-03-07  0
124   452          auULySVooHT    AVTNM sDUBm@origio.net  2019-03-07  0
125   451     JWvJkBbNaBFLCTAA    JBMDK AUcgw@origio.net  2019-03-07  0
126   450     vOImDwBBfWlTEcvw    QNZQK bXiBk@origio.net  2019-03-07  0
127   449          YVxfyxRsvNn    VSTXG VwuIw@origio.net  2019-03-07  0
128   448    wusnbOkpIPTjLvsYg    FPYIA ZSakw@origio.net  2019-03-07  0
129   447    kVLzCzufcCNqLlsFD    DXLFJ QvCQa@origio.net  2019-03-07  0
130   446        xxKuTeGHmOrGS    KWAYC SSvrl@origio.net  2019-03-07  0
-------------------------------------------------------------------------
  ◀ 11 12 [13] 14 15 16 17 18 19 20 ▶
-------------------------------------------------------------------------
계속 하려면 엔터....


단위테스트

메서드를 정의하면 정의한 메서드가 잘 돌아가는지 확인하고 싶을때가 있다.
그때 단위테스트를 사용하면 효과적!

new에서 JUnit Test Case 선택!

image2

image3

Jupiter가 최신버전이라 생각하면 된다.

다음 코드가 기본적으로 생성된다.

package test;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class BoardDAOTest {

    @Test
    void test() {
        fail("Not yet implemented");
    }

}

태스트 하고 싶은 코드를 다음과 같이 작성

package test;

import java.sql.SQLException;
import org.junit.jupiter.api.Test;
import com.util.DBConn;
import MyBoard.MyBoardDAOImpl;

class BoardDAOTest {
    @Test
    void test() throws SQLException {
        //fail("Not yet implemented");
        MyBoardDAOImpl boardDAO = new MyBoardDAOImpl(DBConn.getConnection());
        int numberOfPages = boardDAO.getNumberOfPages(10);
        System.out.println("총 페이지수: " + numberOfPages);
    }

}

image4

출력값

총 페이지수: 50

전체 코드

Mainclass

public class Mainclass {
    public static void main(String[] args) {
        Connection conn = DBConn.getConnection();
        //이 컨트롤 객체는 MyBoardDAOImpl가 사용할것.
        
        IMyBoardDAO boardDAO = new MyBoardDAOImpl(conn);
        //데이터를 받고 이동시킬 dao클래스 생성
        
        MyBoardService boardService = new MyBoardService(boardDAO);
        
        MyBoardController controller = new MyBoardController(boardService);
        //이 컨트롤러에 조회/추가/삭제/수정 전달
        
        controller.start();
    }
}

MyBoardDTO

public class MyBoardDTO{
    private int no;
    private int seq;
    private String name;
    private String password;
    private String content;
    private String subject;
    private String email;
    private int cnt;
    
    @Override
    public String toString() {
        
        return String.format("%2d  %4d %20s %8s %15s  %tF  %d", no, seq, subject, name, email, regDate, cnt);
    }
    public int getSeq() {
        return seq;
    }
    public Date getRegDate() {
        return regDate;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getCnt() {
        return cnt;
    }
    public void setCnt(int cnt) {
        this.cnt = cnt;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    private Date regDate;
    
    public void setSeq(int seq) {
        this.seq = seq;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getNo() {
        return no;
    }
    public String getName() {
        return name;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }

    public void setRegDate(Date regDate) {
        this.regDate = regDate;
    }
    public String getSubject() {
        return subject;
    }
    public void setNo(int no) {
        this.no = no;
    }
}

MyBoardController

public class MyBoardController {
    private int selectNumber;
    private Scanner sc = null;
    private MyBoardService myBoardService = null;
    private int curPage = 1;
    private int pageSize = 10;
    private int pageBLocks = 10; //1~10 page 블록을 출력
    
    public MyBoardController() {
        this.sc = new Scanner(System.in);
    }
    public MyBoardController(MyBoardService boardService) {
        this();
        this.myBoardService = boardService;
    }

    public void start() {
        while (true) {
            printMenu();
            selectMenu();
            processMenu();
        }
    }
    private void selectMenu() {
        System.out.print("선택: ");
        //유효성 검사...X
        this.selectNumber = this.sc.nextInt();
        this.sc.nextLine();
    }
    private void printMenu() {
        String[] menus = {"새글쓰기", "목록보기", "글보기", "글수정"
                , "글삭제", "글검색", "종료"};
        System.out.println("==================================================메뉴==================================================");
        for (int i = 0; i < menus.length; i++) {
            String string = menus[i];
            System.out.printf(i+1 + ". %s \t ", string);
        }
        System.out.println();
    }
    
    private void exit() {
        DBConn.close();
        System.out.println("프로그램을 종료합니다....");
        System.exit(-1);
    }
    
    private void processMenu() {
        switch (this.selectNumber) {
        case 1: //새글쓰기
            insertNewBoard();
            break;
        case 2: //목록보기
            selectAllBoard();
            break;
        case 3: //글보기
            selectDetailBoard();
            break;
        case 4: //글수정
            updateBoard();
            break;
        case 5: //글삭제
            deleteBoard();
            break;
        case 6: //글검색
            searchBoard();
            break;
        case 7: //종료
            exit();
            break;
        default:
            System.out.println("[1~7] 선택...");
            break;
        }
    }

    private static void stop() {
        System.out.println("계속 하려면 엔터....");
        try {
            System.in.read();
            System.in.skip(System.in.available());        
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private void updateBoard() {
        System.out.print("수정할 게시글 번호(seq) 입력: ");
        int seq = this.sc.nextInt();
        this.sc.nextLine();
        MyBoardDTO mdto = this.myBoardService.selectDetailBoard(seq);
        if(mdto == null)
        {
            System.out.println("해당 게시글이 존재하지 않습니다...");
            return;
        }
        else
        {
            System.out.println("============================");
            System.out.printf(" 글번호: %d \n", mdto.getSeq());
            System.out.printf(" 글쓴이: %s \n", mdto.getName());
            System.out.printf(" 글제목: %s \n", mdto.getSubject());
            System.out.printf(" 글내용: %s \n", mdto.getContent());
            System.out.println("============================");
        }
        //제목과 내용만 수정
        System.out.print("수정할 제목 입력: ");
        String subject = this.sc.nextLine();
        System.out.print("수정할 내용 입력: ");
        String content = this.sc.nextLine();
        System.out.print("비밀번호 입력: ");
        String password = this.sc.nextLine();
        mdto.setSubject(subject);
        mdto.setContent(content);
        int resultCnt = this.myBoardService.updateService(mdto, seq, password);
        if(resultCnt > 0)
            System.out.println("수정 성공!");
        else if(resultCnt == -1)
            System.out.println("비밀번호 오류!");
        else
            System.out.println("수정 실패");
        stop();
    }

    //Controller의 밑의 모든 함수들은 MyBoardService함수를 다시 호출한다.
    //Controller는 그저 데이터를 받아서 화면상에 출력해주는 역할을 해주자.
    
    private void selectAllBoard() {
        System.out.print("페이지 번호 입력: ");
        this.curPage = sc.nextInt();
        ArrayList<MyBoardDTO> blist = this.myBoardService.selectService(curPage, pageSize);
        if(blist == null)
        {
            System.out.println("출력할 게시글이 없습니다.");            
            return;
        }
        Iterator<MyBoardDTO> bit = blist.iterator();
        System.out.println("--------------------------------게시판-----------------------------------");
        System.out.println("-------------------------------------------------------------------------");
        
        
        while (bit.hasNext()) {
            MyBoardDTO mdto = (MyBoardDTO) bit.next();
            System.out.println(mdto);
        }
        
        System.out.println("-------------------------------------------------------------------------");
        System.out.println(this.myBoardService.pagingBlockService(curPage, pageSize, pageBLocks););
        System.out.println("-------------------------------------------------------------------------");
        stop(); //System.in.read 로 대기
    }
    
    private void insertNewBoard() {
        System.out.print("이름 입력: ");
        String name = this.sc.nextLine();
        
        System.out.print("비밀번호 입력: ");
        String password = this.sc.nextLine();
        
        System.out.print("이메일 입력: ");
        String email = this.sc.nextLine();
        
        System.out.print("제목 입력: ");
        String subject = this.sc.nextLine();
        
        System.out.print("내용 입력: ");
        String content = this.sc.nextLine();
        
        MyBoardDTO bdto = new MyBoardDTO();
        bdto.setName(name);
        bdto.setPassword(password);
        bdto.setEmail(email);
        bdto.setSubject(subject);
        bdto.setContent(content);
        
        int resultCnt = this.myBoardService.insertService(bdto);
        if(resultCnt > 0)
            System.out.println("새글쓰기 완료!");
        stop();
    }

    private void deleteBoard() {
        System.out.print("삭제할 글 번호 입력: ");
        int seq = this.sc.nextInt();
        System.out.print("비밀번호 입력: ");
        String password = this.sc.next();
        
        
        int resultCnt = this.myBoardService.deleteService(seq, password);
        if(resultCnt > 0)
            System.out.println("삭제완료!");
        else if(resultCnt == -1)
            System.out.println("비밀번호 오류!");
        else
            System.out.println("삭제 실패");
    }
    
    private void selectDetailBoard() {
        System.out.print("보고싶은 게시글 번호(seq) 입력: ");
        int seq = this.sc.nextInt();
        MyBoardDTO mdto = this.myBoardService.selectDetailBoard(seq);
        if(mdto == null)
        {
            System.out.println("해당 게시글이 존재하지 않습니다...");
            return;
        }
        else
        {
            System.out.println("============================");
            System.out.printf("글번호: %d \n", mdto.getSeq());
            System.out.printf("조회수: %d \n", mdto.getCnt());
            System.out.printf("날짜: %s \n", mdto.getRegDate());
            System.out.printf("글쓴이: %s \n", mdto.getName());
            System.out.printf("글제목: %s \n", mdto.getSubject());
            System.out.printf("이메일: %s \n", mdto.getEmail());
            System.out.printf("글내용: %s \n", mdto.getContent());
            System.out.println("============================");
        }
        stop();
    }
    
    private void searchBoard() {
        //게시글 검색은 게시글 모두 보기와 거의 동일하다.
        System.out.println("====================검색조건 선택====================");
        System.out.println("1. 제목 \t 2. 내용 \t 3. 글쓴이 \t 4. 제목+내용");
        System.out.print("조건 선택: ");
        searchCondition = this.sc.nextInt();
        
        String searchWord;
        System.out.print("키워드 입력: ");
        this.sc.nextLine();
        searchWord = this.sc.nextLine();
        this.curPage = 1;
        
        this.myBoardService.pagingBlockSearchService(curPage, pageSize, pageBLocks, searchCondition, searchWord);
        System.out.print("페이지 번호 입력: ");
        this.curPage = sc.nextInt();
        ArrayList<MyBoardDTO> blist = this.myBoardService.searchService(curPage, pageSize, searchCondition, searchWord);

        if(blist == null)
        {
            System.out.println("검색된 게시글이 없습니다.");            
            return;
        }
        
        Iterator<MyBoardDTO> bit = blist.iterator();
        System.out.println("--------------------------------게시판-----------------------------------");
        System.out.println("-------------------------------------------------------------------------");
        
        while (bit.hasNext()) {
            MyBoardDTO mdto = (MyBoardDTO) bit.next();
            System.out.println(mdto);
        }
        
        System.out.println("-------------------------------------------------------------------------");
        String pageStr = this.myBoardService.pagingBlockSearchService(curPage, pageSize, pageBLocks, searchCondition, searchWord);
        System.out.println("\t\t" + pageStr);
        System.out.println("-------------------------------------------------------------------------");
        stop(); //System.in.read 로 대기
    }
}

MyBoardService

public class MyBoardService {
    private IMyBoardDAO boardDAO = null;

    public MyBoardService(IMyBoardDAO boardDAO) {
        this.boardDAO  = boardDAO;
    }

    public ArrayList<MyBoardDTO> selectService(int curPage, int pageSize) {
        ArrayList<MyBoardDTO> blist = null;
        try {
            blist = this.boardDAO.select(curPage, pageSize);
        } catch (SQLException e) {
            e.printStackTrace();
        } //15개씩 보는거로 선택
        //MVC에서 예외처리는 어디서 할지 신중히 정해야한다.
        //우리는 MyBoardService에서 처리하도록 하자, Controller에서 처리해도 된다.
        return blist; 
    }

    public int insertService(MyBoardDTO boardDTO) {
        int resultCnt = 0;
        try {
            resultCnt = this.boardDAO.insert(boardDTO);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return resultCnt;
    }

    public int deleteService(int seq, String password) {
        //게시글 삭제 행위는 2가지 작업으로 이루어진다.
        //1. 비밀번호 확인, 2. 게시글 삭제
        int resultCnt = 0;
        String seq_password = null;
        try {
            seq_password = this.boardDAO.getPassword(seq);
            if(seq_password.equals(password))
                resultCnt = this.boardDAO.delete(seq);
            
            else
                resultCnt = -1;
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return resultCnt;
    }

    public MyBoardDTO selectDetailBoard(int seq) {
        MyBoardDTO boardDTO = null;
        try {
            //1. 조회수 증가
            this.boardDAO.increaseCnt(seq);
            //2. SELECT 게시글
            boardDTO = this.boardDAO.selectDetail(seq);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return boardDTO;
    }

    public int updateService(MyBoardDTO boardDTO, int seq, String password) {
        int resultCnt = 0;
        String seq_password = null;
        try {
            seq_password = this.boardDAO.getPassword(seq);
            if(seq_password.equals(password))
                resultCnt = this.boardDAO.update(seq, boardDTO);
            
            else
                resultCnt = -1;
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return resultCnt;
    }

    public String pagingBlockService(int curPage, int pageSize, int pageBLocks) {
        //페이징 처리를 하려면 총 페이지 수, 현재페이지, 와 한 페이지에 얼만큼의 게시글을 띄울지를 알아야 한다.
        StringBuffer pageServiceStr = new StringBuffer();
        int pageBlockStart;
        int pageBlockEnd;
        try {
            int numberOfPages = this.boardDAO.getNumberOfPages(pageSize); //총 페이지수 반환
            pageBlockStart = (curPage-1)/pageSize * pageSize + 1;
            pageBlockEnd = (curPage-1)/pageSize * pageSize + pageSize;

            if(pageBlockEnd > numberOfPages)
                pageBlockEnd = numberOfPages;
            if( pageBlockStart != 1 ) 
                pageServiceStr.append("  ◀");
            for (int j = pageBlockStart; j <= pageBlockEnd; j++) {
                if(curPage == j)
                    pageServiceStr.append(String.format(" [%d]", j));
                else
                    pageServiceStr.append(String.format("%3d", j));
            }
            if( pageBlockEnd !=  numberOfPages) 
                pageServiceStr.append(" ▶");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return pageServiceStr.toString(); 
    }

    public ArrayList<MyBoardDTO> searchService(int curPage, int pageSize, int searchCondition, String searchWord) {
        //모든 게시물 출력과 다를게 없다.
        //selectService가서 복붙
        ArrayList<MyBoardDTO> blist = null;
        try {
            blist = this.boardDAO.search(curPage, pageSize, searchCondition, searchWord);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //MVC에서 예외처리는 어디서 할지 신중히 정해야한다.
        //우리는 MyBoardService에서 처리하도록 하자, Controller에서 처리해도 된다.
        return blist; 
    }

    public String pagingBlockSearchService(int curPage, int pageSize, int pageBLocks, int searchCondition, String searchWord) {
        //검색된 게시글 페이징 처리를 하기 위해 pagingBlockService 복붙
        StringBuffer pageServiceStr = new StringBuffer();
        int pageBlockStart;
        int pageBlockEnd;
        try {
            int numberOfPages = this.boardDAO.getSearchNumberOfPages(pageSize, searchCondition, searchWord); //총 페이지수 반환
            System.out.println("총페이지 수: "+ numberOfPages);
            pageBlockStart = (curPage-1)/pageSize * pageSize + 1;
            pageBlockEnd = (curPage-1)/pageSize * pageSize + pageSize;

            if(pageBlockEnd > numberOfPages)
                pageBlockEnd = numberOfPages;
            if( pageBlockStart != 1 ) 
                pageServiceStr.append("  ◀");
            for (int j = pageBlockStart; j <= pageBlockEnd; j++) {
                if(curPage == j)
                    pageServiceStr.append(String.format(" [%d]", j));
                else
                    pageServiceStr.append(String.format("%3d", j));
            }
            if( pageBlockEnd !=  numberOfPages) 
                pageServiceStr.append(" ▶");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return pageServiceStr.toString(); 
    }
}

IMyBoardDAO

public interface IMyBoardDAO {
//    게시글 작성
    int insert(MyBoardDTO boardDTO) throws SQLException; 
    //1이면 성공, 0이면 실패, 컨트롤러에서 예외를 처리하기 때문에 예외를 던진다.
    
//    게시글 목록
    ArrayList<MyBoardDTO> select(int curPage, int pageSize) throws SQLException;
    //매개변수로 현재 보고싶은 page를 입력

//    게시글 수정
    int update(int seq, MyBoardDTO boardDTO) throws SQLException;
    
//    게시글 삭제
    int delete(int seq) throws SQLException;
    String getPassword(int seq) throws SQLException;
    
//    게시글 조회수 증가
    int increaseCnt(int seq) throws SQLException;
    
//    게시글 보기
    MyBoardDTO selectDetail(int seq) throws SQLException;

//    게시글 검색
    ArrayList<MyBoardDTO> search(int curPage, int pageSize, int searchCondition, String searchWord) throws SQLException;
    int getSearchNumberOfPages(int pageSize, int searchCondition, String searchWord) throws SQLException;

//    총게시글 수 반환
    int getNumberOfPages(int pageSize) throws SQLException;
}

MyBoardDAOImpl

public class MyBoardDAOImpl implements IMyBoardDAO{
    private Connection connection = null;
    private PreparedStatement pstmt = null;
    private ResultSet rs = null;
    public MyBoardDAOImpl(Connection conn) {
        this.connection = conn;
    }

    public Connection getConnection() {
        return connection;
    }
    public void setConnection(Connection connection) {
        this.connection = connection;
    }
    
    @Override
    public int insert(MyBoardDTO boardDTO) throws SQLException {
        int resultCnt = 0;
        StringBuffer sql = new StringBuffer();
        sql.append(" INSERT INTO tbl_board ");
        sql.append(" (seq, name, password, email, subject, content, tag, userip) ");
        sql.append(" VALUES(seq_myboard.nextval, ?, ?, ?, ?, ?, 'y', '127.0.0.1') ");
        //? 5
        System.out.println(sql.toString());
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setString(1, boardDTO.getName());
        this.pstmt.setString(2, boardDTO.getPassword());
        this.pstmt.setString(3, boardDTO.getEmail());
        this.pstmt.setString(4, boardDTO.getSubject());
        this.pstmt.setString(5, boardDTO.getContent());
        resultCnt = this.pstmt.executeUpdate();
        this.pstmt.close();
        return resultCnt;
    }

    @Override
    public ArrayList<MyBoardDTO> select(int curPage, int pageSize) throws SQLException {
        ArrayList<MyBoardDTO> blist = null;
        StringBuffer sql = new StringBuffer();
        sql.append(" WITH temp AS( ");
        sql.append(" SELECT ROWNUM AS no, temp.* ");
        sql.append(" FROM ");
        sql.append(" ( ");
        sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
        sql.append("    FROM tbl_board ");
        sql.append("    ORDER BY seq desc ");
        sql.append("    )temp ");
        sql.append(" ) ");
        sql.append(" SELECT temp.* FROM temp ");
        sql.append(String.format(" WHERE temp.no BETWEEN ? AND ? "));
        this.pstmt = this.connection.prepareStatement(sql.toString());
        int start = (curPage - 1)* pageSize + 1 ;
        int end = curPage * pageSize;
        this.pstmt.setInt(1,  start);
        this.pstmt.setInt(2,  end);
        
        this.rs = this.pstmt.executeQuery();
        if(this.rs.next())
        {
            MyBoardDTO mdto = null;
            blist = new ArrayList<>();
            do {
                mdto = new MyBoardDTO();
                mdto.setNo(rs.getInt("no"));
                mdto.setSeq(rs.getInt("seq"));
                mdto.setName(rs.getString("name"));
                mdto.setEmail(rs.getString("email"));
                mdto.setSubject(rs.getString("subject"));
                mdto.setCnt(rs.getInt("cnt"));
                mdto.setRegDate(rs.getDate("regdate"));
                
                blist.add(mdto);
            } while (this.rs.next());
        }
        this.rs.close();
        this.pstmt.close();
        return blist;
    }

    @Override
    public int delete(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" DELETE FROM tbl_board WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        int resultCnt = this.pstmt.executeUpdate();
        this.pstmt.close();
        this.rs.close();
        return resultCnt;
    }

    @Override
    public String getPassword(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" SELECT password FROM tbl_board WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        this.rs = this.pstmt.executeQuery();
        String password = null;
        if(rs.next())
        {
            password = rs.getString("password");
        }
        this.pstmt.close();
        this.rs.close();
        return password;
    }

    @Override
    public int increaseCnt(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" UPDATE tbl_board ");
        sql.append(" SET cnt = cnt+1 ");
        sql.append(" WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        int resultCnt = this.pstmt.executeUpdate();
        this.pstmt.close();
        return resultCnt;
    }

    @Override
    public MyBoardDTO selectDetail(int seq) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" SELECT seq, name, email, subject, content, cnt, regdate ");
        sql.append(" FROM tbl_board ");
        sql.append(" WHERE seq = ? ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, seq);
        this.rs = this.pstmt.executeQuery();
        MyBoardDTO mdto  = null;
        if(rs.next())
        {
            mdto = new MyBoardDTO();
            mdto.setSeq(rs.getInt("seq"));
            mdto.setName(rs.getString("name"));
            mdto.setEmail(rs.getString("email"));
            mdto.setSubject(rs.getString("subject"));
            mdto.setContent(rs.getString("content"));
            mdto.setRegDate(rs.getDate("regdate"));
            mdto.setCnt(rs.getInt("cnt"));
        }
        this.rs.close();
        this.pstmt.close();
        return mdto;
    }

    @Override
    public int update(int seq, MyBoardDTO boardDTO) throws SQLException {
        StringBuffer sql = new StringBuffer();
        sql.append(" UPDATE tbl_board ");
        sql.append(" SET subject = ?, content = ? ");
        sql.append(" WHERE seq = ? ");
//        System.out.println(sql.toString());
        this.pstmt = connection.prepareStatement(sql.toString());
        this.pstmt.setString(1, boardDTO.getSubject());
        this.pstmt.setString(2, boardDTO.getContent());
        this.pstmt.setInt(3, seq);
        int resultCnt = this.pstmt.executeUpdate();
        this.rs.close();
        this.pstmt.close();
        return resultCnt;
    }

    @Override
    public int getNumberOfPages(int pageSize) throws SQLException {
        StringBuffer sql = new StringBuffer();
        int numberOfPages = 0;
        sql.append(" SELECT CEIL(COUNT(*) / ?) numberOfPages FROM tbl_board ");
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, pageSize);
        this.rs = this.pstmt.executeQuery();
        if(rs.next())
        {
            numberOfPages = rs.getInt("numberOfPages");
        }
        this.rs.close();
        this.pstmt.close();
        return numberOfPages;
    }

    @Override
    public ArrayList<MyBoardDTO> search(int curPage, int pageSize, int searchCondition, String searchWord) throws SQLException{
        ArrayList<MyBoardDTO> blist = null;
        StringBuffer sql = new StringBuffer();
        sql.append(" WITH temp AS( ");
        sql.append(" SELECT ROWNUM AS no, temp.* ");
        sql.append(" FROM ");
        sql.append(" ( ");
        sql.append("    SELECT seq, name, email, subject, cnt, regdate ");
        sql.append("    FROM tbl_board ");
        switch (searchCondition) {
        case 1: //재목
            sql.append("    WHERE REGEXP_LIKE(subject, ?, 'i') ");
            break;
        case 2: //내용
            sql.append("    WHERE REGEXP_LIKE(content, ?, 'i') ");
            break;
        case 3: //글쓴이
            sql.append("    WHERE REGEXP_LIKE(name, ?, 'i') ");
            break;
        case 4: //제목+내용
            sql.append("    WHERE REGEXP_LIKE(subject, ?, 'i') OR REGEXP_LIKE(content, ?, 'i') ");
            break;

        default:
            break;
        }
        sql.append("    ORDER BY seq desc ");
        sql.append("    )temp ");
        sql.append(" ) ");
        sql.append(" SELECT temp.* FROM temp ");
        sql.append(String.format(" WHERE temp.no BETWEEN ? AND ? "));
//        System.out.println(sql.toString());
        this.pstmt = this.connection.prepareStatement(sql.toString());
        int start = (curPage - 1)* pageSize + 1 ;
        int end = curPage * pageSize;
        this.pstmt.setString(1,  searchWord);
        
        if(searchCondition == 4)
        {
            //참고로 ?에 %를 같이쓰고싶다면 문자열 자체에 %를 붙여야된다. 
            //sql자체에 %와 ?같이쓸 수는 없음.
            this.pstmt.setString(2,  searchWord);
            this.pstmt.setInt(3,  start);
            this.pstmt.setInt(4,  end);
        }
        else
        {
            this.pstmt.setInt(2,  start);
            this.pstmt.setInt(3,  end);
        }
        this.rs = this.pstmt.executeQuery();
        if(this.rs.next())
        {
            MyBoardDTO mdto = null;
            blist = new ArrayList<>();
            do {
                mdto = new MyBoardDTO();
                mdto.setNo(rs.getInt("no"));
                mdto.setSeq(rs.getInt("seq"));
                mdto.setName(rs.getString("name"));
                mdto.setEmail(rs.getString("email"));
                mdto.setSubject(rs.getString("subject"));
                mdto.setCnt(rs.getInt("cnt"));
                mdto.setRegDate(rs.getDate("regdate"));
                
                blist.add(mdto);
            } while (this.rs.next());
        }
        this.rs.close();
        this.pstmt.close();
        return blist;
    }

    @Override
    public int getSearchNumberOfPages(int pageSize, int searchCondition, String searchWord) throws SQLException {
        StringBuffer sql = new StringBuffer();
        int numberOfPages = 0;
        sql.append(" SELECT CEIL(COUNT(*) / ?) numberOfPages FROM tbl_board ");
        switch (searchCondition) {
        case 1: //재목
            sql.append(" WHERE REGEXP_LIKE(subject, ?, 'i') ");
            break;
        case 2: //내용
            sql.append(" WHERE REGEXP_LIKE(content, ?, 'i') ");
            break;
        case 3: //글쓴이
            sql.append(" WHERE REGEXP_LIKE(name, ?, 'i') ");
            break;
        case 4: //제목+내용
            sql.append(" WHERE REGEXP_LIKE(subject, ?, 'i') OR REGEXP_LIKE(content, ?, 'i') ");
            break;

        default:
            break;
        }
        this.pstmt = this.connection.prepareStatement(sql.toString());
        this.pstmt.setInt(1, pageSize);
        this.pstmt.setString(2,  searchWord);
        
        if(searchCondition == 4)
        {
            this.pstmt.setString(3,  searchWord);
        }
        this.rs = this.pstmt.executeQuery();
        if(rs.next())
        {
            numberOfPages = rs.getInt("numberOfPages");
        }
        this.rs.close();
        this.pstmt.close();
        return numberOfPages;
    }
}

카테고리:

업데이트: