코딩항해기

[Team/붕어빵원정대(중중프)] 중중프 완료 Model 담당 파트 정리 본문

Project

[Team/붕어빵원정대(중중프)] 중중프 완료 Model 담당 파트 정리

miniBcake 2024. 8. 30. 21:13

 

팀명 : 붕어빵 원정대 (6인)
(Model 파트 2인 / View 파트 2인 / Controller 파트 2인 )

담당 : Model 파트장
(게시글, 이미지, 카테고리 DTO DAO 진행)


[붕어빵 원정대 - 갈빵질빵 JSP기반 웹페이지 프로젝트]

JSP 
언어: Java, HTML, CSS, JavaScript, jQuery, SQL
라이브러리 및 기술: EL, JSTL 
개발 도구: Eclipse IDE, DBeaver(+Eclipse IDE (플러그인으로 DB 연동))
데이터베이스: Oracle DatabaseDB
웹 서버: Apache Tomcat 10.1
설계 패턴: MVC 패턴 (Model-View-Controller), 싱글톤 패턴(핸들러 맵핑)

 

프로젝트 단계 : 중간 중간 프로젝트 (중중프)

(중간 프로젝트(중프) 전 연습단계)

 

 

오늘이 중중프 발표날이었다, 발표 때 긴장을 너무 많이 해서 발음 실수도 많이하고, 발표 속도를 조절하지 못한 점도 아쉬웠고, 여러 부분에 대해 신경쓰며 진행했는데, 그 부분들을 전부 보여주지 못하는 것 같아 많이 아쉬웠다.

 

프로젝트 파일

MVC파트 취합된 프로젝트 파일은 따로 zip파일로 업로드했다.

(Model파트는 최소 설계대로 패키지를 작업자별로 구분하고, 별도의 sql 계정을 정해 JDBCUtil을 작성했는데, 취합할 때 취합하는 분이 변경해 프로젝트를 취합하셨다.. 그런 이유로 프로젝트에서는 sql 계정명과 패키지 구분이 블로그 글에 정리된 내용과 일부 다를 수 있다.)

 

MMBProject.zip
13.08MB

 

 

 

 

테이블

먼저 중중프에서 사용한 테이블이다.

↓ 테이블, 뷰 쿼리문 보기

더보기
--ID: BREADFISH
--PW: 1234

SELECT * FROM BD_MEMBER;
SELECT * FROM BD_CATEGORY;
SELECT * FROM BD_BOARD;
SELECT * FROM BD_LIKE;
SELECT * FROM BD_IMAGE;
SELECT * FROM BD_REPLY;

DROP TABLE BD_MEMBER CASCADE CONSTRAINTS;
DROP TABLE BD_CATEGORY CASCADE CONSTRAINTS;
DROP TABLE BD_BOARD CASCADE CONSTRAINTS;
DROP TABLE BD_LIKE CASCADE CONSTRAINTS;
DROP TABLE BD_IMAGE CASCADE CONSTRAINTS;
DROP TABLE BD_REPLY CASCADE CONSTRAINTS;

CREATE TABLE BD_MEMBER (
   MEMBER_NUM         	NUMBER                        		--PK
   ,MEMBER_EMAIL      	VARCHAR2(100) UNIQUE NOT NULL      	--이메일 (아이디)
   ,MEMBER_PW         	VARCHAR2(100) NOT NULL            	--비밀번호
   ,MEMBER_NAME       	VARCHAR2(50) NOT NULL            	--이름
   ,MEMBER_PHONE       	VARCHAR2(50)                  		--전화번호
   ,MEMBER_NICKNAME   	VARCHAR2(100) UNIQUE NOT NULL      	--닉네임
   ,MEMBER_PROFILE_WAY 	VARCHAR2(500) NOT NULL            	--프로필 이미지 서버 경로
   ,MEMBER_ROLE      	VARCHAR2(20) DEFAULT 'user'         --권한 (일반, 점주, 관리자)
   ,MEMBER_HIREDAY      DATE DEFAULT SYSDATE            	--가입일
   ,CONSTRAINT PK_BD_MEMBER PRIMARY KEY(MEMBER_NUM)
   ,CONSTRAINT CHECK_MEMBER_ROLE CHECK(MEMBER_ROLE IN('user', 'seller', 'admin')) 
);


CREATE TABLE BD_CATEGORY (
	CATEGORY_NUM 	NUMBER									--카테고리 넘버 PK
	,CATEGORY_NAME 	VARCHAR2(30) NOT NULL UNIQUE			--카테고리명
	,CONSTRAINT PK_BD_CATEGORY PRIMARY KEY(CATEGORY_NUM)
);

CREATE TABLE BD_BOARD (
	BOARD_NUM			NUMBER								--게시물 넘버 PK
	,BOARD_TITLE		VARCHAR2(500) NOT NULL				--게시물 제목
	,BOARD_CONTENT		VARCHAR2(1000) NOT NULL				--게시물 내용
	,MEMBER_NUM			NUMBER 								--작성자 FK
	,BOARD_WRITE_DAY	DATE DEFAULT SYSDATE				--게시물 작성일시
	,BOARD_VISIBILITY	VARCHAR2(20) DEFAULT '공개' 			--공개 비공개 여부 (공개, 비공개)
	,BOARD_CATEGORY		NUMBER NOT NULL						--카테고리 FK
	,CONSTRAINT PK_BD_BOARD PRIMARY KEY(BOARD_NUM)
	,CONSTRAINT FK_BD_BOARD_MEMBER_NUM FOREIGN KEY(MEMBER_NUM) REFERENCES BD_MEMBER(MEMBER_NUM) ON DELETE SET NULL
	,CONSTRAINT FK_BD_BOARD_CATEGORY FOREIGN KEY(BOARD_CATEGORY) REFERENCES BD_CATEGORY(CATEGORY_NUM) ON DELETE CASCADE
	,CONSTRAINT CHECK_BOARD_VISIBILITY CHECK(BOARD_VISIBILITY IN('공개', '비공개'))
);

CREATE TABLE BD_LIKE (
	LIKE_NUM	NUMBER						--좋아요 PK
	,BOARD_NUM	NUMBER NOT NULL				--게시물 번호 FK
	,MEMBER_NUM	NUMBER						--좋아요 누른 사람 FK
	,CONSTRAINT PK_BD_LIKE PRIMARY KEY(LIKE_NUM)
	,CONSTRAINT FK_BD_LIKE_BOARD_NUM FOREIGN KEY(BOARD_NUM) REFERENCES BD_BOARD(BOARD_NUM) ON DELETE CASCADE 
	,CONSTRAINT FK_BD_LIKE_MEMBER_NUM FOREIGN KEY(MEMBER_NUM) REFERENCES BD_MEMBER(MEMBER_NUM) ON DELETE SET NULL
);

CREATE TABLE BD_IMAGE (
	IMAGE_NUM NUMBER						--이미지 넘버 PK
	,IMAGE_WAY VARCHAR2(500) NOT NULL		--이미지 서버 경로
	,BOARD_NUM NUMBER NOT NULL				--게시물 번호 FK
	,CONSTRAINT PK_BD_IMAGE PRIMARY KEY(IMAGE_NUM)
	,CONSTRAINT FK_BD_IMAGE_BOARD_NUM FOREIGN KEY(BOARD_NUM) REFERENCES BD_BOARD(BOARD_NUM) ON DELETE CASCADE 
);

