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
를 사용하면 편할듯 하다.
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
를선언하고 결과값을 받을 HashMap
과 ArrayList
,
데이터 받는걸 도와줄 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에게 데이터 처리를 맞긴다.
Controller가 Model로부터 데이터를 받으면 View에게 출력할 화면을 만들라고 일처리를 맞긴다.
View가 이쁘게 출력할수 있도록 구성이 끝나면 컨트롤러가 마지막으로 이 데이터를 요청자에게 반환한다.
tbl_board 테이블을 MVC패턴으로 구성해보자.
게시판 MVC 패턴으로 구성하기
tbl_board 은 다음처럼 생겼다.
테이블 생성은 https://kouzie.github.io/database/DB-11일차/#게시판-테이블-설계하기
게시판 기능을 나름 MVC형태로 구현하려면
총 6개의 클래스가 필요하다.
- MyBoardDTO
- IMyBoardDAO
- MyBoardDAOImpl
- MyBoardService
- MyBoardController
- mainClass(메인함수)
MyBoardDTO
는 tbl_board 테이블에서 데이터를 받아오기 위한 객체.
IMyBoardDAO
안에는 게시글에서 할수있는 작업을 인터페이스로 선언한다.
게시글하면 떠오르는 다음 작업들이 기본적으로 정의되어야 한다.
게시글 작성
게시글 목록
게시글 수정
게시글 삭제
게시글 조회수 증가
게시글 보기
총게시글 수 반환
총페이지 수 반환
이런 기본적인 작업은 인터페이스에서 정의하고 이를 구현하는 클래스를 정의하는 것이 좋다.
MyBoardImpl
은 IMyBoardDAO
구현한 DB와 연결 및 각종 작업별로 데이터를 가져오는 클래스이다.
MyBoardService
클래스는 DAO의 보조적인 역할을 하는 클래스이다.
굳이 없어도 되지만 있으면 좋은?
게시글에서 글을 클릭하면 하는 행동은 게시글 상세출력 하나로 볼 수 있지만 실제로 DAO안에선 조회수 증가, 실제글 출력 2가지 작업이 수행된다.
만약 로그를 추가한다던가 다른 특이작업을 한다면 한 행동안에 2개 이상의 작업이 추가될 수 있다.(물론 DAO에 추가 정의해야함)
이 여러개의 작업이 하나의 행동으로, 하나의 트랜잭션으로 묶어 동작해야 하는데 MyBoardService
가 이를 도와준다.
MyBoardController
는 말 그대로 컨트롤러 역할을 하는 클래스, 단 지금은 View의 역할도 같이 해준다.
mainClass
는 클라이언트 역할을 해줄 조회/추가/삭제/수정 요청을 하는 클래스
Sequence Diagram
그럼 흐름은 다음과 같이 된다.
MainClass
에서 조회/추가/삭제/수정 신청을 하면
MyBoardController
가 요청을 받는다. 받은 요청을 MyBoardService
객체에게 전달
MyBoardService
는 요청을 받아 함수를 호출하고 이 함수안에선 MyBoardDAOImpl
의 함수를 호출하게 된다.
MyBoardDAOImpl
는 DB서버에서 데이터를 처리(조회의 경우 조회수올리고 데이터 SELECT
)하고 MyBoardService
에게 결과를 리턴한다.
MyBoardService
는 리턴결과를 다시 MyBoardController
에게 보낸다.
MyBoardController
는 MainClass
에게 출력할 데이터를 보낸다.
(View의 역할을 MyBoardController이 해주는 걸로….)
이런 흐름을 Sequence Diagram이라 하는데 그림을 그리면 협엽하기 좋다.
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();
}
}
진행 흐름을 보면
MyBoardDAOImpl
는 Connection
가 필요하다.
MyBoardService
는 MyBoardDAOImpl
가 필요하고
Controller
는 MyBoardService
가 필요하고
이런 관계를 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;
}
}
MyBoardService
도 Controller
에게 받은 작업을 DAO객체
에게 떠넘긴다.
굳이 Controller
가 직접 DAO객체
에게 명령내리지 않고
Service
객체를 거치는 이유는 다음과 같다.
게시글 상세보기를 예로 들어보자.
Controller
가 게시글 상제 조회하라고 게시글 번호와 함께 명령을 내리면
Service
는 받은 글번호를 DAO
에게 넘겨주며 DTO객체
를 요청한다.
DTO객체
를 요청받은 MyBoardDAOImpl
DAO객체는 총 2가지 작업을 수행해야 한다.
- 해당 게시글 조회수 증가
- 해당 게시글 DTO객체 반환
MyBoardService.selectDetailBoard
메서드를 보면 위 2가지 작업에 해당하는
boardDAO.increaseCnt
, boardDAO.selectDetail
메서드를 호출한다.
위에서 말했듯이 Controll
객체가 바로 boardDAO.increaseCnt
와 boardDAO.selectDetail
를 호출하지 않고 Service
객체를 거쳐가는 이유는
하나의 행동을 하나의 트랜잭션으로 묶어야 하기 때문이다.
조회수 증가가 성공했다 하더라도 게시글 출력이 실패한다면 증가된 조회수도 다시 rollback
하는 트랜잭션 작업이 필요하다.
즉 하나의 행동에 여러 작업이 추가되더라도 수월한 트랜잭션 처리를 하기 위해 Service객체를 두는 것 이다.
유지보수 측면에서도 만약 로그저장 같은 작업이 추가되더라도 DAO객체에만 해당 기능을 하는 함수를 정의하면
Service
와 Controlller
에는 더이상 수정할 작업이 별로 없기 때문에 유지보수가 수월하다.
참고: 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
============================
계속 하려면 엔터....
조회수 증가와 함께 상세 글보기 작업이 수행되었다.
게시글 목록 조회 순서
- Controller.selectAllBoard (요청,출력 담당)
- myBoardService.selectService (게시판 리스트 요청)
- boardDAO.select (실제 sql쿼리 수행, 게시판 리스트(ArrayList) 반환)
게시글 상세 조회 순서
- Controller.selectDetailBoard (요청,출력 담당)
- myBoardService.selectDetailBoard (MyBoardDTO 요청)
- 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
을 구하는 공식이 바로 위의
pageBlockStart
과 pageBlockEnd
이다.
한페이지 출력 게시글 수가 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
선택!
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);
}
}
출력값
총 페이지수: 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;
}
}