CREATE TABLE BD_REPLY (
	REPLY_NUM		NUMBER					--댓글 번호 PK
	,REPLY_CONTENT	VARCHAR2(500) NOT NULL	--댓글 내용
	,MEMBER_NUM		NUMBER					--댓글 작성자 FK
	,REPLY_WRITE_DAY DATE DEFAULT SYSDATE 	--댓글 작성일시
	,BOARD_NUM		NUMBER NOT NULL			--댓글 달린 게시물 FK
	,CONSTRAINT PK_BD_REPLY PRIMARY KEY(REPLY_NUM)
	,CONSTRAINT FK_BD_REPLY_MEMBER_NUM FOREIGN KEY(MEMBER_NUM) REFERENCES BD_MEMBER(MEMBER_NUM) ON DELETE SET NULL
	,CONSTRAINT FK_BD_REPLY_BOARD_NUM FOREIGN KEY(BOARD_NUM) REFERENCES BD_BOARD(BOARD_NUM) ON DELETE CASCADE 
);

--보드 (+멤버, 카테고리, 좋아요) 뷰 생성
--뷰 생성
CREATE VIEW BD_BOARD_JOIN_MEMCATELIKE 
AS SELECT bb.BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, bm.MEMBER_NUM, MEMBER_NICKNAME, NVL(LIKE_COUNT, 0) AS LIKE_COUNT, BOARD_WRITE_DAY
FROM BD_BOARD bb 
JOIN BD_MEMBER bm ON bb.MEMBER_NUM = bm.MEMBER_NUM
JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM
LEFT OUTER JOIN (SELECT BOARD_NUM, COUNT(*) AS LIKE_COUNT FROM BD_LIKE GROUP BY BOARD_NUM) bl ON bb.BOARD_NUM = bl.BOARD_NUM;
--뷰 삭제
DROP VIEW BD_BOARD_JOIN_MEMCATELIKE;

 

6개의 테이블과, 1개의 뷰로 구성되어있으며, 테이블 설계는 진행할 프로젝트 기능에 맞춰 개념적 설계, 논리적 설계, 물리적 설계, 정규화, 역정규화 단계로 진행했다.

 

먼저 멤버는 여러 개의 게시글을 작성할 수 있고, 게시글은 하나의 작성자인 멤버를 가지기 때문에 한 테이블에 존재할 수 없어 멤버와 보드 테이블이 분리했다. 마찬가지로 좋아요와 댓글, 이미지도 일대다 관계를 가지기 때문에 분리했다. 카테고리는 보드 테이블의 컬럼으로 존재할 수도 있으나, 추후 추가, 변동 가능성을 고려하여 별도의 테이블로 분리해 유지보수가 용이하도록 설계했다.

 

이렇게 여러 개의 테이블로 분리하다보니, 조회할 때 여러 테이블을 조인하는 쿼리가 반복되어 복잡해지는 문제가 발생할 것이 예상됐다. 조회 성능을 올리기 위해 반복되어 사용되는 조인을 뷰로 정의했다.

 

뷰는 보드, 멤버, 카테고리, 좋아요 총합계 결과를 join하고 있으며, 이를 통해 보드를 조회할 때 멤버 닉네임과 카테고리명, 해당 게시물의 좋아요 총 개수를 함께 확인할 수 있게 되어있는 구조이다. 설계 후에는 멤버와 보드 기준으로 파트를 구분했다.

 

사실 여기서 아쉬웠던 부분이 있었는데, 글을 작성한 유저가 없어져도 게시글 등이 유지되도록 테이블 제약조건을 설정해두고선, 뷰에서 inner join을 사용하는 바람에 작성한 유저를 받아오는 fk가 null이 되면 조회되지 않게 되어버렸다.

이 부분은 중간프로젝트에서는 보완될 수 있도록 더욱 신경 써야겠다!

 

 

CRUD 분석

이미지는 게시글과 일대다관계로 게시글과 분리해 crud를 진행한다. insert delete selectAll 기능이 있으며, 설계 상 이미지 하나만을 단독으로 지정해 불러오는 경우는 없기 때문에 selectOne은 사용되지 않는다.

 

카테고리에는 insert update delete selectOne selectAll 5가지의 기능이 있고, 다른 테이블과의 연결없이 crud를 진행한다. 카테고리는 일반 유저가 변경할 일은 없으며, 관리자가 주로 관리하게 되는 부분이다.

 

게시글 DAO는 앞서 설명한 뷰를 통해 보드, 카테고리, 좋아요, 멤버 테이블을 조인하여 관련된 내용을 함께 확인, 반환하고 있다.

selectOne부터 보면, 컨디션 ONE으로 들어와 실행되는 기능은 게시글 상세조회 기능으로, 전달받은 보드의 PK값을 받아와 해당 게시글의 정보를 반환한다.

 

CNT와 CNTCONTENTS, CNTWRITER, CNTTITLE은 페이지네이션을 위한 게시글 총 개수를 반환한다. 이때 Controller에서는 DB에 카테고리의 PK(번호)를 알지 못하는 상황이기 때문에 카테고리명 (unique, not null (이름 변경될 가능성이 있어 PK로는 부적격함)) 을 받아 카테고리를 구분하게 됩니다. 검색어에 따른 게시글의 총 개수를 반환할 때는 키워드도 함께 받아오게 된다.

 

한 번 이상 데이터를 반환하게 되면 카테고리 PK도 함께 반환하게 되고, Controller는 이제 카테고리명이 아니라 카테고리 PK를 넘겨줄 수 있게 되기 때문에 새로운 보드 데이터를 insert할 때는 카테고리 명이 아닌 카테고리 넘버로 입력을 받을 수 있게 된다.

 

selectAll에서는 게시글 목록에서 띄워줄 내용을 반환하게 되는데, 게시글은 모두 페이지네이션 기능이 있기 때문에 시작번호와 끝 번호를 받아와 특정 범위의 데이터만을 반환하고 있다. 여기서도 selectOne과 마찬가지로 카테고리명을 받아와 카테고리를 구분하고 있다.

 

나머지 selectAll도 비슷한 형태이지만 인기글 3개를 반환하는 HOT_SELECTALL만 조금 다른 형태이다. 카테고리명을 받아와 해당 카테고리의 데이터를 반환하는 것은 같지만, 최소 좋아요 개수, 보여줄 게시글의 개수 기준에 맞춰 보여준다는 차이가 있다.

하지만 이는 사용자가 입력하는 값이 아니라 따로 시스템에서 고정된 값이므로 Controller로부터 받아오지는 않는다.

 

이렇게 받아온 데이터로 DB를 조회해 데이터를 Controller에게 반환한다.

 

 

↓ 쿼리문

더보기
--카테고리
--C
INSERT INTO BD_CATEGORY(CATEGORY_NUM, CATEGORY_NAME) VALUES((SELECT NVL(MAX(CATEGORY_NUM)+1,1) FROM BD_CATEGORY), '문의');
--U
UPDATE BD_CATEGORY SET CATEGORY_NAME = ? WHERE CATEGORY_NUM = 1;
--D
DELETE FROM BD_CATEGORY WHERE CATEGORY_NUM = 1;
--R
-- 현재 이름을 가진 카테고리의 pk반환 (selectOne)
SELECT CATEGORY_NUM FROM BD_CATEGORY WHERE CATEGORY_NAME = '문의';
-- 현재 카테고리 리스트 확인 (selectAll)
SELECT CATEGORY_NUM, CATEGORY_NAME FROM BD_CATEGORY;




--이미지
--C
INSERT INTO BD_IMAGE(IMAGE_NUM, IMAGE_WAY, BOARD_NUM) VALUES ((SELECT NVL(MAX(IMAGE_NUM)+1,1) FROM BD_IMAGE), '경로', 1);
--D
DELETE FROM BD_IMAGE WHERE IMAGE_NUM = 1;
--R 
--- 해당 게시물의 이미지 불러오기 (selectAll)
SELECT IMAGE_NUM, IMAGE_WAY FROM BD_IMAGE WHERE BOARD_NUM = 1;


--뷰 쿼리
CREATE VIEW BD_BOARD_JOIN_MEMCATELIKE 
AS SELECT bb.BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, bm.MEMBER_NUM, MEMBER_NICKNAME, 
	NVL(LIKE_COUNT, 0) AS LIKE_COUNT, BOARD_WRITE_DAY
FROM BD_BOARD bb 
JOIN BD_MEMBER bm ON bb.MEMBER_NUM = bm.MEMBER_NUM --INNER JOIN 
JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM --INNER JOIN 
LEFT OUTER JOIN (SELECT BOARD_NUM, COUNT(*) AS LIKE_COUNT FROM BD_LIKE GROUP BY BOARD_NUM) bl ON bb.BOARD_NUM = bl.BOARD_NUM; --LEFT JOIN 

--보드
--C
INSERT INTO BD_BOARD(BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, MEMBER_NUM)
VALUES((SELECT NVL(MAX(BOARD_NUM)+1,1) FROM BD_BOARD), '제목', '내용', '공개', 1, 1);
--U
UPDATE BD_BOARD SET BOARD_TITLE = '제목수정', BOARD_CONTENT = '내용수정', BOARD_VISIBILITY = '비공개' WHERE BOARD_NUM = 1;
--D
DELETE FROM BD_BOARD WHERE BOARD_NUM = 1;
--R
-- 게시글 상세 조회 (selectOne)
SELECT BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
FROM BD_BOARD_JOIN_MEMCATELIKE WHERE BOARD_NUM = 1;
-- 게시글 가장 최근 PK (selectOne)
SELECT MAX(BOARD_NUM) AS B_MAX_PK FROM BD_BOARD;
-- 게시판 별 글 개수 (selectOne)
SELECT COUNT(*) AS B_CNT 
FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM --카테고리 넘버 대신 카테고리 이름으로 조회하기 위한 INNER JOIN 
WHERE CATEGORY_NAME = '문의';
-- 게시판 별 글 개수 (제목) (selectOne)
SELECT COUNT(*) AS B_CNT 
FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM --카테고리 넘버 대신 카테고리 이름으로 조회하기 위한 INNER JOIN 
WHERE CATEGORY_NAME = '문의'  AND BOARD_TITLE LIKE '%'||''||'%';
-- 게시판 별 글 개수 (내용) (selectOne)
SELECT COUNT(*) AS B_CNT 
FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM --카테고리 넘버 대신 카테고리 이름으로 조회하기 위한 INNER JOIN 
WHERE CATEGORY_NAME = '문의'  AND BOARD_CONTENT LIKE '%'||''||'%';
-- 게시판 별 글 개수 (작성자/닉네임) (selectOne)
SELECT COUNT(*) AS B_CNT 
FROM BD_BOARD_JOIN_MEMCATELIKE 
WHERE CATEGORY_NAME = ?  AND MEMBER_NICKNAME LIKE '%'||''||'%';

-- 게시판 별 목록 (selectAll)
SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
FROM ( --범위를 지정하기 위해 서브쿼리 사용
	SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, 
		BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
	FROM BD_BOARD_JOIN_MEMCATELIKE  
	WHERE CATEGORY_NAME = '문의' 
	ORDER BY RN
	) 
WHERE RN BETWEEN 1 AND 3;

-- 게시판 별 인기글 3개 (selectAll)
SELECT ROWNUM, b.BOARD_NUM, b.BOARD_TITLE, b.BOARD_CONTENT, b.BOARD_VISIBILITY, b.BOARD_CATEGORY, b.CATEGORY_NAME, b.MEMBER_NUM, b.MEMBER_NICKNAME, b.LIKE_COUNT, b.BOARD_WRITE_DAY 
FROM ( --정렬한 후에 ROWNUM(가상번호)를 매기기 위해 서브쿼리 사용
	SELECT BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
	FROM BD_BOARD_JOIN_MEMCATELIKE 
	WHERE CATEGORY_NAME = '문의' AND LIKE_COUNT > 2 
	ORDER BY LIKE_COUNT DESC 
	) b 
WHERE ROWNUM <= 3 
ORDER BY BOARD_WRITE_DAY DESC;

-- 검색 기능 (제목) (selectAll)
SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
FROM ( --범위를 지정하기 위해 서브쿼리 사용
	SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, 
		BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
	FROM BD_BOARD_JOIN_MEMCATELIKE 
	WHERE CATEGORY_NAME = '문의' AND BOARD_TITLE LIKE '%'||''||'%' 
	ORDER BY RN 
	) 
WHERE RN BETWEEN 1 AND 3;

-- 검색 기능 (작성자) (selectAll)
SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
FROM ( --범위를 지정하기 위해 서브쿼리 사용
	SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, 
		BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
	FROM BD_BOARD_JOIN_MEMCATELIKE 
	WHERE CATEGORY_NAME = '문의' AND MEMBER_NICKNAME LIKE '%'||''||'%' 
	ORDER BY RN 
	) 
WHERE RN BETWEEN 1 AND 3;

-- 검색 기능 (내용) (selectAll)	
SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
FROM ( --범위를 지정하기 위해 서브쿼리 사용
	SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, 
		BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY 
	FROM BD_BOARD_JOIN_MEMCATELIKE 
	WHERE CATEGORY_NAME = '문의' AND BOARD_CONTENT LIKE '%'||''||'%' 
	ORDER BY RN 
	) 
WHERE RN BETWEEN 1 AND 3;

이미지와 카테고리는 다른 테이블과의 조인 없이 사용되며, PK에는 시스템에서 받아오는 값으로 들어갈 수 있도록 서브쿼리를 사용했다.

 

게시글에서는 insert update delete 때에는 조인 없이 사용되며, select에서는 조인이 사용된다. 뷰를 사용하고 있기 때문에, 4중 조인을 했지만 간결한 쿼리를 확인할 수 있다. 뷰에서 정의한 정도의 조인이 필요없는 부분에서는 별도의 조인을 사용해 필요한 데이터를 반환하고 있다.

 

selectAll 쿼리에서는 서브쿼리가 주로 실행 순서를 바꾸기 위해 사용된다.

페이지네이션을 하기위해서 특정 조건으로 정렬한 뒤 범위를 지정하기 위해 row_number라는 함수가 사용된다. 이 함수는 select절이 실행될 때 생성되는데, 범위를 지정하는 조건이 들어갈 수 있는 where절은 select절보다 앞서 실행되므로, row_number를 생성 한 뒤에 조건을 확인할 수 있도록 서브쿼리를 사용했다.

 

인기글 반환하는 부분에서는 rownum이라는 임시 행 번호를 매기는데, rownum은 select절에서 명시적으로 표시 해주지 않아도 where절에서 조건으로 줄 수 있는 성격을 가지고 있다. 하지만 정렬은 where절 이후에 실행되는 order by절에서 실행된다. 그럼 임의의 순서로 매겨졌던 rownum 순서가 섞여버리게 된다. 이를 보완하기 위해 서브쿼리를 사용해 정렬을 한 뒤에 정렬된 순서로 rownum이 생성될 수 있도록 했다. select절에서는 데이터 확인을 위해 rownum을 조회하고 있다.

 

 

 

↓ DTO 코드

더보기

카테고리 DTO

package model.jiyoon;

public class CategoryDTO {
	private int categoryNum;		//카테고리 pk
	private String categoryName;	//카테고리 이름
	
	
	//getter setter
	public int getCategoryNum() {
		return categoryNum;
	}
	public void setCategoryNum(int categoryNum) {
		this.categoryNum = categoryNum;
	}
	public String getCategoryName() {
		return categoryName;
	}
	public void setCategoryName(String categoryName) {
		this.categoryName = categoryName;
	}
	@Override
	public String toString() {
		return "CategoryDTO [categoryNum=" + categoryNum + ", cateboryName=" + categoryName + "]";
	}

}

 

 

이미지 DTO

package model.jiyoon;

public class ImageDTO {
	private int imageNum;		//이미지 pk
	private String imageWay;	//이미지 경로
	private int boardNum;		//게시물 pk(fk)
	
	
	//getter setter
	public int getImageNum() {
		return imageNum;
	}
	public void setImageNum(int imageNum) {
		this.imageNum = imageNum;
	}
	public String getImageWay() {
		return imageWay;
	}
	public void setImageWay(String imageWay) {
		this.imageWay = imageWay;
	}
	public int getBoardNum() {
		return boardNum;
	}
	public void setBoardNum(int boardNum) {
		this.boardNum = boardNum;
	}
	@Override
	public String toString() {
		return "ImageDTO [imageNum=" + imageNum + ", imageWay=" + imageWay + ", boardNum=" + boardNum + "]";
	}

}

 

 

게시글 DTO

package model.jiyoon;

public class BoardDTO {
	private int boardNum; 				//게시물 pk
	private String title; 				//제목
	private String content; 			//내용
	private String visibility; 			//공개여부 (공개, 비공개)
	private String writeDay;			//작성일자
	private int categoryNum; 			//카테고리 pk(fk)
	private String categoryName; 		//카테고리 이름
	private int memberNum; 				//멤버 pk(fk)
	private String memberNickname; 		//멤버 닉네임
	private int likeCnt; 				//좋아요 개수
	private String condition; 			//쿼리 구분값
	private String keyword; 			//검색어
	private int boardCnt;				//게시글 별 게시물 개수
	private int startNum;				//페이지네이션 게시물 시작 번호
	private int endNum;					//페이지네이션 게시물 끝 번호
	
	//getter setter
	public int getBoardNum() {
		return boardNum;
	}
	public void setBoardNum(int boardNum) {
		this.boardNum = boardNum;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getVisibility() {
		return visibility;
	}
	public void setVisibility(String visibility) {
		this.visibility = visibility;
	}
	public String getWriteDay() {
		return writeDay;
	}
	public void setWriteDay(String writeDay) {
		this.writeDay = writeDay;
	}
	public int getCategoryNum() {
		return categoryNum;
	}
	public void setCategoryNum(int categoryNum) {
		this.categoryNum = categoryNum;
	}
	public String getCategoryName() {
		return categoryName;
	}
	public void setCategoryName(String categoryName) {
		this.categoryName = categoryName;
	}
	public int getMemberNum() {
		return memberNum;
	}
	public void setMemberNum(int memberNum) {
		this.memberNum = memberNum;
	}
	public String getMemberNickname() {
		return memberNickname;
	}
	public void setMemberNickname(String memberNickname) {
		this.memberNickname = memberNickname;
	}
	public int getLikeCnt() {
		return likeCnt;
	}
	public void setLikeCnt(int likeCnt) {
		this.likeCnt = likeCnt;
	}
	public String getCondition() {
		return condition;
	}
	public void setCondition(String condition) {
		this.condition = condition;
	}
	public String getKeyword() {
		return keyword;
	}
	public void setKeyword(String keyword) {
		this.keyword = keyword;
	}
	public int getBoardCnt() {
		return boardCnt;
	}
	public void setBoardCnt(int boardCnt) {
		this.boardCnt = boardCnt;
	}
	public int getStartNum() {
		return startNum;
	}
	public void setStartNum(int startNum) {
		this.startNum = startNum;
	}
	public int getEndNum() {
		return endNum;
	}
	public void setEndNum(int endNum) {
		this.endNum = endNum;
	}
	@Override
	public String toString() {
		return "BoardDTO [boradNum=" + boardNum + ", title=" + title + ", content=" + content + ", visibility="
				+ visibility + ", writeDay=" + writeDay + ", categoryNum=" + categoryNum + ", categoryName="
				+ categoryName + ", memberNum=" + memberNum + ", memberNickname=" + memberNickname + ", likeCnt="
				+ likeCnt + ", condition=" + condition + ", keyword=" + keyword + ", boardCnt=" + boardCnt
				+ ", startNum=" + startNum + ", endNum=" + endNum + "]";
	}
}

게시글 DTO 필드를 살펴보면 조인이 여러 개 된 뷰를 사용하기 때문에, 보드 컬럼 외에도 카테고리 컬럼인 카테고리명, 멤버 컬럼인 멤버 닉네임, 좋아요 테이블에서 집계된 수인 좋아요 개수 필드가 있다. 그 외 컨디션, 키워드, 보드카운트, 스타트넘, 엔드넘은 개발을 위해 추가한 필드다.

 

 

 ↓ JDBCUtil

더보기
package model.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCUtil {

   private static final String driverName="oracle.jdbc.driver.OracleDriver";
   private static final String url="jdbc:oracle:thin:@localhost:1521:xe";
   private static final String userName="BREADFISH"; // sql 계정이름
   private static final String password="1234"; // 비밀번호
   
   public static Connection connect() {
      Connection conn=null;
      
      try {
         Class.forName(driverName);
      } catch (ClassNotFoundException e) {
         System.err.println("Class.forName(driverName) fail");
      } finally {
         System.out.println("드라이버를 메모리에 로드(load,적재)");
      }
      
      try {
         conn=DriverManager.getConnection(url, userName, password);
      } catch (SQLException e) {
         System.err.println("Connection fail");
      } finally {
         System.out.println("연결 객체 확보");
      }
      
      return conn;
   }
   
   public static boolean disconnect(Connection conn, PreparedStatement pstmt) {
      try {
    	 if(pstmt == null || conn == null) {
    		 System.err.println("pstmt, conn null");
    		 return false;
    	 }
         pstmt.close();
         conn.close();
      } catch (SQLException e) {
         System.err.println("pstmt, conn close fail");
         return false;
      } finally {
         System.out.println("연결 해제");
      }
      return true;
   }
   
}

 

 ↓ DAO

더보기

이미지 DAO

package model.jiyoon;

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

import model.common.JDBCUtil;

public class ImageDAO {
	private final String INSERT = "INSERT INTO BD_IMAGE VALUES ((SELECT NVL(MAX(IMAGE_NUM)+1,1) FROM BD_IMAGE), ?, ?)";
	private final String DELETE = "DELETE FROM BD_IMAGE WHERE IMAGE_NUM = ?";
	private final String SELECTALL = "SELECT IMAGE_NUM, IMAGE_WAY FROM BD_IMAGE WHERE BOARD_NUM = ?";
	
	public boolean insert(ImageDTO imageDTO) {
		System.out.println("log: Image insert start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(INSERT);
			pstmt.setString(1, imageDTO.getImageWay()); //이미지 경로
			pstmt.setInt(2, imageDTO.getBoardNum()); 	//게시물 pk(Fk)
			//넘어온 값 확인 로그
			System.out.println("log: parameter getImageWay : "+imageDTO.getImageWay());
			System.out.println("log: parameter getBoardNum : "+imageDTO.getBoardNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Image insert execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Image insert SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Image insert Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Image insert disconnect fail");
				return false;
			}
			System.out.println("log: Image insert end");
		}
		System.out.println("log: Image insert true");
		return true;
	}
	
	private boolean update(ImageDTO imageDTO) {
		return false;
	}
	
	public boolean delete(ImageDTO imageDTO) {
		System.out.println("log: Image delete start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(DELETE);
			pstmt.setInt(1, imageDTO.getImageNum()); //이미지 pk
			//넘어온 값 확인 로그
			System.out.println("log: parameter getImageNum : "+imageDTO.getImageNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Image delete execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Image delete SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Image delete Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Image delete disconnect fail");
				return false;
			}
			System.out.println("log: Image delete end");
		}
		System.out.println("log: Image delete true");
		return true;
	}
	
	public ArrayList<ImageDTO> selectAll(ImageDTO imageDTO) {
		System.out.println("log: Image selectAll start");
		ArrayList<ImageDTO> datas = new ArrayList<>();
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(SELECTALL);
			pstmt.setInt(1, imageDTO.getBoardNum()); //게시물 pk(Fk)
			//넘어온 값 확인 로그
			System.out.println("log: parameter getBoardNum : "+imageDTO.getBoardNum());
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) { 
				ImageDTO data = new ImageDTO();
				data.setImageNum(rs.getInt("IMAGE_NUM")); 		//이미지 pk
				data.setImageWay(rs.getString("IMAGE_WAY"));	//이미지 경로
				//반환된 객체 리스트에 추가
				datas.add(data); 
				System.out.print("result "+data.getImageNum()+" | ");
			}
			rs.close();
			System.out.println("end");
		} catch (SQLException e) {
			System.err.println("log: Image selectAll SQLException fail");
			datas.clear();//잔여데이터 삭제
		} catch (Exception e) {
			System.err.println("log: Image selectAll Exception fail");
			datas.clear();//잔여데이터 삭제
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Image selectAll disconnect fail");
				datas.clear();//잔여데이터 삭제
			}
			System.out.println("log: Image selectAll end");
		}
		System.out.println("log: Image selectAll return datas");
		return datas;
	}
	
	private ImageDTO selectOne(ImageDTO imageDTO) {
		ImageDTO data = null;
		return data;
	}
}

 

카테고리 DAO

package model.jiyoon;

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

import model.common.JDBCUtil;

public class CategoryDAO {
	private final String INSERT = "INSERT INTO BD_CATEGORY(CATEGORY_NUM, CATEGORY_NAME) VALUES((SELECT NVL(MAX(CATEGORY_NUM)+1,1) FROM BD_CATEGORY), ?)";
	private final String UPDATE = "UPDATE BD_CATEGORY SET CATEGORY_NAME = ? WHERE CATEGORY_NUM = ?";
	private final String DELETE = "DELETE FROM BD_CATEGORY WHERE CATEGORY_NUM = ?";
	
	private final String SELECTONE = "SELECT CATEGORY_NUM FROM BD_CATEGORY WHERE CATEGORY_NAME = ?";
	private final String SELECTALL = "SELECT CATEGORY_NUM, CATEGORY_NAME FROM BD_CATEGORY";	
	
	public boolean insert(CategoryDTO categoryDTO) {
		System.out.println("log: Category insert start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(INSERT);
			pstmt.setString(1, categoryDTO.getCategoryName()); //카테고리명
			System.out.println("log: parameter getCategoryName : "+categoryDTO.getCategoryName());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: parameter Category insert execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Category insert SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Category insert Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Category insert disconnect fail");
				return false;
			}
			System.out.println("log: Category insert end");
		}
		System.out.println("log: Category insert true");
		return true;
	}
	public boolean update(CategoryDTO categoryDTO) {
		System.out.println("log: Category update start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(UPDATE);
			pstmt.setString(1, categoryDTO.getCategoryName()); //카테고리 명
			System.out.println("log: parameter getCategoryName : "+categoryDTO.getCategoryName());
			pstmt.setInt(2, categoryDTO.getCategoryNum()); //카테고리 pk
			System.out.println("log: parameter getCategoryNum : "+categoryDTO.getCategoryNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Category update execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Category update SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Category update Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Category update disconnect fail");
				return false;
			}
			System.out.println("log: Category update end");
		}
		System.out.println("log: Category update true");
		return true;
	}
	public boolean delete(CategoryDTO categoryDTO) {
		System.out.println("log: Category delete start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(DELETE);
			pstmt.setInt(1, categoryDTO.getCategoryNum()); //카테고리 pk
			System.out.println("log: parameter getCategoryNum : "+categoryDTO.getCategoryNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Category delete execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Category delete SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Category delete Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Category delete disconnect fail");
				return false;
			}
			System.out.println("log: Category delete end");
		}
		System.out.println("log: Category delete true");
		return true;
	}
	public ArrayList<CategoryDTO> selectAll(CategoryDTO categoryDTO) {
		System.out.println("log: Category selectAll start");
		ArrayList<CategoryDTO> datas = new ArrayList<>();
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(SELECTALL);
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) { 
				CategoryDTO data = new CategoryDTO();
				data.setCategoryNum(rs.getInt("CATEGORY_NUM"));
				data.setCategoryName(rs.getString("CATEGORY_NAME"));
				//반환된 객체 리스트에 추가
				datas.add(data); 
				System.out.print("result "+data.getCategoryNum()+" | ");
			}
			rs.close();
			System.out.println("end");
		} catch (SQLException e) {
			System.err.println("log: Category selectAll SQLException fail");
			datas.clear();//잔여데이터 삭제
		} catch (Exception e) {
			System.err.println("log: Category selectAll Exception fail");
			datas.clear();//잔여데이터 삭제
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Category selectAll disconnect fail");
				datas.clear();//잔여데이터 삭제
			}
			System.out.println("log: Category selectAll end");
		}
		System.out.println("log: Category selectAll return datas");
		return datas;
	}
	public CategoryDTO selectOne(CategoryDTO categoryDTO) {
		System.out.println("log: Category selectOne start");
		CategoryDTO data = null;
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(SELECTONE);
			pstmt.setString(1, categoryDTO.getCategoryName());
			System.out.println("log: parameter getCategoryName : "+categoryDTO.getCategoryName());
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) { 
				data = new CategoryDTO();
				data.setCategoryNum(rs.getInt("CATEGORY_NUM"));
				System.out.println("result exists");
			}
			rs.close();
			System.out.println("end");
		} catch (SQLException e) {
			System.err.println("log: Category selectOne SQLException fail");
			return null; //실패
		} catch (Exception e) {
			System.err.println("log: Category selectOne Exception fail");
			return null; //실패
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Category selectOne disconnect fail");
				return null; //실패
			}
			System.out.println("log: Category selectOne end");
		}
		System.out.println("log: Category selectOne success");
		return data;
	}
}

 

게시글 DAO

package model.jiyoon;

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

import model.common.JDBCUtil;

public class BoardDAO {
	//쿼리
	//join없이 진행
	private final String INSERT = "INSERT INTO BD_BOARD(BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, MEMBER_NUM) "
								+ "VALUES((SELECT NVL(MAX(BOARD_NUM)+1,1) FROM BD_BOARD), ?, ?, ?, ?, ?)";
	private final String UPDATE = "UPDATE BD_BOARD SET BOARD_TITLE = ?, BOARD_CONTENT = ?, BOARD_VISIBILITY = ? WHERE BOARD_NUM = ?";
	private final String DELETE = "DELETE FROM BD_BOARD WHERE BOARD_NUM = ?";
	
	//join해서 진행
	//*BD_BOARD_JOIN_MEMCATELIKE => board, member, category, like 조인한 view
	private final String SELECTONE = "SELECT BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
									+ "FROM BD_BOARD_JOIN_MEMCATELIKE WHERE BOARD_NUM = ?";
	
	private final String SELECTONE_CNT = "SELECT COUNT(*) AS B_CNT FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM WHERE CATEGORY_NAME = ? ";
	
	private final String SELECTONE_TITLE_CNT = "SELECT COUNT(*) AS B_CNT FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM WHERE CATEGORY_NAME = ?  AND BOARD_TITLE LIKE '%'|| ? ||'%'";
	
	private final String SELECTONE_CONTENT_CNT = "SELECT COUNT(*) AS B_CNT FROM BD_BOARD bb JOIN BD_CATEGORY bc ON bb.BOARD_CATEGORY = bc.CATEGORY_NUM WHERE CATEGORY_NAME = ?  AND BOARD_CONTENT LIKE '%'|| ? ||'%'";
	
	private final String SELECTONE_NICKNAME_CNT = "SELECT COUNT(*) AS B_CNT FROM BD_BOARD_JOIN_MEMCATELIKE WHERE CATEGORY_NAME = ?  AND MEMBER_NICKNAME LIKE '%'|| ? ||'%' ";
	
	private final String SELECTONE_MAX_PK = "SELECT MAX(BOARD_NUM) AS B_MAX_PK FROM BD_BOARD";
	
	private final String SELECTALL = "SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
								+ "FROM (SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
								+ "FROM BD_BOARD_JOIN_MEMCATELIKE  WHERE CATEGORY_NAME = ? ORDER BY RN) "
								+ "WHERE RN BETWEEN ? AND ?";
	
	private final String SELECTALL_HOT = "SELECT ROWNUM, b.BOARD_NUM, b.BOARD_TITLE, b.BOARD_CONTENT, b.BOARD_VISIBILITY, b.BOARD_CATEGORY, b.CATEGORY_NAME, b.MEMBER_NUM, b.MEMBER_NICKNAME, b.LIKE_COUNT, b.BOARD_WRITE_DAY FROM ( "
										+ "SELECT BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
										+ "FROM BD_BOARD_JOIN_MEMCATELIKE WHERE CATEGORY_NAME = ? AND LIKE_COUNT > ? ORDER BY LIKE_COUNT DESC ) b WHERE ROWNUM <= ? ORDER BY BOARD_WRITE_DAY DESC ";
	
	private final String SELECTALL_TITLE = "SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
									+ "FROM (SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
									+ "FROM BD_BOARD_JOIN_MEMCATELIKE WHERE CATEGORY_NAME = ? AND BOARD_TITLE LIKE '%'|| ? ||'%' ORDER BY RN ) "
									+ "WHERE RN BETWEEN ? AND ? ";
	
	private final String SELECTALL_NICKNAME = "SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
										+ "FROM (SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
										+ "FROM BD_BOARD_JOIN_MEMCATELIKE WHERE CATEGORY_NAME = ? AND MEMBER_NICKNAME LIKE '%'|| ? ||'%' ORDER BY RN ) "
										+ "WHERE RN BETWEEN ? AND ? ";
	
	private final String SELECTALL_CONTENT = "SELECT RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
										+ "FROM (SELECT ROW_NUMBER() OVER(ORDER BY BOARD_WRITE_DAY DESC) AS RN, BOARD_NUM, BOARD_TITLE, BOARD_CONTENT, BOARD_VISIBILITY, BOARD_CATEGORY, CATEGORY_NAME, MEMBER_NUM, MEMBER_NICKNAME, LIKE_COUNT, BOARD_WRITE_DAY "
										+ "FROM BD_BOARD_JOIN_MEMCATELIKE WHERE CATEGORY_NAME = ? AND BOARD_CONTENT LIKE '%'|| ? ||'%' ORDER BY RN ) "
										+ "WHERE RN BETWEEN ? AND ? ";
	
	//고정 설정
	private final int MINLIKE = 5;		//인기글 최소 기준
	private final int SHOWHOTBOARD = 3;	//보여줄 인기글 개수
	
	public boolean insert(BoardDTO boardDTO) {
		System.out.println("log: Board insert start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(INSERT);
			pstmt.setString(1, boardDTO.getTitle()); 		//제목
			pstmt.setString(2, boardDTO.getContent()); 		//내용
			pstmt.setString(3, boardDTO.getVisibility()); 	//공개 비공개
			pstmt.setInt(4, boardDTO.getCategoryNum()); 	//카테고리 pk(fk)
			pstmt.setInt(5, boardDTO.getMemberNum()); 			//멤버 pk(fk)
			//넘어온 값 확인 로그
			System.out.println("log: parameter getTitle : "+boardDTO.getTitle());
			System.out.println("log: parameter getContent : "+boardDTO.getContent());
			System.out.println("log: parameter getVisibility : "+boardDTO.getVisibility());
			System.out.println("log: parameter getCategoryNum : "+boardDTO.getCategoryNum());
			System.out.println("log: parameter getMemberNum : "+boardDTO.getMemberNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Board insert execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Board insert SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Board insert Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Board insert disconnect fail");
				return false;
			}
			System.out.println("log: Board insert end");
		}
		System.out.println("log: Board insert true");
		return true;
	}
	public boolean update(BoardDTO boardDTO) {
		System.out.println("log: Board update start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(UPDATE);
			pstmt.setString(1, boardDTO.getTitle()); 		//제목
			pstmt.setString(2, boardDTO.getContent()); 		//내용
			pstmt.setString(3, boardDTO.getVisibility()); 	//공개 비공개
			pstmt.setInt(4, boardDTO.getBoardNum()); 		//게시물 pk
			//넘어온 값 확인 로그
			System.out.println("log: parameter getTitle : "+boardDTO.getTitle());
			System.out.println("log: parameter getContent : "+boardDTO.getContent());
			System.out.println("log: parameter getVisibility : "+boardDTO.getVisibility());
			System.out.println("log: parameter getBoardNum : "+boardDTO.getBoardNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Board update execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Board update SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Board update Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Board update disconnect fail");
				return false;
			}
			System.out.println("log: Board update end");
		}
		System.out.println("log: Board update true");
		return true;
	}
	public boolean delete(BoardDTO boardDTO) {
		System.out.println("log: Board delete start");
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(DELETE);
			pstmt.setInt(1, boardDTO.getBoardNum()); //게시글 pk
			//넘어온 값 확인 로그
			System.out.println("log: parameter getBoardNum : "+boardDTO.getBoardNum());
			if(pstmt.executeUpdate() <= 0) { 
				//쿼리는 정상적으로 실행됐으나 실패
				System.err.println("log: Board delete execute fail");
				return false;
			}
		} catch (SQLException e) {
			System.err.println("log: Board delete SQLException fail");
			return false;
		} catch (Exception e) {
			System.err.println("log: Board delete Exception fail");
			return false;
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Board delete disconnect fail");
				return false;
			}
			System.out.println("log: Board delete end");
		}
		System.out.println("log: Board delete true");
		return true;
	}
	public ArrayList<BoardDTO> selectAll(BoardDTO boardDTO) {
		System.out.println("log: Board selectAll start");
		ArrayList<BoardDTO> datas = new ArrayList<>();
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			if(boardDTO.getCondition().equals("ALL")) {
				//- 게시판 별 목록
				System.out.println("log: Board selectAll condition : SELECTALL");
				pstmt = conn.prepareStatement(SELECTALL);
				pstmt.setString(1, boardDTO.getCategoryName()); 	//카테고리 명
				pstmt.setInt(2, boardDTO.getStartNum());			//페이지네이션 시작번호
				pstmt.setInt(3, boardDTO.getEndNum());				//페이지네이션 끝번호	
				//넘어온 값 확인 로그
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				System.out.println("log: parameter getStartNum : "+boardDTO.getStartNum());
				System.out.println("log: parameter getEndNum : "+boardDTO.getEndNum());
			}
			else if(boardDTO.getCondition().equals("HOT_SELECTALL")) {
				//- 게시판 별 인기글 3개
				System.out.println("log: Board selectAll condition : SELECTALL_HOT");
				pstmt = conn.prepareStatement(SELECTALL_HOT);
				pstmt.setString(1, boardDTO.getCategoryName()); 	//카테고리 명
				pstmt.setInt(2, MINLIKE);							//인기글 최소 기준
				pstmt.setInt(3, SHOWHOTBOARD); 						//보여줄 인기글 개수
				//넘어온 값 확인 로그
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
			}
			else if(boardDTO.getCondition().equals("TITLE_SELECTALL")) {
				//- 검색 기능 (제목)
				System.out.println("log: Board selectAll condition : SELECTALL_TITLE");
				pstmt = conn.prepareStatement(SELECTALL_TITLE);
				pstmt.setString(1, boardDTO.getCategoryName()); 	//카테고리 명
				pstmt.setString(2, boardDTO.getKeyword());			//검색어
				pstmt.setInt(3, boardDTO.getStartNum());			//페이지네이션 시작번호
				pstmt.setInt(4, boardDTO.getEndNum());				//페이지네이션 끝번호	
				//넘어온 값 확인 로그
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				System.out.println("log: parameter getStartNum : "+boardDTO.getStartNum());
				System.out.println("log: parameter getEndNum : "+boardDTO.getEndNum());
			}
			else if(boardDTO.getCondition().equals("NICKNAMEV")) {
				//- 검색 기능 (작성자)
				System.out.println("log: Board selectAll condition : SELECTALL_NICKNAME");
				pstmt = conn.prepareStatement(SELECTALL_NICKNAME);
				pstmt.setString(1, boardDTO.getCategoryName()); 	//카테고리 명
				pstmt.setString(2, boardDTO.getKeyword());			//검색어
				pstmt.setInt(3, boardDTO.getStartNum());			//페이지네이션 시작번호
				pstmt.setInt(4, boardDTO.getEndNum());				//페이지네이션 끝번호	
				//넘어온 값 확인 로그
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				System.out.println("log: parameter getStartNum : "+boardDTO.getStartNum());
				System.out.println("log: parameter getEndNum : "+boardDTO.getEndNum());
			}
			else if(boardDTO.getCondition().equals("CONTENT_SELECTALL")) {
				//- 검색 기능 (내용)
				System.out.println("log: Board selectAll condition : SELECTALL_CONTENT");
				pstmt = conn.prepareStatement(SELECTALL_CONTENT);
				pstmt.setString(1, boardDTO.getCategoryName()); 	//카테고리 명
				pstmt.setString(2, boardDTO.getKeyword());			//검색어
				pstmt.setInt(3, boardDTO.getStartNum());			//페이지네이션 시작번호
				pstmt.setInt(4, boardDTO.getEndNum());				//페이지네이션 끝번호
				//넘어온 값 확인 로그
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				System.out.println("log: parameter getStartNum : "+boardDTO.getStartNum());
				System.out.println("log: parameter getEndNum : "+boardDTO.getEndNum());
			}
			else {
				System.err.println("log: Board selectAll condition fail");
			}
			ResultSet rs = pstmt.executeQuery();
			while(rs.next()) { 
				BoardDTO data = new BoardDTO();
				data.setBoardNum(rs.getInt("BOARD_NUM"));						//게시글 pk
				data.setTitle(rs.getString("BOARD_TITLE"));						//제목
				data.setContent(rs.getString("BOARD_CONTENT"));					//내용
				data.setVisibility(rs.getString("BOARD_VISIBILITY"));			//공개 비공개
				data.setCategoryNum(rs.getInt("BOARD_CATEGORY"));				//카테고리 pk(fk)
				data.setCategoryName(rs.getString("CATEGORY_NAME"));			//카테고리 명
				data.setMemberNum(rs.getInt("MEMBER_NUM"));	 					//작성자 pk(fk)
				data.setMemberNickname(rs.getString("MEMBER_NICKNAME"));		//작성자 닉네임
				data.setLikeCnt(rs.getInt("LIKE_COUNT")); 						//좋아요 수 
				data.setWriteDay(rs.getString("BOARD_WRITE_DAY")); 				//작성일자
				//반환된 객체 리스트에 추가
				datas.add(data); 
				System.out.print("result "+data.getCategoryNum()+" | ");
			}
			rs.close();
			System.out.println("end");
		} catch (SQLException e) {
			System.err.println("log: Board selectAll SQLException fail");
			datas.clear();//잔여데이터 삭제
		} catch (Exception e) {
			System.err.println("log: Board selectAll Exception fail");
			datas.clear();//잔여데이터 삭제
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Board selectAll disconnect fail");
				datas.clear();//잔여데이터 삭제
			}
			System.out.println("log: Board selectAll end");
		}
		System.out.println("log: Board selectAll return datas");
		return datas;
	}
	public BoardDTO selectOne(BoardDTO boardDTO) {
		BoardDTO data = null;
		System.out.println("log: Board selectOne start");
		ArrayList<BoardDTO> datas = new ArrayList<>();
		Connection conn = JDBCUtil.connect();
		PreparedStatement pstmt = null;
		try {
			if(boardDTO.getCondition().equals("ONE")) {
				//- 게시글 상세 조회
				System.out.println("log: Board selectOne condition : SELECTONE");
				pstmt = conn.prepareStatement(SELECTONE);
				pstmt.setInt(1, boardDTO.getBoardNum()); 			//게시글 번호
				System.out.println("log: parameter getBoardNum : "+boardDTO.getBoardNum());
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardNum(rs.getInt("BOARD_NUM"));				//게시글 pk
					data.setTitle(rs.getString("BOARD_TITLE"));				//제목
					data.setContent(rs.getString("BOARD_CONTENT"));			//내용
					data.setVisibility(rs.getString("BOARD_VISIBILITY"));	//공개 비공개
					data.setCategoryNum(rs.getInt("BOARD_CATEGORY"));		//카테고리 pk(fk)
					data.setCategoryName(rs.getString("CATEGORY_NAME"));	//카테고리 명
					data.setMemberNum(rs.getInt("MEMBER_NUM"));	 			//작성자 pk(fk)
					data.setMemberNickname(rs.getString("MEMBER_NICKNAME"));//작성자 닉네임
					data.setLikeCnt(rs.getInt("LIKE_COUNT")); 				//좋아요 수 
					data.setWriteDay(rs.getString("BOARD_WRITE_DAY")); 		//작성일자
					System.out.println("result true");
				}
				rs.close();
			}
			else if(boardDTO.getCondition().equals("CNT")) {
				//- 게시판 별 글 개수
				System.out.println("log: Board selectOne condition : SELECTONE_CNT");
				pstmt = conn.prepareStatement(SELECTONE_CNT);
				pstmt.setString(1, boardDTO.getCategoryName()); 			//카테고리 명
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardCnt(rs.getInt("B_CNT"));			//게시판 별 게시글 총 개수
					System.out.println("result true");
				}
				rs.close();
			}
			else if(boardDTO.getCondition().equals("CNTCONTENTS")) {
				//- 게시판 별 내용 검색 글 개수
				System.out.println("log: Board selectOne condition : SELECTONE_CONTENT_CNT");
				pstmt = conn.prepareStatement(SELECTONE_CONTENT_CNT);
				pstmt.setString(1, boardDTO.getCategoryName()); 			//카테고리 명
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				pstmt.setString(2, boardDTO.getKeyword()); 					//검색어
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardCnt(rs.getInt("B_CNT"));			//게시판 별 게시글 총 개수
					System.out.println("result true");
				}
				rs.close();
			}
			else if(boardDTO.getCondition().equals("CNTWRITER")) {
				//- 게시판 별 닉네임 검색 글 개수
				System.out.println("log: Board selectOne condition : SELECTONE_NICKNAME_CNT");
				pstmt = conn.prepareStatement(SELECTONE_NICKNAME_CNT);
				pstmt.setString(1, boardDTO.getCategoryName()); 			//카테고리 명
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				pstmt.setString(2, boardDTO.getKeyword()); 					//검색어
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardCnt(rs.getInt("B_CNT"));			//게시판 별 게시글 총 개수
					System.out.println("result exists");
				}
				rs.close();
			}
			else if(boardDTO.getCondition().equals("CNTTITLE")) {
				//- 게시판 별 제목 검색 글 개수
				System.out.println("log: Board selectOne condition : SELECTONE_TITLE_CNT");
				pstmt = conn.prepareStatement(SELECTONE_TITLE_CNT);
				pstmt.setString(1, boardDTO.getCategoryName()); 			//카테고리 명
				System.out.println("log: parameter getCategoryName : "+boardDTO.getCategoryName());
				pstmt.setString(2, boardDTO.getKeyword()); 					//검색어
				System.out.println("log: parameter getKeyword : "+boardDTO.getKeyword());
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardCnt(rs.getInt("B_CNT"));			//게시판 별 게시글 총 개수
					System.out.println("result exists");
				}
				rs.close();
			}
			else if(boardDTO.getCondition().equals("MAXPK")) {
				//- 가장 마지막으로 사용된 PK넘버
				System.out.println("log: Board selectOne condition : SELECTONE_MAX_PK");
				pstmt = conn.prepareStatement(SELECTONE_MAX_PK);
				ResultSet rs = pstmt.executeQuery();
				if(rs.next()) { 
					data = new BoardDTO();
					data.setBoardCnt(rs.getInt("B_MAX_PK"));		//가장 마지막으로 사용된 PK넘버
					System.out.println("result exists");
				}
				rs.close();
			}
			else {
				System.err.println("log: Board selectOne condition fail");
			}
			System.out.println("end");
		} catch (SQLException e) {
			System.err.println("log: Board selectOne SQLException fail");
			datas.clear();//잔여데이터 삭제
		} catch (Exception e) {
			System.err.println("log: Board selectOne Exception fail");
			datas.clear();//잔여데이터 삭제
		} finally {
			//연결해제
			if(!JDBCUtil.disconnect(conn, pstmt)) {
				//연결해제 실패
				System.err.println("log: Board selectOne disconnect fail");
				datas.clear();//잔여데이터 삭제
			}
			System.out.println("log: Board selectOne end");
		}
		System.out.println("log: Board selectOne return datas");
		return data;
	}
}

게시글 DAO의 selectOne과 selectAll은 기능이 여러 개이기 때문에 컨디션을 받아와 구분해 실행한다. 해당 컨디션 값은 controller와 설계 단계에서 약속된 값이다.

selectAll에서는 반환하는 정보의 개수가 모두 동일하기 때문에 컨디션값으로 분기를 나눈 곳에서는 쿼리문을 세팅만하고 결과값은 한 번에 실행하는 구조이다. 반면  selectOne은 각 컨디션에 따라 반환하는 데이터의 종류가 다르기 때문에 결과값을 받아오는 구문도 컨디션 분기 안에 있는 구조임을 확인할 수 있다.

 

게시글 DAO selectAll에서 인기글을 반환하는 hot_selectAll에서는 따로 기준을 입력받도록 되어있다. 해당 범위는 사용자에게 입력받은 기준이 아니라 고정된 기준이라 쿼리문에 고정시켜도 되지만, 차후 변경될 수 있는 점을 고려하여 유지보수를 위해 상단에 따로 변수를 선언해 사용하고 있다.

 

로그는 Controller에서 최대한 모델파트 코드를 확인하지 않더라도 값이 정상적으로 전달되는지, 진행이 어떻게 되는지를 콘솔창에서 확인 할 수 있도록 하고자 노력했다.