문제

점수계산


OX 문제는 맞거나 틀린 두 경우의 답을 가지는 문제를 말한다. 여러 개의 OX 문제로 만들어진
시험에서 연속적으로 답을 맞히는 경우에는 가산점을 주기 위해서 다음과 같이 점수 계산을 하기
로 하였다. 1번 문제가 맞는 경우에는 1점으로 계산한다. 앞의 문제에 대해서는 답을 틀리다가
답이 맞는 처음 문제는 1점으로 계산한다. 또한, 연속으로 문제의 답이 맞는 경우에서 두 번째
문제는 2점, 세 번째 문제는 3점, ..., K번째 문제는 K점으로 계산한다. 틀린 문제는 0점으로 계
산한다.
예를 들어, 아래와 같이 10 개의 OX 문제에서 답이 맞은 문제의 경우에는 1로 표시하고, 틀린 경
우에는 0으로 표시하였을 때, 점수 계산은 아래 표와 같이 계산되어, 총 점수는
1+1+2+3+1+2=10 점이다.


 1 0 1 1 1 0 0 1 1 0

 


시험문제의 채점 결과가 주어졌을 때, 총 점수를 계산하는 프로그램을 작성하시오.


▣ 입력설명
첫째 줄에 문제의 개수 N (1 ≤ N ≤ 100)이 주어진다. 둘째 줄에는 N개 문제의 채점 결과를 나
타내는 0 혹은 1이 빈 칸을 사이에 두고 주어진다. 0은 문제의 답이 틀린 경우이고, 1은 문제의
답이 맞는 경우이다.


▣ 출력설명
첫째 줄에 입력에서 주어진 채점 결과에 대하여 가산점을 고려한 총 점수를 출력한다.


▣ 입력예제 1
10
1 0 1 1 1 0 0 1 1 0


▣ 출력예제 1
10

 


내 풀이

# 내 풀이
n = int(input())
a = list(map(int, input().split()))
sum = 0
cnt = 1
for i in range(n):
    if a[i] == 1 :
        #print(i+1, "번쨰 점수 더할 cnt 는", cnt)
        sum += cnt
        cnt += 1

    else:
        cnt = 1
        #print(i+1, "번쨰 점수 cnt 는", cnt)
print(sum)

점수


강사님 풀이

# 강사님 풀이
n = int(input())
a = list(map(int, input().split()))

sum = 0
cnt = 0
for x in a:
    if x == 1:
        cnt += 1
        sum += cnt
    else:
        cnt = 0
print(sum)

문제

주사위 게임


1에서부터 6까지의 눈을 가진 3개의 주사위를 던져서 다음과 같은 규칙에 따라 상금을 받는 게
임이 있다.


규칙(1) 같은 눈이 3개가 나오면 10,000원+(같은 눈)*1,000원의 상금을 받게 된다.
규칙(2) 같은 눈이 2개만 나오는 경우에는 1,000원+(같은 눈)*100원의 상금을 받게 된다.
규칙(3) 모두 다른 눈이 나오는 경우에는 (그 중 가장 큰 눈)*100원의 상금을 받게 된다.


예를 들어, 3개의 눈 3, 3, 6이 주어지면 상금은 1,000+3*100으로 계산되어 1,300원을 받게 된
다. 또 3개의 눈이 2, 2, 2로 주어지면 10,000+2*1,000 으로 계산되어 12,000원을 받게 된다.
3개의 눈이 6, 2, 5로 주어지면 그 중 가장 큰 값이 6이므로 6*100으로 계산되어 600원을 상금
으로 받게 된다.
N 명이 주사위 게임에 참여하였을 때, 가장 많은 상금을 받은 사람의 상금을 출력하는 프로그램
을 작성하시오


▣ 입력설명
첫째 줄에는 참여하는 사람 수 N(2<=N<=1,000)이 주어지고 그 다음 줄부터 N개의 줄에 사람
들이 주사위를 던진 3개의 눈이 빈칸을 사이에 두고 각각 주어진다.


▣ 출력설명
첫째 줄에 가장 많은 상금을 받은 사람의 상금을 출력한다.


▣ 입력예제 1
3
3 3 6
2 2 2
6 2 5


▣ 출력예제 1
12000


내 풀이

# 내 풀이
n = int(input())
maxNum = 0
for i in range(n):
    arr = list(map(int,input().split()))
    prize_money = 0

    if arr[0] == arr[1]:
        num = arr[0]
        if arr[1] == arr[2]:
            prize_money = 10000 + num * 1000
        else:
            prize_money = 1000 + num * 100
    elif arr[1] == arr[2]:
        num = arr[1]
        if arr[1] == arr[0]:
            prize_money = 10000 + num * 1000
        else:
             prize_money = 1000 + num * 100           
    elif arr[2] == arr[0]:
        num = arr[2]
        if arr[2] == arr[1]:
            prize_money = 10000 + num * 1000
        else:
            prize_money = 1000 + num * 100
    else:
        prize_money = max(arr) * 100
    #print(prize_money)
    if prize_money > maxNum:
        maxNum = prize_money
print(maxNum)

점수


강사님 풀이

# 강사님 풀이
n = int(input())
res = 0
for i in range(n):
    tmp = input().split() # 문자형으로, ['1', '2', '3'] 저장
    tmp.sort() # 눈이 다르면 큰수 찾아야하므로 정렬
    a, b, c = map(int, tmp) # c 가 가장 큰 수
    #print(a, b, c)
    if a == b and b == c: # 가장 좋은 조건을 가장 먼저 if 로 씀
        money = 10000 + a * 1000
    elif a == b or a == c:
        money = 1000 + (a * 100)
    elif b == c:
        money = 1000 + (b * 100)
    else:
        money = c * 100
    if money > res:
        res = money
print(res)

# 가장 좋은 조건을 먼저 if 로 작성

# a == b 와 a ==  c 일땐 공통인 값은 a 로 쓰면 되고 , b == c 일땐 공통인 값은 b 로 쓰면 되므로 두 경우를 따로 작성


문제

뒤집은 소수
N개의 자연수가 입력되면 각 자연수를 뒤집은 후 그 뒤집은 수가 소수이면 그 수를 출력하는
프로그램을 작성하세요. 예를 들어 32를 뒤집으면 23이고, 23은 소수이다. 그러면 23을 출력
한다. 단 910를 뒤집으면 19로 숫자화 해야 한다. 첫 자리부터의 연속된 0은 무시한다.
뒤집는 함수인 def reverse(x) 와 소수인지를 확인하는 함수 def isPrime(x)를 반드시 작성하
여 프로그래밍 한다.


▣ 입력설명
첫 줄에 자연수의 개수 N(3<=N<=100)이 주어지고, 그 다음 줄에 N개의 자연수가 주어진다.
각 자연수의 크기는 100,000를 넘지 않는다.


▣ 출력설명
첫 줄에 뒤집은 소수를 출력합니다. 출력순서는 입력된 순서대로 출력합니다.


▣ 입력예제 1
5
32 55 62 3700 250


▣ 출력예제 1
23 73


내 풀이

# 내 풀이
def reverse(x):
    sum = 0
    mul = 100000
    while x > 0:
        mul /= 10
        sum += x % 10 * mul
        x = x // 10
    return int(sum/mul)

def isPrime(x):
    if x == 1:
        return False
    for i in range(2, x):
        if x % i == 0:
            return False
    return True

n = input()
nums = list(map(int, input().split()))

for x in nums:
    rev = reverse(x)
    if isPrime(rev):
        print(rev, end=" ")

점수


강사님 풀이

# 강사님 풀이
n = input()
a = list(map(int, input().split()))

def reverse(x):
    res = 0
    while x > 0:
        t = x % 10
        res = res * 10 + t
        x = x // 10
    return res

def isPrime(x):
    if x == 1:
        return False
    for i in range(2, x//2 + 1): # + 1 해줘야 절반'까지' 돈다
        if x % i == 0:
            return False
    else:
        return True

for x in a:
    tmp = reverse(x)
    if isPrime(tmp):
        print(tmp, end=" ")

<reverse>
# 125 % 10 == 5 끝자리 구해짐

# res = res * 10 + 끝자리 를 반복하면 한자리씩 올라가면서 누적된다

 

<isPrime>

# 16 에서 1, 16 을 제외한 약수는 8 까지 존재한다
# 16 의 약수 : 1 2 4 8 16
# 그러므로 절반인 8 까지만 돌면 그 뒤로는 자신을 제외한  약수가 없다
# 즉 8 까지 나눠지는 수가 없으면 소수이다


문제

소수(에라토스테네스 체)


자연수 N이 입력되면 1부터 N까지의 소수의 개수를 출력하는 프로그램을 작성하세요.
만약 20이 입력되면 1부터 20까지의 소수는 2, 3, 5, 7, 11, 13, 17, 19로 총 8개입니다.
제한시간은 1초입니다.


▣ 입력설명
첫 줄에 자연수의 개수 N(2<=N<=200,000)이 주어집니다.


▣ 출력설명
첫 줄에 소수의 개수를 출력합니다.


▣ 입력예제 1
20


▣ 출력예제 1
8


내 풀이

# 내 풀이
n = int(input()) # N
# 소수 : 나눠지는 수가 1 과 자신뿐, 1 은 소수 아님
count = 0
for x in range(2, n+1): # 2 부터 N 까지
    for i in range(2, x):
        if x % i == 0:
            break
    else:
        count += 1
print(count)

 

점수


강사님 풀이

n = int(input()) # N
ch = [0] * (n+1)
cnt = 0
for i in range(2, n+1):
    if ch[i] == 0:
        cnt += 1
        for j in range(i, n+1, i): # i 부터 i 씩 증가 = i 의 배수
            ch[j] = 1
print(cnt)

# 에라토스테네스 체

# 2를 카운트하고, 2의 배수는 모두 지우기, 3을 카운트하고, 3의 배수는 모두 지우기 ...


문제

자릿수의 합


N개의 자연수가 입력되면 각 자연수의 자릿수의 합을 구하고, 그 합이 최대인 자연수를 출력
하는 프로그램을 작성하세요. 각 자연수의 자릿수의 합을 구하는 함수를 def digit_sum(x)를
꼭 작성해서 프로그래밍 하세요.


▣ 입력설명
첫 줄에 자연수의 개수 N(3<=N<=100)이 주어지고, 그 다음 줄에 N개의 자연수가 주어진다.
각 자연수의 크기는 10,000,000를 넘지 않는다.


▣ 출력설명
자릿수의 합이 최대인 자연수를 출력한다. 자릿수의 합이 같을 경우 입력순으로 먼저인 숫자
를 출력합니다.


▣ 입력예제 1
3
125 15232 97


▣ 출력예제 1
97


내 풀이

# 내 풀이
def digit_sum(x):
    sum = 0
    init = 1000000
    for i in range(7):
        if(x // init > 0):
            sum += (x//init)
            x = x % init
        init /= 10
    return sum

n = input()
num = list(map(int, input().split()))

max = 0
index = -1
for i, x in enumerate(num):
    result = digit_sum(x)
    if result > max:
        max = result
        end = x  

print(end)

점수


강사님 풀이

# 강사님 풀이
#import sys
#sys.stdin = open("input.txt", "r")
n = input()
a = list(map(int, input().split()))

def digit_sum(x):
    sum=0
    while x > 0 :
        sum += x % 10 # 끝자리수가 나온다 125 % 10 == 5
        x = x // 10 # 마지막 자리 를 없앤다 125 // 10 == 12
    return sum

max = - 2147000000
for x in a:
    tot = digit_sum(x)
    if tot > max:
        max = tot
        res = x
print(res)

# 125 % 10 == 5

# 125 // 10 == 12

# 끝자리부터 더하면 된다

# 또다른 풀이

# 파이썬에선 이런 풀이도 가능
def digit_sum1(x):
    sum = 0
    for i in str(x): # x 를 문자열처리, i 는 각 자리 문자 하나를 받음
        sum += int(i)
    return sum

max = -2147000000 # 4바이트 정수형 최소 숫자

for x in a:
    tot = digit_sum1(x)
    if tot > max:
        max = tot
        res = x
print(res)

# 각 자리를 문자열로 받고 반복문으로 한자리씩 구해서 더하기


문제

정다면체


두 개의 정 N면체와 정 M면체의 두 개의 주사위를 던져서 나올 수 있는 눈의 합 중 가장 확
률이 높은 숫자를 출력하는 프로그램을 작성하세요.
정답이 여러 개일 경우 오름차순으로 출력합니다.


▣ 입력설명
첫 번째 줄에는 자연수 N과 M이 주어집니다. N과 M은 4, 6, 8, 12, 20 중의 하나입니다.


▣ 출력설명
첫 번째 줄에 답을 출력합니다.


▣ 입력예제 1
4 6


▣ 출력예제 1
5 6 7


내 풀이

# 내 코드
n, m = map(int, input().split())
#print(n, m)
maxNum = n + m
sum = []
# 주사위 모든 경우의 수
count = [0 for i in range(maxNum)]
for i in range(n):
    for j in range(m):
        sum.append((i+1) + (j+1))
        
#print(sum)
#print(len(sum)) # 4 * 6

# 카운트하기
for i in range(maxNum):
    for j in range(len(sum)):
        if sum[j]== i + 1:
            count[i] += 1

#print(count)

# 가장 큰 수 고르기 (가장 많은 확률)
minIndex = -1
maxIndex = -1
maxNum = 0
for i, x in enumerate(count):
    if x > maxNum:
        maxNum = x
        minIndex = i
    if x == maxNum:
        maxIndex = i
#print(maxNum)
#print(minIndex)
#print(maxIndex)

# 그 경우의 index 구하기
for i in range (minIndex + 1, maxIndex + 2): #인덱스조정
    print(i, end=" ")

점수


강사님 풀이

# 강사님 코드
#import sys
#sys.stdin=open("input,txt", "r")
n, m = map(int, input().split())
cnt = [0]*(n+m+3) # 넉넉히 잡음
max=-2147000000

for i in range(1, n+1):
    for j in range(1, m+1):
        cnt[i+j] += 1
        
for i in range(n+m+1):
    if cnt[i] > max:
        max = cnt[i]
        
for i in range(n+m+1):
    if cnt[i] == max:
        print(i, end=" ")

문제

대표값


N명의 학생의 수학점수가 주어집니다. N명의 학생들의 평균(소수 첫째자리 반올림)을 구하고,
N명의 학생 중 평균에 가장 가까운 학생은 몇 번째 학생인지 출력하는 프로그램을 작성하세
요.


평균과 가장 가까운 점수가 여러 개일 경우 먼저 점수가 높은 학생의 번호를 답으로 하고, 높
은 점수를 가진 학생이 여러 명일 경우 그 중 학생번호가 빠른 학생의 번호를 답으로 합니다.


▣ 입력설명
첫줄에 자연수 N(5<=N<=100)이 주어지고, 두 번째 줄에는 각 학생의 수학점수인 N개의 자연
수가 주어집니다. 학생의 번호는 앞에서부터 1로 시작해서 N까지이다.


▣ 출력설명
첫줄에 평균과 평균에 가장 가까운 학생의 번호를 출력한다.
평균은 소수 첫째 자리에서 반올림합니다.


▣ 입력예제 1
10
45 73 66 87 92 67 75 79 75 80


▣ 출력예제 1
74 7


예제설명)
평균이 74점으로 평균과 가장 가까운 점수는 73(2번), 75(7번), 75(9번)입니다. 여기서 점수가 높은
75(7번), 75(9번)이 답이 될 수 있고, 75점이 두명이므로 학생번호가 빠른 7번이 답이 됩니다.


내 풀이

# 내 풀이
n = int(input())
arr = list(map(int,input().split()))
sum = 0
# 평균 구하기
for x in arr :
    sum += x
avg = round(sum/n)

#print(avg)
# 차이 구하기
diffArr = []
for i in range(n):
    diffArr.append(abs(avg-arr[i]))

#print(diffArr)
# 절대값의 최소값 구하기
arrMin = float('inf')
index = -1
for i in range(n):
    if diffArr[i] < arrMin:
        arrMin = diffArr[i]
        index = i
#print(arrMin)
#print(-arrMin)
#print(index)

#절대값이 최소값인 것 찾기
result = 0
for i in range(n):
    if diffArr[i] == -arrMin:
        result = i + 1
        break
else:
    result = index + 1

#print(result)

print(avg, result)

점수


강사님 풀이

#import sys
#sys.stdin=open("input.txt", "r")
n = int(input())
a = list(map(int,input().split()))
min = 2147000000

#평균 구하기
ave = round(sum(a)/n) # sum 은 리스트의 모든 값 합해

#평균과 가까운 값 탐색
for idx, x in enumerate(a): # a 리스트의 값 (0,a[0]) 이 반환 idx = 0, x = a[0] 이 됨
    tmp = abs(x-ave)
    if tmp < min :
        min = tmp
        score = x # 차이의 절대값이 가장 작은 값일때 점수를 저장
        res = idx + 1 # 그때의 학생번호 
    elif tmp == min : # 같은
        if x > score : # 현재 점수가 저번에 저장된 점수보다 크면
            score = x
            res = idx + 1
print(ave,res)

# 절대값이 작은 값일땐 계속 작은값을 넣는식, 절대값이 같은 값일땐 큰값을 넣음
# 73 75 73 75 일때, 평균이 74
# 처음에 x 가 73일때 1이 min 이 된다, tmp 는 1
# 다음에 x 가 75알때 tmp 가 1 이므로 elif 에서 75 > score인 73 이므로 75가 score 가 됨
# 다음에 x 가 73일때 tmp 가 1 이므로 elif 에서 73 > score인 75 false 이므로 넘어감
# 다음에 x 가 75일때 tmp 가 1 이므로 elif 에서 75 > score인 75 false 이므로 넘어감
# 답은 가장 앞에 있는 75

# enumerate 활용

# 최대값, 최소값 구하기

# 대표값 문제 오류 수정, 반올림 round_half_up 하기

# 대표값 문제 오류 수정
# 우리가 아는 반올림은 round_half_up 방식 ( 5 이상 올림 )
# Python 의 round 는 round_half_even 방식을 택한다

# round_half_even 방식
a = 4.500
print(round(a)) # 4
# 정확히 하프 지점에 있으면 짝수(even)값으로 근사값 해줌
# ex) 4 와 5 의 하프지점에 있으므로 4로 간다

a = 4.5111 # 정확한 하프가 아니면 올라간다
print(round(a)) # 5

a = 5.5000
print(round(a)) # 6 (짝수로 간다)

a = 66.5
print(round(a)) # 66

# 소수 첫째자리에서 무조건 반올림 하는 법

a = 66.5
a = a + 0.5
# 66.5 여도 0.5 더하면 67, 66.6 이어도 0.5 더하면 67.1 로 자리올림 일어남
a=int(a) # 소수점 없어짐, 내림됨
print(a) # 처음의 a 에서 반올림한 67이 출력

# 66.4 이면 0.5 더해도 66.9 -> 66 이므로 올림안됨, 맞음

복습

- 목록 가져오기 Service 클래스에서 목록 추출 후 결과를 공유설정

- View 페이지에서 EL 로 출력하기 전에 공유설정되어야함

 

Service 클래스에서 DTO 객체가 공유되면

		request.setAttribute("board", board);

- View 페이지에서 ${공유되는 네임값.필드명} 으로 출력

		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name}"/>
		</td>

 

Spring 학습 전 앞으로 할 내용

Maven

- 라이브러리 관리자 중 하나

- Spring 프로젝트를 만들면 Maven 으로 라이브러리 관리하는게 기본

 

ORM 프레임워크 : MyBatis

- 기존의 DAO 클래스 안에 있는 내용을 xml 에 따로 뺀다, 간단해짐

- Spring 은 MyBatis 와 연동하는 경우가 많다


- 수정 폼, Controller 클래스에서 연결하는것도 다 구현했다

- 글 수정 Service 클래스, 수정하는 DAO 메소드만 구현하면 된다

 

 게시판 프로그램 : 글 수정 Service 클래스

- service 패키지 안에 글 수정 Service 클래스인 BoardModifyjava.java 를 작성하자

- BoardModify.java (수정 후 1)

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModify");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass = request.getParameter("board_pass");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));

		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getDetail(board_num);
		
		// 비번 비교
		if(old.getBoard_pass().equals(board_pass)) { // 비번 일치시
			int result = dao.update(board);		// update SQL문 실행
			if(result == 1) System.out.println("글수정 성공");
		} else {								// 비번 불일치
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다');");
			out.println("history.go(-1)");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page=" + page);
//		forward.setPath("/BoardDetailAction.do?board_num="+board_num+ "&page=" + page); // 상세정보로 갈 수도 있다
		return forward;
	}
}

1. 글 수정폼에서 입력된 정보들이 넘어오므로 한글값 인코딩을 한다

- 비번 불일치시 출력할 alert 창을 위해 out 객체를 생성하고 현재 문서 한글값 인코등을 한다

2.  글 수정 폼에서 hidden 으로 전달된 글 번호, 페이지 번호를 받아와서 저장한다

- 페이지번호, 글 번호는 목록 페이지 -> 상세 정보 -> 글 수정 폼 -> 글 수정 으로 넘어왔다

- 글 번호는 수정시에 필요하고, 페이지 번호는 수정 후 원래 페이지로 돌아갈 때 필요하다

3. DTO 객체 board 를 생성하고, 수정폼에서 넘어온 값들을 받아서 객체 board 에 저장

4. 사용자가 수정폼에 입력한 비번을 받고 DB에 저장된 비번과 비교해서 일치하는지 확인

- 사용자가 수정폼에 입력한 비번은 form 에서 넘어온 값이므로 request.getParamter("board_pass") 로 가져온다

- 상세정보를 가져오는 메소드 getDetail() 을 호출해서 DB 에 저장된 1개 글에 대한 정보를 가져와서 DTO 객체 old 에 저장

5. 비번 일치시 DAO 의 update() 메소드를 실행해서 실제 수정을 수행함

- 이후 DAO 에 갔다가 여기로 다시 돌아온다

6. 비번 불일치시 out 객체로 비밀번호가 틀렸다는 메세지를 뿌린 후 이전 페이지인 수정폼으로 돌아간다

- 비번 불일치시 포워딩 시키면 안되므로 포워딩 객체를 생성하기 전에 return null 로 함수 execute() 를 종료

7. 수정에 성공하면 목록 페이지로 가기 위해 "/BoardListAction.do" 로 요청하여 목록 가져오기를 한다

- 목록페이지 포워딩 시 원래 페이지로 가기 위해 페이지 번호를 get 방식 전달, 목록 페이지에서 그 페이지번호를 받아서 그 페이지에 해당하는 글들을 가져와서 출력해준다

- 상세페이지로 갈 수도 있다, 상세페이지로 갈때는 페이지번호 뿐 아니라 글번호도 가져가야한다

 

DAO 클래스 글 수정 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 update() 부분 코드만

	// 글 수정
	public int update(BoardBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_name=?,board_subject=?,";
			sql += "board_content=? where board_num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_subject());
			pstmt.setString(3, board.getBoard_content());
			pstmt.setInt(4, board.getBoard_num());
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}		
		}
		return result;
	}

- 수정 폼에서 수정을 위해 작성하는 값들은 이름, 제목, 내용 뿐이므로 이 값들만 update 해주면 된다

- DAO update()가 끝나고 Service 클래스로 돌아간다

 

- Service 클래스, DAO update() 작성 후 수정을 해보자

- index.jsp 실행해서 목록 페이지로 온 다음, 상세 페이지로 가고, 수정폼으로 가고, 수정해보기

- 수정이 잘 된다

 

문제점

- 수정 성공시 성공했다는 메세지가 뜨지 않음

- 수정 성공시 메세지는 alert() 으로 출력되지 않는다

 

문제 해결

			response.sendRedirect("./board/updateresult.jsp?page="+page);

 

- 포워딩할때 Controller 클래스를 거치는 것이 원칙이지만 여기선 바로 수정 성공 메세지를 출력하는 View 페이지로 간다

- 포워딩 시에 페이지번호를 가져가서, 그 페이지 내에서 <script></script> 내에서 출력을 하고 목록페이지로 돌아가야함

- 만약 메세지를 띄우고 싶다면 출력을 담당하는 다른 페이지로 포워딩해서 이 메세지를 띄워야한다

+ 이 작업을 아래의 forward 객체로 포워딩하면 안된다, 여기서 바로 Redirect 방식으로 포워딩해야함

- 아래의 forward 코드는 주석으로 막고 return null 을 해야한다

- Controller 클래스에 "/update.do" 요청을 처리하는 코드를 작성하자

 

- 다음은 수정 성공 후 메세지를 출력할 updateresult.jsp 파일 생성

- updateresult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<script>
	alert(${param.page});
	alert("글 수정 성공");
	location.href = "<%=request.getContextPath()%>/BoardListAction.do?page="+ ${param.page};
</script>

 

- 글 수정을 하고 메세지 출력 후 목록페이지로 잘 가는지 확인해야한다

- 원래 페이지인 1 페이지로 돌아온다, URL 을 보면 알 수 있음

+ 7 페이지의 글을 수정해도 다시 7 페이지로 돌아왔음을 확인 했다

+ 나중에는 이렇게 복잡하게 메세지를 출력하지 않는다


게시판 프로그램 : 글 삭제

- 먼저 상세 페이지 View 페이지인 qna_board_view.jsp 의 버튼 중 '삭제' 버튼에 링크를 걸어야한다

- qna_board_view.jsp 부분

<input type="button" value="삭제"
onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">

- 상세페이지에서 글번호 board_num 과 페이지번호 page 가 넘어온다

- 글 삭제 폼으로 가기 위해 "/BoardDeleteAction.do" 로 요청을 한다

 

 Controller 클래스에서 글 삭제폼으로 가기 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDeleteAction.do 로 요청이 올때의 경우를 처리

		// 삭제 폼
		} else if(command.equals("/BoardDeleteAction.do")) {
			try {
				forward = new ActionForward();
				forward.setRedirect(false); // dispatcher 방식으로 포워딩
				forward.setPath("./board/qna_board_delete.jsp");
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

- 삭제 폼은 DB와 연동할 필요가 없으므로 Service 클래스로 가지 않는다

- 요청을 Controller 페이지에서 받으면 바로 삭제폼 View 페이지 qna_board_delete.jsp 로 포워딩한다

- 여기서 forword 객체를 생성하고 포워딩 방식, 포워딩 페이지를 설정하면 아래 포워딩 코드에 의해 바로 View 로 포워딩 됨

- Dispatcher 방식으로 포워딩해야만 param 으로 값을 받을 수 있다

+ Dispatcher 방식으로 포워딩해야 호출하는 페이지의 request, response 객체를 공유하기때문이다

 

- board 폴더 안에 삭제폼 View 페이지인 qna_board_delete.jsp 생성 및 작성

- qna_board_delete.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 삭제</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<!-- 글번호, 페이지번호를 request 객체로 받기 -->
board_num1 : <%=request.getParameter("board_num") %> <br>
page1 : <%=request.getParameter("page") %> <br>
<!-- 글번호, 페이지번호를 param 내장객체로 받기 -->
board_num2 : ${param.board_num} <br>
page2 : ${param.page }

<form action="<%=request.getContextPath() %>/BoardDelete.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 삭제</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="삭제">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_modify.jsp 를 복붙 후 수정

- 상세페이지에서 넘겨준 글번호, 페이지번호 를 받아서 글 삭제 Service 클래스 BoardDelete.do 로 넘겨준다

- 삭제 시 "/BoardDelete.do" 로 요청

- 삭제 시 삭제 Service 클래스인 BoardDelete.java 로 글 번호, 원래 페이지 번호, 비밀번호가 넘어가야한다

+ 삭제할 글 번호를 알아야하기때문에, 원래 페이지로 돌아가야하므로, 비밀번호 일치 확인을 해야하므로

- 입력양식은 비번 입력 양식만 남긴다

 

수정폼 vs 삭제폼

- 수정 폼은 DB와 연동해서 Service 클래스에서 객체 board 를 공유했고 그걸 수정폼에 뿌렸었다

- 삭제 폼은 DB와 연동하지 않았으므로 param 객체로 글번호, 페이지번호를 가져와서 hidden 으로 글 수정 Service 클래스로 넘긴다

+ Dispatcher 방식으로 포워딩했으므로 상세페이지의 request, response 객체를 공유하므로 param 으로 그 글의 글 번호, 원래 페이지 번호를 받아옴

 

param 내장 객체 (qna_board_delete.jsp 부분)

<!-- 글번호, 페이지번호를 request 객체로 받기 -->
board_num1 : <%=request.getParameter("board_num") %> <br>
page1 : <%=request.getParameter("page") %> <br>
<!-- 글번호, 페이지번호를 param 내장객체로 받기 -->
board_num2 : ${param.board_num} <br>
page2 : ${param.page }

- param 은 getParameter() 와 같은 코드

- 코드가 간단해짐

 

- 글 삭제 Service 클래스인 BoardDelete.java 를 service 패키지 안에 생성하자

 

게시판 프로그램 : 글 삭제 Service 클래스

- service 패키지 안에 글 삭제 Service 클래스인 BoardDelete.java 를 생성 및 작성하자

- BoardDelete.java

package service;

import java.io.File;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDelete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardDelete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass = request.getParameter("board_pass");
		
		String path = request.getRealPath("boardupload");
		System.out.println(path);
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getDetail(board_num); // 상세정보 구하기
		System.out.println(old.getBoard_pass());
		System.out.println(board_pass);
		
		// 비번 비교
		if(old.getBoard_pass().equals(board_pass)) {	// 비번 일치시
			int result = dao.delete(board_num);	// delete SQL문 실행
			if(result == 1) System.out.println("삭제 성공");
			
			// 첨부파일이 있는 경우에 첨부파일 삭제
			if(old.getBoard_file() != null) {
				
				File file = new File(path);
				file.mkdir();
				
				// boardupload 디렉토리의 모든 첨부 구해오기
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();	// 첨부파일 삭제
					}
				}
			}
		} else {	// 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page=" + page);
		return forward;
	}

}

1. 삭제 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩 처리를 한다

- 비번이 일치하지 않을때 alert 창에 출력하기 위해 문서 한글 인코딩 처리도 하고 out 객체도 생성한다

2. 삭제 폼에서 hidden 으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장한다

- 목록 페이지 -> 상세페이지 -> 삭제폼 -> 삭제 Service 클래스 까지 글 번호와 페이지 번호가 넘어온 것이다

3. 삭제 폼에서 사용자가 입력한 비밀번호도 받아서 DAO 의 getDetail() 메소드로 상세정보를 구해온다

4. 비번 일치시 DAO 의 delete() 메소드를 호출하여 글을 삭제한다

- 매개변수로는 글 번호를 넘겨준다

5. 삭제한 글에 해당하는 첨부파일이 있다면 첨부파일도 지우도록 한다

* 첨부파일 지우기 설명은 아래에

6. 비번 불일치시 out 객체를 통해 alert 창에 메세지를 뿌리고 이전 페이지인 삭제 폼으로 돌아간다 

- 이후 return null 을 작성 아래쪽의 포워딩 객체를 생성하고 설정하는 곳으로 가지 않고 함수를 종료시킨다

7. 글 삭제 성공 후 forward 객체를 생성하고 설정, 포워딩 페이지는 목록을 가져오는 "/BoardListAction.do" 로 한다

- get 방식으로 페이지 번호를 넘겨줘서, 원래 페이지로 돌아갈 수 있게 한다

 

첨부파일 지우기 (BoardDelete.java 부분)

			// 첨부파일이 있는 경우에 첨부파일 삭제
			if(old.getBoard_file() != null) {
				
				File file = new File(path);
				file.mkdir();
				
				// boardupload 디렉토리의 모든 첨부 구해오기
				File[] f = file.listFiles();
				for(int i=0; i<=f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();	// 첨부파일 삭제
					}
				}
			}

- 첨부파일 지우기는 DAO 안에서 처리해도 되고, Service 클래스의 delete() 호출 아래에서 처리해도 된다

1) 첨부파일이 저장된 진짜 경로를 getRealPath() 로 구해서 path 변수에 저장한다

2) 상세정보를 구해온 객체 old.getBoard_file 을 통해 첨부파일이 있는지 없는지 판별가능, 있을때 삭제

3) File 객체 f를 생성할때 생성자의 매개변수에 생성될 디렉토리의 위치인 path 변수가 들어간다

4) file.listFiles() 로 boardupload 디렉토리의 모든 첨부파일을 구해온다

5. 반복문을 통해서 모든 파일을 돌면서, 파일명이 delete()로 삭제한 글의 DB 속에 저장된 파일명과 일치하면 파일 삭제

 

Controller 클래스에서 글 삭제 Service 클래스 BoardDelete.java로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDelete.do 로 요청이 올때의 경우를 처리

		// 글 삭제
		} else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 글 삭제 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 delete() 부분 코드만

	// 글 삭제
	public int delete(int board_num) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "delete from model2 where board_num = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			result = pstmt.executeUpdate(); // SQL문 실행
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}			
		}
		return result;
	}

 

- index.jsp 를 실행해서 목록페이지로 오고 10 페이지의 글 하나를 삭제해보자

- 삭제 후 다시 목록페이지로 왔다, 원래 페이지였던 10 페이지로 돌아왔다

C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\model2board\boardupload

- Desert21.jpg 첨부파일이 삭제되었는지 확인하기 위해 콘솔에 찍힌 path 를 탐색기에 검색

- 삭제 되었음을 확인 가능하다

JSP Model 2 끝


라이브러리 관리

- 지금까지는 Dynamic Web Project 를 만들고, 직접 라이브러리를 구해서 lib 폴더 안에 모든 라이브러리를 저장했다

- Spring 에서는 Maven 으로 라이브러리를 관리한다

- Maven 은 라이브러리 관리자 중 하나이다

 

라이브러리 관리자 maven

- spring project 에선 기본적으로 maven 으로 라이브러리를 관리함

- springboot project 에선 maven 또는 gradle 로 라이브러리를 관리함, 선택 가능

- 환경설정 파일 안에 라이브러리를 추가하는 식이다

 

라이브러리 관리자 gradle

- android project

- springboot projec

 

Maven 프로젝트 생성

 

Dynamic Web Project vs Maven Project

- 이때까지 Dynamic Web Project 로 프로젝트를 생성해 왔다

- 이젠 Maven Project 로 프로젝트를 생성하자

- Maven Project는 기본적으로 maven 으로 라이브러리를 관리하게끔 설정됨

- Maven Project는 Dynamic Web Project 와 폴더구조나 라이브러리 관리 방식이 크게 다르다

 

- Catalog 를 Internal 로 바꾼다

- 어떤 archetype 을 선택하냐에 따라 프로젝트 모양이 달라짐

- 일반적으로 웹 어플리케이션 프로젝트를 생성할때는 이 maven-archetype-webapp 를 선택

 

- Group id 값은 도메인명 역순을 주로 입력함. 현재는 com.myhome

- Artifact id 값은 프로젝트가 생성되면 프로젝트 명이 된다. 현재는 maventest

- Finish 누르면 maventest 프로젝트가 생성된다

- 기존의 Dynamic Web Project 와 구조가 다름

- pom.xml 파일이 Maven 의 환경설정 파일이다

+ 환경설정 파일은 xml 파일로 되어있는 경우가 많음

- 파일들에서 에러가 발생한다

 

maven project 에러발생

- maven 프로젝트 생성후 index.jsp 파일에 에러가 발생

- Libraries 에 Apache Tomcat Library 를 추가하면 에러가 사라진다.

- 여기 Apache Tomcat Library 가 없다

 

Apache Tomacat Library 추가하는 방법

- 그럼 빨간 에러가 사라진다

- index.jsp 를 실행해보자

 

Maven Project 구조

maintest/src/main 안의 3개 폴더

- 들어가는 내용들이 정해져 있다

 

1. java 폴더

- 확장자가 .java 인 모든 파일은 반드시 이 폴더 안에 저장

- dynamic web project 의 src 와 같은 역할

 

2. resources 폴더

- MaBatis 의 환경 설정 파일들이 주로 들어가있다

- 기존 DAO 클래스 안의 SQL문을 xml 파일로 따로 뺀다, 그 SQL문 파일들이 저장된다

 

3. webapp 폴더

- View 페이지가 저장되는 위치

- index 파일도 여기 안에 만들어야한다, webapp/WEB-INF 폴더 안에 index 파일저장

- 하지만 WEB-INF 폴더 안에 라이브러리는 더이상 없다, pom.xml 에서 라이브러리를 추가함

- dynamic web project 의 WebContent 와 같은 역할

 

pom.xml 파일

- maventest 프로젝트 하위

- Maven 의 환경설정 파일, 필요한 라이브러리를 여기에 추가한다

- 필요한 라이브러리를 pom.xml 추가시 원격 저장소에서 로컬 저장소로 다운받는다

 

- pom.xml 파일을 살펴보자

 

pom.xml 파일

- pom.xml (원하는 라이브러리 추가 전)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.myhome</groupId>
  <artifactId>maventest</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>maventest Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>maventest</finalName>
  </build>
</project>

- Maven 의 환경설정 파일

- 가장 바깥의 Root Element 가 project 이다, 전체가 <project></project> 로 덮여있다

- Maven Project 생성시 설정했던 내용들이 들어가있다, Group Id, ArtifactId 등

- Dependencies Element 안에 Dependency Element 들이 있다

- 이 Dependencies Element 안에 우리가 원하는 라이브러리를 추가해야함, 의존 라이브러리라고 부른다

- 여기선 junit 라이브러리 추가,  junit 은 테스트 용도로 제공되는 라이브러리

 

라이브러리 가져오기

- 먼저 Maven Repository 에서 의존 라이브러리를 검색한다

- cos 라이브러리를 찾아서 가져와보자

 

Maven Repository: Search/Browse/Explore (mvnrepository.com)

- 이 버전을 클릭

 

- 복사한 코드를 pom.xml 의 dependencies element <dependencies></dependencies> 안에 추가한다

- 추가 후 저장시 원격 저장소에서 로컬 저장소(내 컴퓨터) 로 다운로드를 시작한다

 

- pom.xml (원하는 라이브러리 추가 후, 미완성)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
	
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		
	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

- 이렇게 여기 dependency element 로 추가되는 라이브러리를 의존 라이브러리 라고 한다

+ Spring 에서는 maven 으로 라이브러리를 관리함

 

로컬 저장소 위치

- C:\Users\admin\.m2\repository

- 내 컴의 로컬 저장소(local repository) 위치이다.

- pom.xml 에 추가되어 원격 저장소에서 로컬 저장소로 다운받아진 라이브러리들이 여기 저장되어 있다.

 

- Windows - Preperences 로 들어와서 Maven ->  Use Settings 선택

- 내 컴의 로컬 저장소 위치이다

- maven 으로 라이브러리 관리 시 기본적으로 이렇게 로컬 저장소가 생성된다

- 특정 라이브러리를 다운하다가 문제가 생기면 로컬 저장소 안의 파일들을 다 지워도 된다, 새로 실행시 처음부터 다시 다운받음

- 만약 특정 라이브러리가 다운이 안된다면, 이미 그 라이브러리를 잘 다운 받은 컴퓨터의 레파지토리를 복사해서 내 컴에 덮어씌운다

 

- 저 주소를 복사해서 탐색기에 쳐보자

- 라이브러리들이 보인다

- 로컬 저장소안의 라이브러리를 지우고 싶으면 이클립스를 종료하고 지워야함

- 이클립스를 종료하고 모두 삭제하면 다시 이클립스를 실행할때 다시 다 다운받는다

 

Oracle JDBC 라이브러리 추가

pom.xml 에 Oracle Repository 추가

- Oracle은 공식 저장소에서 지원되는 라이브러리들이 다운이 안되는 경우가 많이 발생함

- 그래서 비공식적인 오라클 Repository 를 먼저 따로 추가해야함

- pom.xml 의 dependencies Element "위"에 오라클 Repository 를 가져오는 이 코드를 복사

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

- 그리고 오라클 JDBC 라이브러리를 dependencies Element 안에 추가

- 이 코드 잘 작동하지 않는다, 이미 다운받은 Repository 를 덮어쓰자

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

 

- pom.xml (오라클 Reopository 추가, 오라클 JDBC Library 추가)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>
		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

- 오라클 JDBC 라이브러리를 다운받을때 문제가 생긴다

 

MySQL JDBC 라이브러리 추가

 

- 지금 설치한 mysql 버전이 8 점대 임을 고려해서 8 점대 중 8.0.28 버전 다운

 

- dependencies element 안에 이 코드를 복붙

 

프레임워크 MyBatis 라이브러리 추가

- 지금은 Spring 을 안쓰므로 MyBatis 만 있으면 된다

- dependencies element 안에 이 코드를 복붙

 

JSTL 라이브러리 추가

- 하나만으로 처리가 안되는 경우가 있으므로 두가지 라이브러리를 가져옴

- 나중엔 JSTL 연결 라이브러리도 필요

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
</dependency>

- 이걸 pom.xml 의 dependencies element 안으로 가져옴

 

문제점

- 오라클 JDBC 라이브러리를 가져올때만 문제가 생겼다

 

문제 해결 방법

- 이미 다운을 받은 레파지토리를 복사해서 내 레파지토리로 붙여넣는다

- 이미 오라클 JDBC 라이브러리를 다운받은 레파지토리를 클라우드에서 다운받아서 압축 해제하고 그 안의 내용을 모두 내 로컬 레파지토리로 복붙하자

- 이클립스를 종료하고 내 로컬 레파지토리의 내용은 모두 삭제 하고, 이 repository 압축 풀은 모든 내용을 복붙하기

- 다 삭제하고 복붙 완료한 내 레파지토리

- 다시 이클립스를 켠다

 

- 복붙한 라이브러리 버전과 다른 경우는 다시 pom.xml 에 쓰인 버전으로 다운받는다

- 그럼 오라클 JDBC 라이브러리 코드를 작성해도 에러가 나타나지 않음

- 빨간 에러가 사라졌다

- pom.xml 에 작성된 라이브러리들만 여기에 나타난다

 

- pom.xml (최종)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>
		<!-- 오라클 JDBC Library -->
 		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.28</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.7</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jstl-impl</artifactId>
			<version>1.2</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

 

maven 프로젝트 vs spring 프로젝트

- 현재는 maven 프로젝트를 생성했을때 pom.xml 에 의존 라이브러리는 junit 만 있었다

- Spring 프로젝트 만들면 기본적인 라이브러리가 다 들어가있다

+ Spring 프로젝트도 라이브러리 관리는 maven 라이브러리 관리자를 사용

 

Spring 프로젝트 만들기

- Spring 프로젝트는 이클립스 메뉴 없어서 지금은 만들 수 없다

1. 플로그인에 추가

2. STS 란 프로그램 사용, SpringBoot 만들 수 있는 메뉴도 추가되어있다

 

maven 프로젝트 import, export

- maven 프로젝트를 import / export 할때 war 파일로 import / export 하면 안된다

- spring 프로젝트도 import / export 할때 war 파일로 import / export 하면 안된다

- maven 프로젝트를 war 파일로 import / export 하면 Maven Project 가 Dynamic Web Project 로 풀려버림

 

- war 파일로 export -> import 했을때 프로젝트는 Dynamic Web Project 가 된다

 

maven 프로젝트 export 하는 법

- maven 프로젝트를 export 할때는 프로젝트를 통째로 Ctrl + C 로 복사해서 다른 곳에(바탕화면) 붙여넣기 한다

- 이렇게 폴더로 나타난다

 

maven 프로젝트 import 하는 법

- 현재는 이미 이클립스에 maventest 라는 프로젝트가 있으므로 Finish 가 활성화되어있지 않음

- 이클립스의 maventest 프로젝트를 삭제 후 다시 import 하자

- maventest 프로젝트가 maven project 그대로 유지되면서 import 되었다

Controller (Servlet) 클래스

- WebServlet 클래스 등은 Apache Tomcat 의 servlet-api.jar 파일에 있다

 

흐름 정리

- 클라이언트가 요청하면 무조건 Controller 클래스로 가고, 적절한 Service 클래스로 넘겨준다 거기서 DAO 의 메소드를 호출하여 DB와 연동

- DAO 에서 검색한 결과를 Service 클래스에서 공유설정하고 Controller 클래스에서 포워딩해서 View 에서 공유된 값을 가져와서 출력


문제점 1

- 등록 버튼을 눌러서 글을 등록한 뒤 목록페이지에서 F5(새로고침) 를 누르면 똑같은 데이터가 추가된다

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 새로고침을 누를때마다 하나씩 추가됨

 

문제점 1 해결

- BoardAddAction.java (원문 글 작성 Service 클래스) 에서 setRedirect(false) 를 true 로 설정해서 Redirect 방식으로 포워딩하면 이 문제가 해결된다

 

문제점 2

- 글 작성 성공 후 글 목록 가져오기를 "/BoardListAction.do" 로 요청할때 get이 나와야하는데 post 방식이 나온다

- 포워딩을 하면 get 방식으로 전달되지만, 콘솔창에는 post 방식으로 뜬다

+ 폼으로 /BoardListAction.do 로 오는게 아니므로 post 가 아닌 get 방식이다

 

문제점 2 이유

- 위는 글 작성 요청 아래는 글 작성이 끝난 후 글 목록을 가져오는 요청

- dispatcher 방식으로 포워딩되면 URL 주소가 바뀌지 않으므로 이전에 글 작성할때 사용했던 post 방식으로 나옴

 

문제점 1 & 문제점 2 해결 방법

- setRedirect 를 true 로 바꿈

- setPath 에서 혹시 경로를 못찾아가면 . 를 앞에 붙여줌

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 새로고침해도 같은 데이터가 들어가지 않는다

 

문제점 정리

- Get 방식처럼 단순히 조회하는 요청은 Dispatcher 방식을 사용

- Post, Put 방식처럼 무언가를 변화시키는 요청에는 Redirect 방식을 사용

- Dispatcher 방식이 Redirect 방식보다 성능상 좋다. 

- Post, Put 같은 무언가를 변화시키는 요청에는 Redirect 방식을 사용

- 클라이언트가 Post 요청으로 무언가를 변화시킨 후 새로고침을 눌러버리면 웹 브라우저에서 새로고침은 마지막에 서버에 전송한 데이터를 다시 전송하기 때문에 다시 URL에 접속되면서 Post 요청이 한번 더 전송되기 때문

ex) 상품 등록 요청을 하였는데 재요청을 보내버리면 2개의 상품이 중복 등록가 돼버리는 상황이 발생하게 된다.

 

다른 페이지로 이동

- 아직 페이지를 선택할 수 있는 부분을 만들지 않았으므로 URL 에 입력해서 찾아가보자

- 이 페이지에 맞는 10 개의 글(데이터) 을 뿌려줌

 

파생변수 number

	<c:set var="num" value="${listcount - (page - 1) * 10}"/>

- 각 페이지에 출력될 시작 번호를 저장하고 있다

- 마지막의 10 은 한 페이지에 출력할 데이터 개수 limit 를 의미한다

- 그 번호에서 반복문으로 1씩 감소하여서 출력

 

더미 데이터 만들기

- 페이지 처리 구현시 보기 위해 더미 데이터 여러개를 만든다

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료


게시판 프로그램 : 목록 가져오기 (이어서)

- 목록가져오는 것은 구현했고, 공유된 리스트, 기본변수, 파생변수들을 View 에서 가져와서 출력하는 과정에 있다

- 목록은 뿌렸고 페이지 처리, 페이지 선택할 수 있는 것 만들고 있다

- qna_board_list.jsp (최종)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %> 개 <br>

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<td>번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>날짜</td>
		<td>조회수</td>
	</tr>
	
	<c:set var="num" value="${listcount - (page - 1) * 10}"/>
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>
			<!-- 댓글 제목 앞에 여백 처리 -->
			<c:if test="${b.board_re_lev > 0}">
				<c:forEach var="i" begin="0" end="${b.board_re_lev}">
					&nbsp;
				</c:forEach>
			</c:if>
			<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_subject}</a>
		</td>
		<td>
			${b.board_name}
		</td>
		<td>
			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEEE"/>
		</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>
</table> <br><br>

<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">

<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>

<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
	<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>

<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
	<c:if test="${i == page}"> <!-- 현재 페이지 -->
		[${i}]
	</c:if>
	<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
		<a href="./BoardListAction.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>

<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>

<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>

</c:if>
</center>

 

페이지 처리 / 페이지 메뉴 바 설계

각 블럭에 10개의 페이지 출력 (qna_board_list.jsp 부분)

<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
	<c:if test="${i == page}"> <!-- 현재 페이지 -->
		[${i}]
	</c:if>
	<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
		<a href="./BoardListAction.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>

 

- forEach 문으로 startPage 와 endPage 까지 반복문을 돌려서 1 2 3 ... 10 로 페이지 선택하는 걸 만듬

- 현재 페이지가 아닌 경우 클릭한 페이지 번호를 넘기면서 "/BoardListAction.do" 로 요청한다

- 이러면 Controller 클래스를 다시 찾아가서 BoardListAction.java Service 클래스로 가서 다시 목록을 가져온다

- 이때 BoardListAction.java 의 아래 코드에서

		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}

- 사용자가 선택한 페이지가 page 변수에 저장되고 그걸 이용해서 데이터를 가져옴

- 그리고 page 변수로부터 유도된 변수 및 리스트를 BoardListAction.java 아래에서 공유설정함

		// 공유 설정
		request.setAttribute("page", page);

- 그걸 다시 View qna_board_list.jsp 에서 받는다

 

첫 페이지로 이동, 마지막 페이지로 이동 (qna_board_list.jsp 부분)

 

<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>

- 1 page 로 이동

<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>

- 총 페이지 수 == 마지막 페이지

- pageCount 는 총 페이지 수이고, 공유되어 받아온 값이다

- >> 클릭시 마지막 페이지로 이동

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

 

이전 블럭 (qna_board_list.jsp)

<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
	<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>

- 블럭단위로 이동

- startPage 는 본인이 속한 블럭의 첫 페이지 의미

- startPage > 10 은 '1 페이지가 아니면' 이라는 의미, ${startPage - 10} 은 현재 블럭의 첫 페이지에서 -10 을 하면 이전 블럭의 첫 페이지로 이동

ex) 13 페이지에 있다가 [이전] 클릭시 현재 블럭 startPage 인 11 에서 -10 을 하여 1 페이지로 이동, 즉 1 블럭의 startPage 로 이동

 

다음 블럭 이동 (qna_board_list.jsp)

<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>

- endPage 는 본인이 속한 블럭의 끝 페이지를 의미

- endPage < pageCount 은 '마지막 페이지가 아니면' 이라는 의미, ${startPage + 10} 은 현재 블럭의 첫 페이지에서 +10 을 하면 다음 블럭의 첫 페이지로 이동

 

 

블럭과 파생변수 startPage, endPage

- 두번째 블럭의 startPage 는 11, endPage 는 20

 

- 페이지를 클릭할때마다

- 페이지 번호를 누를때마다 계속 DB와 연동해서 목록을 가져오는 작업, request 로 공유하는 작업, View 에서 받아서 뿌리는 작업이 수행된다

 


+ 같은 페이지 처리를 JSTL 쓰지 않고 했을때

- Model 1 게시판 페이지 메뉴바 만드는 코드이다 : https://laker99.tistory.com/117

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	// pageCount : 총 페이지 수
	int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
	// endPage : 각 블럭의 끝 페이지 번호 :    10, 20, 30 ...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에 endPage값을 pageCount로 수정
	if(endPage > pageCount) {	//
		endPage = pageCount;
	}
%>	
	<!-- 1page로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
	// 이전 블럭으로 이동
	if(startPage > 10){	%>
		<a href="list.jsp?page=<%=startPage-10 %>">[이전]</a>
<%	}

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) { %>
			[<%=i %>]
<%		} else {%>
			<a href="list.jsp?page=<%=i %>">[<%=i %>]</a>
<%		}
	}// for end
	
	// 다음 블럭으로 이동
	if(endPage < pageCount) {
%>	
		<a href="list.jsp?page=<%=startPage+10 %>">[다음]</a>	
<%	} %>
	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" style="text-decoration:none"> >> </a>	
<%}%>
</center>


게시판 프로그램 : 상세 페이지

- 제목 클릭시 상세 페이지로 넘어간다

- 글번호 num 과 페이지 번호를 전달해야한다 ( 총 3번 전달 중 첫번째)

- 목록페이지 -> 상세페이지 -> 수정/삭제 폼 -> 수정/삭제

- 처음 2 번은 get 방식으로 전달, 마지막은 post 로 전달

- qna_board_list.jsp 부분

<td>
	<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>

- 상세 페이지로 가기 위해 /BoardDetailAction.do 로 요청한다

 

 게시판 프로그램 : 상세 페이지 Service 클래스

- service 패키지 안에 상세 페이지 Service 클래스인 BoardDetailAction.java 를 생성 및 작성하자

- BoardDetailAction.java (수정 전)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDetailAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardDetailAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		dao.readcountUpdate(board_num);	// 조회수 1 증가
		BoardBean board = dao.getDetail(board_num);	// 상세정보 구하기
		
		// 글내용 줄바꿈 기능
		String content = board.getBoard_content().replace("\n", "<br>");
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("content", content);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_view.jsp");
		
		return forward;
	}

}

- 목록 페이지에서 제목을 클릭해서 넘어왔고, 글번호와 페이지번호가 넘어왔다, 그 값을 변수 board_num 과 page 로 받음

- 글번호는 int 형으로 형변환한다

<조회수 1 증가>

- 조회수 값을 1 증가시키는 update SQL 을 수행하는 메소드 readcountUpdate() 가 필요하다

- 그 글의 조회수 컬럼을 1 증가시켜야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 따로 리턴을 받을 필요는 없다, 아무것도 리턴 받지 않고 있음

* DAO 클래스 readcountUpdate() 메소드 작성은 아래에

<상세 정보 구하기>

- 상세 정보를 구해오는 메소드 select SQL 을 수행하는 메소드 getDetail() 이 필요하다

- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO

* DAO 클래스 getDetail() 메소드 작성은 아래에

<DAO -> Service 로 돌아왔을때> (BoardDetailAction.java 부분)

		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("content", content);
		request.setAttribute("page", page);

- View 페이지로 돌아간다, 돌아갈때 2가지 변수 글번호, 페이지번호 뿐 아니라 검색된 결과를 돌려받은 객체 board 도 가져가야한다

- 이때, 객체 board, 줄바꿈 처리를 한 content , 페이지번호 page 3개를 공유설정한다

- 글번호 num 은 board 객체 안의 컬럼 board_num 에 있음

- content 는 board 에서 가져와서 줄바꿈 처리를 한 것이다

- 상세페이지 출력 View 페이지인 qna_board_view.jsp 파일로 포워딩한다.

- 이 Service 클래스를 호출했던 Controller 클래스로 돌아감


Model 1 vs Model 2

- Model 1 에서는 두 기능을 하나의 메소드에 처리했다, Model 2 에서는 따로 메소드를 만듬

+ MyBatis 로 바꿔서 처리하면 1개의 메소드 내에서 2개의 sql 문 사용하기 힘들어지므로 지금부터 다른 메소드로 처리

 

Controller 클래스에서 상세 페이지 Service 클래스 BoardDetailAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDetailAction.do 로 요청이 올때의 경우를 처리

		// 상세 페이지
		} else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

- 연결 이후 Service 클래스까지 잘 넘어간다

 

DAO 클래스 조회수 1 증가 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 readcountUpdate() 부분 코드만

	// 조회수 1 증가
	public void readcountUpdate(int board_num) {
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_readcount = board_readcount + 1 ";
			sql += "where board_num = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}		
		}
	}

- 데이터를 돌려줄 필요없으므로 리턴 자료형 void

- 그 글의 조회수 컬럼을 1 증가시켜야하므로 그 글을 특정할 수 있는 글번호 board_num 이 전달되고, 그걸로 SQL문 where 절 작성

 

DAO 클래스 상세정보 구하기 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getDetail() 부분 코드만

	// 상세 페이지
	public BoardBean getDetail(int board_num) {
		BoardBean board = new BoardBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from model2 where board_num = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setBoard_num(rs.getInt("board_num"));
				board.setBoard_name(rs.getString("board_name"));
				board.setBoard_pass(rs.getString("board_pass"));
				board.setBoard_subject(rs.getString("board_subject"));
				board.setBoard_content(rs.getString("board_content"));
				board.setBoard_file(rs.getString("board_file"));
				board.setBoard_re_ref(rs.getInt("board_re_ref"));
				board.setBoard_re_lev(rs.getInt("board_re_lev"));
				board.setBoard_re_seq(rs.getInt("board_re_seq"));
				board.setBoard_readcount(rs.getInt("board_readcount"));
				board.setBoard_date(rs.getTimestamp("board_date"));
			}
		} catch (Exception e) {
			e.printStackTrace();			
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return board;
	}

- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO

- select 문을 작성하므로 ResultSet rs 객체를 생성하고 그 객체로 검색된 데이터들을 받는다
- DTO 객체 board 를 생성하고, 그 board 에 검색된 데이터를 setter 메소드로 세팅한 후 리턴한다

 

- 다음은 board 폴더 하위에 상세 페이지를 출력하는 View 페이지인 qna_board_view.jsp 파일을 생성

- qna_board_view.jsp (수정 전, 버튼 구현이 완성 안됨, 수정/삭제/댓글 버튼 링크 안걸었다)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<table border=1 width=400 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>제목</td>
		<td>${board.board_subject}</td>
	</tr>
	<tr>
		<td>내용</td>
		<td>
			<pre>${board.board_content}</pre>
			${content}
		</td>
	</tr>
	<tr>
		<td>첨부파일</td>
		<td>
			<!-- 첨부파일이 있을때 첨부파일 출력 -->
			<c:if test="${board.board_file != null}">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">
					${board.board_file}
				</a>
			</c:if>
		</td>
	</tr>
	<tr>
		<td colspan=2 align=center>
			<input type="button" value="댓글">
			<input type="button" value="수정">
			<input type="button" value="삭제">
			<input type="button" value="목록"
			onClick="location.href='./BoardListAction.do?page=${page}'">
		</td>
	</tr>
</table>
</body>
</html>

<EL>

- Service 클래스인 BoardDetailAction.java 에서 board 객체를 공유설정했으므로 ${board.board_subject} 처럼 네임값.필드명 으로 간단히 출력 가능

<내용 출력>

- 내용 content 을 출력하는 방법 2가지를 모두 썻다

1. 공유된 content 를 바로 EL 로 출력

2. 공유된 board 객체에서 board.board_content 로 내용을 가져와서 <pre></pre> 로 감싸기

<첨부파일> (qna_board_view.jsp 부분)

			<!-- 첨부파일이 있을때 첨부파일 출력 -->
			<c:if test="${board.board_file != null}">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">
					${board.board_file}
				</a>
			</c:if>

- if 태그로 첨부파일이 null 이 아니면, 즉 첨부파일이 있으면 첨부파일명을 출력

- 첨부파일명에 링크를 걸어 클릭시 첨부파일을 다운받도록 한다 첨부파일명을 클릭시 file_down.jsp 로 이동

- file_down.jsp 로 이동하며 다운로드 받을 파일명 ${board.board_file} 을 file_name 변수에 담아 전달한다

<목록 버튼>

- '목록' 버튼 클릭시 "/BoardListAction.do" 요청을 하면서 현제 페이지 번호를 목록 구하는 곳으로 전달한다

- page 변수에 값이 저장되었으므로 원래 페이지로 돌아갈 수 있다

<댓글 버튼>

- '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 하면서 글번호 board_num 과 원래 페이지번호 page 를 전달

- 특정 글 아래에 댓글을 달게되므로 글번호 board_num 이 필요하다

- 댓글 작성 완료 후 원래의 페이지로 돌아가기 위해서 page 번호가 필요하다

- 나머지 버튼들은 나중에 링크 연결하기

 

Q. 왜 파일 다운하기 위해 file_down.jsp 로 갈떄는 원문 글 작성폼으로 갈때와 달리 Controller 안거치고 jsp 로 가는지 모르겠다

A. 원칙은 Controller 클래스 거쳐서 가야하지만 이 file_down.jsp 파일은 예외적으로 바로 JSP 파일로 가기


게시판 프로그램 : 댓글 작성

- 상세페이지에서 '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 get 방식으로 한다

- Controller 클래스에서 이 요청을 처리하고 댓글 작성폼 Service 클래스인 BoardReplyAction.java 로 가야함

 

게시판 프로그램 : 댓글 작성폼 Service 클래스

- 댓글 작성폼으로 가기 전, 부모글의 정보가 필요하고, 부모글의 정보를 가져올떄 DB 연동을 해야하므로 필요한 Service 클래스이다

- 댓글 작성폼으로 이동 전 필요하므로 이름을 "댓글 작성폼 Service 클래스" 로 한다

- service 패키지 안에 BoardReplyAction.java 클래스 생성 및 작성

- BoardReplyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReplyAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardReplyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		
		// 부모글의 상세 정보 구하기
		BoardBean board = dao.getDetail(board_num);
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_reply.jsp");
		return forward;
	}

}

- 상세 페이지 View 인 qna_board_view.jsp 에서 넘어온 글번호 board_name 와 페이지번호를 받는다

- 댓글 처리 관련 변수 3개를 처리하기 위해서는 부모글의 상세 정보 (변수 3개)를 먼저 구해야하므로 getDetail() 사용

- getDetail() 의 매개변수로 부모 글의 번호가 될 board_name 를 전달한다

ex) 부모의 board_re_lev 에서 + 1 을 하는 등 부모의 정보 필요

- DB에서 받아온 부모 글의 정보를 저장한 객체 board 와 페이지번호 page 를 공유설정한다.

- 댓글 작성폼 View 페이지 qna_board_reply.jsp 로 포워딩한다, request 로 공유설정했으므로 dispatcher 방식으로 포워딩

 

Controller 클래스에서 댓글 작성폼 Service 클래스 BoardReplyAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardReplyAction.do 로 요청이 올때의 경우를 처리

		// 댓글 폼
		} else if(command.equals("/BoardReplyAction.do")) {
			try {
				action = new BoardReplyAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

- 댓글 작성폼 qna_board_reply.jsp 를 생성 및 작성하자

- qna_board_reply.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>댓글 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">
<input type="hidden" name="board_re_ref" value="${board.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${board.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${board.board_re_seq}">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">댓글 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="re."/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="댓글">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_write.jsp 의 내용 복붙 후 수정

- 첨부파일 부분을 삭제, enctype 도 삭제하고, action 값을 "/BoardReply.do" 로 수정

- 부모글의 댓글 관련 컬럼 3개와, 글번호, 페이지번호 총 5개를 hidden 으로 값을 전달해야한다

- 그리고 댓글 작성 폼에 입력된 값들도 전달

- 글번호는 부모글의 글 번호를 의미, 그래서 공유된 부모글 정보 객체인 board 에서 부모글의 글번호를 가져옴

 

게시판 프로그램 : 댓글 작성 Service 클래스

- 실제로 댓글을 작성하는 역할을 한다

- service 패키지 안에 BoardReply.java 클래스 생성 및 작성

- BoardReply.java 

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReply implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardReply");
		
		request.setCharacterEncoding("utf-8");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
		int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
		int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
		String page = request.getParameter("page");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_re_ref(board_re_ref);
		board.setBoard_re_lev(board_re_lev);
		board.setBoard_re_seq(board_re_seq);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_pass(request.getParameter("board_pass"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.boardReply(board); // 댓글 작성
		if(result == 1) System.out.println("댓글 작성 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(true);
		forward.setPath("./BoardListAction.do?page="+page);
		return forward;
	}

}

- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩

- hidden 으로 넘어온 5개 값과 댓글 작성 입력 양식에서 넘어온 값들도 request.getParameter() 로 받는다

- 생성한 DTO 객체 board에 받아온 값들( 부모 글 정보 + 댓글 작성 입력양식에 사용자가 작성한 값들) 을 setter 메소드로 세팅한다

- 부모 글에 관한 정보를 저장한 변수들은 SQL문 안에서 사용될것이므로 int 형으로 변환한다

- 댓글 작성 메소드인 boardReply() 메소드를 호출하면서 부모 글 정보와 입력한 데이터가 담긴 board 객체를 매개변수로 전달해줌

- 원래 페이지로 돌아가기 위해 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩 하면서 get 방식으로 현재 페이지 번호를 넘겨준다, 이로 인해 원래 페이지로 돌아갈 수 있음

* DAO 클래스의 boardReply() 작성은 아래에

 

Controller 클래스에서 댓글 작성 Service 클래스 BoardReply로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardReply.do 로 요청이 올때의 경우를 처리

		// 댓글 작성
		} else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 댓글 작성 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 boardReply() 부분 코드만

	// 댓글 작성
	public int boardReply(BoardBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		// 부모글에 대한 정보
		int re_ref = board.getBoard_re_ref(); // 글 그룹 번호
		int re_lev = board.getBoard_re_lev(); // 댓글의 깊이
		int re_seq = board.getBoard_re_seq(); // 댓글의 출력순서
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_re_seq = board_re_seq + 1";
			sql += " where board_re_ref = ? and board_re_seq > ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, re_ref);
			pstmt.setInt(2, re_seq);
			pstmt.executeUpdate(); // SQL문 실행
			
			sql = "insert into model2 values(model2_seq.nextval,";
			sql += "?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_pass());
			pstmt.setString(3, board.getBoard_subject());
			pstmt.setString(4, board.getBoard_content());
			pstmt.setString(5, ""); // board_file
			pstmt.setInt(6, re_ref); // board_re_ref
			pstmt.setInt(7, re_lev+1); // board_re_lev
			pstmt.setInt(8, re_seq+1); // board_re_seq
			pstmt.setInt(9, 0); // board_readcount
			result = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();		
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}			
		}
		return result;
	}

- 매개변수로는 부모글의 정보와 댓글 작성 폼에 적힌 데이터가 저장된 DTO 객체 board 가 넘어온다

- 부모글에 대한 3가지 정보를 변수에 저장

- 2가지 SQL 문을 사용해야 한다

<update 문>

- 댓글의 board_re_seq 는 자신의 부모글 보다 seq 값이 큰 글들만 seq 값을 1 씩 증가시킴

1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가

2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가

<insert 문>

- 댓글 번호는 마찬가지로 sequence 로 넣는다

- 댓글의 board_re_ref 는 부모글의 board_re_ref 와 일치해야함

- 댓글의 board_re_lev 는 부모글의 board_re_lev 보다 1 커야함

- board_re_lev 와 board_re_seq 는 부모 글의 board_re_lev 와 board_re_seq 에서 1 증가한 값을 넣음

 

- 이제 상세 페이지에서 '댓글' 을 클릭해서 댓글 폼으로 간 후 댓글을 작성하자

 

문제점

- 안나온다

- 또 get 으로 전송하는데도 post 가 나옴

 

문제 해결

- BoardReply.java 의 setRedirect() 를 true 로 바꾸기

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 댓글 작성 후 목록페이지가 잘 나타난다

 

- 이제 댓글과 대댓글을 더 많이 써보자


게시판 프로그램 : 글 수정

- 수정 폼으로 넘어가기 전 DAO 에서 글의 상세정보를 불러와야한다

- 그러므로 "/BoardModifyAction.do" 로 요청함

 

- 상세 페이지 하단에서 '수정' 눌렀을때 수정폼 Service 클래스로 넘어가기 위해 "/BoardModifyAction" 로 요청

- qna_board_view.jsp 부분

<input type="button" value="수정"
onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">

 

- 상세페이지에서 글번호 board_num 과 페이지번호 page 가 넘어온다

 

 게시판 프로그램 : 글 수정폼 Service 클래스

- service 패키지 안에 글 수정폼 Service 클래스인 BoardModifyAction.java 를 생성 및 작성하자

- BoardModifyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModifyAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModifyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		
		// 상세 정보 구하기
		BoardBean board = dao.getDetail(board_num);
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_modify.jsp");
		return forward;
	}

}

- 수정폼으로 가기 전에 상세정보를 먼저 구해야 한다, 그 후에 수정폼으로 포워딩해야함

- 가장 먼저 상세페이지에서 get 방식으로 넘어온 글번호 board_num 과 페이지번호 page 를 가져온다

- DAO 의 getDetail() 메소드로 board_num 을 사용해서 글의 상세정보를 가져온다

- 가져온 상세정보를 받은 객체 board 를 공유설정하고, 페이지 번호 page 도 공유설정한다

+ 글 번호는 객체 board 안에도 있으므로 따로 공유설정 하지 않음

 

Controller 클래스에서 글 수정폼 Service 클래스 BoardModifyAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardModifyAction.do 로 요청이 올때의 경우를 처리

		// 수정 폼
		} else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

 

- 다음은 글 수정폼 View 페이지인 qna_board_modify.jsp 파일을 생성하자

- qna_board_modify.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 수정</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 수정</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="${board.board_subject}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="수정">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_reply.jsp 파일의 내용을 복붙 후 수정

- 수정폼에 수정할 정보를 입력하고 '수정' 버튼 클릭시 글 수정 요청인 "/BoardModify.do" 로 요청이 간다 

- hidden 으로는 글 번호인 board_num 과 페이지번호 page 가 넘어간다

- 글 수정 요청인 "/BoardModify.do" 로 가면서 hidden 으로 2개, 입력양식으로 4개의 값들, 총 6개의 값이 글 수정 Service 클래스 BoardModify.java 로 전달됨

- Service 클래스 BoardModifyAction.java 에서 공유했던 board 객체에서 이름을 "${board.board_name}" 로 가져와서 입력양식에 value 속성으로 넣기

- 이름 뿐 아니라 제목, 내용도 마찬가지로 처리한다

 

- 구현 후 상세페이지에서 '수정' 클릭시 나타나는 수정폼에 정보들이 나타남

 

- 비밀번호를 입력하고 '수정' 버튼 클릭시 /BoardModify.do 로 요청함

 

- 실제 수정을 처리해주는 Service 클래스인 BoardModifyjava 클래스를 생성하자

 

 게시판 프로그램 : 글 수정 Service 클래스

- service 패키지 안에 글 수정 Service 클래스인 BoardModifyjava.java 를 생성 및 작성하자

- BoardModify.java (수정 전 1)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModify");
		
		ActionForward forward = new ActionForward();
		return forward;
	}
}

- 아직 작성 안했고 여기까지 도착했는지 출력만 하고 있다, 내일 작성

 

Controller 클래스에서 글 수정 Service 클래스 BoardModify로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardModify.do 로 요청이 올때의 경우를 처리

		// 글 수정
		} else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}	
		}

JSP Model 2 게시판 프로그램 만들기

게시판 프로그램

- 댓글게시판 + 자료실 게시판

 

게시판 프로그램 : 주요 기능 소개

1. Connection Pool

2. 댓글 기능

3. 자료실 – 첨부파일 업로드, 다운로드 : cos 라이브러리 사용

- Model 1, Model 2 에선 주로 cos 라이브러리 사용, Spring 에서는 다른 라이브러리 사용

4. request 객체 공유 설정

- 게시판에서는 request 객체로 공유하는 경우가 많다

5. Controller 클래스 : Java Servlet

6. Model = Service + DAO Service, DTO, DAO 클래스

7. View (화면 인터페이스) : EL , JSTL 사용

 

게시판 프로그램 : 프로그램 구조 설명, 구조도

- Model 2 에서는 DAO, DTO 가 다른 패키지에 있다

- 같은 기능을 하는 클래스들끼리 묶어서 패키지 안에 저장

- controller 패키지 안에 Controller 클래스, dao 패키지 안에 DAO 클래스, model 패키지 안에 DTO 클래스

- service 패키지 안에 Service 클래스들, board 안에 View 페이지들

 

게시판 프로그램 : 프로젝트 생성

- 위의 구조도처럼 만들기 위해 model2board 라는 프로젝트부터 생성

- 이후 update 프로젝트의 WebContent 폴더 하위에 초기에 보여줄 파일인 index.jsp 를 생성

 

게시판 프로그램 : 기초 파일 가져오기

- 클라우드에서 기본 작성 양식, 환경설정 코드가 있는 board 폴더를 이클립스 WebContent 폴더로 가져오기

 

게시판 프로그램 : 몇가지 환경 구축

1. WebContent/WEB-INF/lib 폴더 하위에 라이브러리 3개 넣기

1) cos 라이브러리를 사용하기 위해 cos.jar 파일을 WEB-INF/lib 폴더 안으로 가져오기

2) JSTL 을 사용하기 위한 라이브러리 jstl-1.2.jar 를 WEB-INF/lib 폴더 안으로 가져오기

3) 오라클용 JDBC Driver 인 ojdbc.jar 를 WEB-INF/lib 폴더 안에 가져오기, 다른 프로젝트에서 가져오면 된다

2. board 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

- 커넥션 풀의 환경설정 파일이고, 기존에 작성했던 내용

 

게시판 프로그램 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

- context.xml

<Context> 
  <Resource name="jdbc/orcl" 
   auth="Container"
   type="javax.sql.DataSource" 
   username="totoro" 
   password="totoro123"
   driverClassName="oracle.jdbc.driver.OracleDriver"
   factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"
   url="jdbc:oracle:thin:@localhost:1521:xe"
   maxActive="500"  
   maxIdle="100"/>  
</Context>

<!--  p423 참조
 maxActive="500"  컨넥션풀이 관리하는 컨넥션의 최대 갯수
		          (기본값 : 8)
 maxIdle="100" 	  컨넥션풀이 관리하는 최대 유휴 갯수(기본값 : 8)          
-->

- 테이블을 아직 생성하지 않았지만 계정이 설정되어있으므로 Connection Pool 테스트가 가능하다

 

- dbcpAPITest.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*" %>
<%@ page import="javax.naming.*" %>
<%
 	Connection conn = null; 
	
	try {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();
  		
  		out.println("<h3>연결되었습니다.</h3>");
	}catch(Exception e){
		out.println("<h3>연결에 실패하였습니다.</h3>");
		e.printStackTrace();
 	}
%>

- 서버를 한번 중지시켰다가 dbcpAPITest.jsp 실행시

- 정상적으로 Connection Pool 에서 Connection을 구해온다

- 커넥션 풀에서 커넥션 가져오기 테스트 성공

+ 추후 DAO 클래스 만들때 커넥션풀에서 커넥션 구하기 3줄 코드를 DAO 클래스에 넣을 것

  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();

 

 

게시판 프로그램 : 첨부파일을 저장할 폴더 생성

- 업로드된 첨부파일을 저장할 폴더인 boardupload를 WebContent 하위에 생성

- 실제 저장되는 폴더는 아니지만 여기에 boardupload폴더를 만들어야 실제 서버에 저장되는 위치에도 boardupload폴더가 생성된다

 

게시판 프로그램 : 관련 컬럼

- board_num : 게시판에서는 글 번호가 primary key 가 된다, 글 번호는 sequence 로 입력한다

- board_pass : 글 작성시 작성한 비밀번호를 수정/삭제 시 사용하도록 하기 위한 비밀번호

- board_file : 첨부파일명

- board_re_ref : 글 그룹 번호, 원문은 글번호 num과 같은 숫자가 들어가고, 댓글은 부모와 같은 숫자가 들어간다

- board_re_lev : 댓글 깊이, 원문은 0 이 저장, 1단계 댓글은 1 저장, 2단계 댓글은 2 저장

- board_re_seq : 댓글을 출력 순서 제어, 원문은 0 으로 고정, 나머지는 오름차순 정렬되며 계속해서 바뀌는 컬럼

 

게시판 프로그램 : 테이블 및 시퀀스 생성

- 이전에 회원관리 프로그램에서 사용했던 오라클계정인 totoro 계정에 model2 테이블 생성

- 아래 양식으로 테이블을 생성한다

create table model2(
	board_num number primary key,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50), -- 첨부 파일명
	board_re_ref number, -- 글 그룹번호
	board_re_lev number, -- 댓글 깊이 : 원문(0), 1, 2...
	board_re_seq number, -- 댓글 출력 순서 : 원문(0) 오름차순 정렬
	board_readcount number,
	board_date timestamp );
    
create sequence model2_seq
	start with 1
	increment by 1
	nocache;

- WebContent 하위에 sql 폴더를 만들고 안에 board.sql 파일 생성

- 커넥션 프로파일을 설정 : Oracle_11, New Oracle(totoro), xe 로 해준다

- totoro 계정으로 커넥션을 연결하고

- 테이블과 시퀀스를 생성한다

- model2 테이블과 model2_seq 테이블 생성 확인

- 생성확인 완료

 

게시판 프로그램 : DAO 와 DTO 클래스 만들기

- 확장자가 .java 인 파일은 src 폴더 안에 있어야 정상동작한다

- Model 2 에선 기능에 따라 세분화하므로 DAO, DTO 가 다른 패키지에 들어감

- 해당 클래스를 사용할때 반드시 import 를 해야하고, 접근제어자는 public 이어야한다

- 나중에 회원관리, 게시판이 같은 프로젝트 안에 들어갈땐, 같은 기능을 하는 파일끼리 같은 폴더에 넣음

 

- 프로그램 구조도 대로 src안에 model 패키지를 만들고 그 안에 DTO 클래스를 만든다

- 프로그램 구조도 대로 src안에 dao 패키지를 만들고 그 안에 DAO 클래스를 만든다

 

게시판 프로그램 : DTO 클래스 작성

- 테이블 model2 의 create 문에서 가져와서 DTO 클래스에 복붙 후 수정

- varchar2 는 String 으로, number 는 Int 로, timestamp 는 Timestamp로 바꿔서 자바 변수(프로퍼티) 만들기

+ 프로퍼티의 접근제어자는 private

+ java.sql 의 Timestamp import

- 이후 getter / setter 메소드 추가

 

- DTO 클래스 완성 코드

- BoardBean.java

// DTO (Data Transfer Object)
package model;

import java.sql.Timestamp;

public class BoardBean {
	private int board_num;
	private String board_name;
	private String board_pass;
	private String board_subject;
	private String board_content;
	private String board_file;
	private int board_re_ref;
	private int board_re_lev;
	private int board_re_seq;
	private int board_readcount;
	private Timestamp board_date;
	
	public int getBoard_num() {
		return board_num;
	}
	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}
	public String getBoard_name() {
		return board_name;
	}
	public void setBoard_name(String board_name) {
		this.board_name = board_name;
	}
	public String getBoard_pass() {
		return board_pass;
	}
	public void setBoard_pass(String board_pass) {
		this.board_pass = board_pass;
	}
	public String getBoard_subject() {
		return board_subject;
	}
	public void setBoard_subject(String board_subject) {
		this.board_subject = board_subject;
	}
	public String getBoard_content() {
		return board_content;
	}
	public void setBoard_content(String board_content) {
		this.board_content = board_content;
	}
	public String getBoard_file() {
		return board_file;
	}
	public void setBoard_file(String board_file) {
		this.board_file = board_file;
	}
	public int getBoard_re_ref() {
		return board_re_ref;
	}
	public void setBoard_re_ref(int board_re_ref) {
		this.board_re_ref = board_re_ref;
	}
	public int getBoard_re_lev() {
		return board_re_lev;
	}
	public void setBoard_re_lev(int board_re_lev) {
		this.board_re_lev = board_re_lev;
	}
	public int getBoard_re_seq() {
		return board_re_seq;
	}
	public void setBoard_re_seq(int board_re_seq) {
		this.board_re_seq = board_re_seq;
	}
	public int getBoard_readcount() {
		return board_readcount;
	}
	public void setBoard_readcount(int board_readcount) {
		this.board_readcount = board_readcount;
	}
	public Timestamp getBoard_date() {
		return board_date;
	}
	public void setBoard_date(Timestamp board_date) {
		this.board_date = board_date;
	}
	
}

 

게시판 프로그램 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2. 정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 게시판 CRUD 관련 메소드

 

1. 싱글톤

- 외부 접근 불가능하게 private, 공유하기 위해 static 해서 자기자신의 클래스로 객체 생성을 한번만 하기

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드 getConnection() 생성

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- DBCP 방식으로 DB 와 연결하므로 이 DB 연결시 커넥션을 구해오는 이 메소드를 사용해야한다

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 dbcpAPITest.jsp 3줄을 복사해서 넣는다

 

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

+ Context 와 DataSource 둘 다 Interface 이다

 

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

+ 리턴자료형을 private 으로 해서 DAO 클래스 내에서만 호출가능하도록 함

 

- 현재까지 DAO 클래스 BoardDAO.java 코드

// DAO (Data Access Object)
package dao;

import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class BoardDAO {
	
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDAO instance = new BoardDAO();
	
	public static BoardDAO getInstance() {	// 정적 메소드
		return instance;
	}
	
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	private Connection getConnection() throws Exception {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 글 작성 : 원문작성
}

 

4. 그 이후 게시판 CRUD 관련 메소드

- 가장 먼저 원문 글 작성 메소드를 만들어야한다, 그 후 다양한 메소드들을 DAO에 작성

 

게시판 프로그램 : Controller 클래스

- Java Servlet 클래스로 만든다

- 모든 요청의 진입점

- src에서 오른마우스를 클릭해서 Servlet 선택

- controller 패키지, BoardFrontController 클래스 를 만든다

- Controller 클래스 생성 완료

 

- BoardFrontController.java (수정 전 1 , 기본적인 것만 작성했음)

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

 

- 오류 생겼을때 어느정도까지 실행되었는지 확인하기 위해서 System.out.println(); 으로 브라우저에 출력

- doGet() 과 doPost() 에 같은 내용을 써야한다, 요청 방식에 따라 서로 다른 일을 하는게 더 이상함

- doGet() 과 doPost() 에서 처리할 내용은 동일해야하므로 따로 공통적인 작업을 처리하는 메소드 doProcess() 를 만듬

- 그 메소드 doProcess() 를 doGet(), doPost() 에서 호출하도록 함

- doGet(), doPost() 는 자동으로 호출되는 메소드이므로 doGet(), doPost() 의 매개변수 request, response 를 doProcess() 호출시 전달

- doProcess() 안의 내용은 아래에

 

WebServlet 어노테이션 이름값으로 "*.do" 로 임의로 설정

@WebServlet("*.do")

- "*.do" 는 do 확장자로 요청하는 모든 요청을 받는다는 의미

- "*.do" 는 앞쪽의 파일명은 어떤 이름이어도 상관없고, 확장자가 do 인 모든 요청을 받는다는 의미

- 이 확장자는 임의로 설정한다, 주로 회사명

 

- @WebServlet("/") 로 하면 아무 이름으로 찾아가도 무조건 여기로 찾아온다

- 지금은 특정 확장자로 요청할때만 찾아오도록 확장자를 사용하자

+ 아파치 톰캣 버전 6 이하에는 @WebServlet 어노테이션 대신 web.xml 에 관련 내용들이 들어갔었다

+ Spring 프로젝트에서는 어노테이션 대신 과거 방식 처럼 Controller 클래스 이름과 위치, Servlet 매핑을 잡는 내용을 web.xml 안에서 설정함

+ Spring 에선 web.xml 안의 servlet  class (controller class) 관련 내용, 매핑 잡는 내용(요청 이름값 등) 들이 들어가있다

 

doProcess() 안의 내용 (BoardFrontController.java 부분)

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end

- request 객체에서 제공되는 6개의 메소드 중 getRequestURI() 와 getContextPath() 를 쓰고 있다

- Controller 클래스는 요청을 어떤 서비스 클래스로 넘겨줄지 결정함

- getRequestURI() : 요청 URI 를 구해줌 ex) /model2board/BoardAddAction.do

- getContextPath() : 현재 프로젝트 명을 구해줌 ex) /model2board

- substring() 으로 contextPath 변수(현재 프로젝트명) 의 길이 숫자부터 시작해서 추출

- 추출시 요청 URI 만 남게되고, 이 요청 URI 가 command 변수에 저장됨 ex) /BoardAddAction.do

 

Controller 클래스에서 맞는 Service 클래스로 요청을 넘기는 방법 (BoardFrontController.java 부분)

- /model2board 의 길이는 12, /model2board/BoardAddAction.do 에서 0번 인덱스부터 시작하므로 12번 인덱스는 /, 즉 /BoardAddAction.do 가 command 가 된다

 

요청을 보내는 방법

- 게시판에 원문 글을 작성하는 양식 WebContent/board/qna_board_write.jsp 파일을 보자

- qna_board_write.jsp 에서 form 의 action 값을 확인해보자

- 요청이름 확장자가 .do 이므로 Controller 클래스로 찾아간다

+ 요청 이름값과 Service 클래스명을 일치시킬 것 ex) 원문 글 작성 Service 클래스는 BoardAddAction.java 로 만들 것

 

- qna_board_write.jsp 를 실행시켜서 아무 값이나 입력하고 '등록' 을 누른 후 콘솔창을 보자

- 콘솔창 확인

- 요청이름값이 command 변수에 저장됨

 

만약 qna_board_write.jsp 에서 form 태그 action에 이렇게 패키지명을 쓰지 않고 ./ 로 쓰면

- requestURI 에 다른 하위폴더도 나오게 되어서 경로가 잘 맞지 안게 됨

 

게시판 프로그램 : Service 클래스의 부모 인터페이스

- Service 클래스를 통일성 있게 설계하기 위해 부모가 될 인터페이스 Action 먼저 만들기

- 인터페이스 Action 을 만들고 안에는 추상메소드 execute() 만들기

- Action.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Action {
	
	// 추상 메소드
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

- 리턴자료형은 ActionForward 로 한다

- Controller 클래스의 doProcess() 의 매개변수 request, response 를 그대로 execute() 의 매개변수에 복사

- 추상메소드 execute() 는 형식만 있다

 

게시판 프로그램 : ActionForward 클래스

- service 패키지 안에 ActionForward 클래스 만들기

- ActionForward.java

package service;

public class ActionForward {
	
	private boolean redirect;	// 포워딩 방식 설정
	private String path;		// 포워딩 페이지명 설정
	
	public boolean isRedirect() {
		return redirect;
	}
	public void setRedirect(boolean redirect) {
		this.redirect = redirect;
	}
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	
}

- 포워딩 방식과 포워딩 페이지명을 저장하는 클래스

1. 포워딩 방식을 결정할 변수 redirect 정의,  dispatcher, redirect 방식 중 어느 것으로 할지 true, false 로 결정

+ request 객체로 공유가 되었을때는 반드시 dispatcher 방식으로 포워딩되어야 뷰페이지에 출력 가능

2. 포워딩할 페이지명이 저장될 변수 path 를 정의

- 두 변수는 private 으로 설정

3. getter, setter 메소드 만들기

- Service 클래스에서 값을 처리하고 View 페이지로 가기 전에 이 ActionForward 로 포워딩 방식, 포워딩 페이지명을 결정

- setter 메소드는 Service 클래스 하단에서 호출해서 설정하고, getter 메소드는 Controller 하단에서 포워딩 할때 호출한다

 

- 다시 Controller 클래스로 돌아와서 Service 클래스로 넘겨주는 작업 및 포워딩 작업 코드를 쓰자

- BoardFrontController.java  (수정 전 2)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction")) {
			
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

- 모두 doProcess() 안에서 작성한다

- service 패키지 안의 Action 인터페이스 객체 action 를 만들고 null 로 초기화

- service 패키지 안의 ActionForward 클래스 객체 forward 를 만들고 null 로 초기화

- 원문 글 작성 요청이름값인 "/BoardAddAction.do" 일때의 처리를 해줌. Service 클래스 BoardAddAction.java 로 넘기기

- 마지막 부분에서 포워딩 처리를 한다

- forward != null 로 인해 DAO -> Service -> Controller 로 돌아올때만 여기서 포워딩을 해줌

 

포워딩 처리 (BoardFrontController.java 부분)

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Controller 클래스에서는 포워딩 처리도 한다

- forward 객체는 한번 Service 클래스로 갔다오면 null 이 아니게 됨

- forward.isRedirect() 는 true, false 를 돌려줌, true면 redirect 방식 포워딩, false 면 diepacher 방식으로 포워딩 하기로 함

- getPath() 메소드로 포워딩할 페이지 불러오기

 


게시판 프로그램 : 원문 글 작성

- 이제 잠시 Controller 클래스는 두고, 원문 글 작성 기능을 먼저 구현하자

- 먼저 원문 글 작성 폼 qna_board_write.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>MVC 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">MVC 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">파일 첨부</div>
		</td>
		<td>
			<input name="board_file" type="file"/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="등록">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

 

원문 글 작성 폼 form 태그 보기 (qna_board_write.jsp)

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">

- 개발환경이 아닐땐 프로젝트 명이 빠지므로, 프로젝트 명을 명시하지 말고 request.getContextPath() 메소드 사용하기

- 첨부파일을 포함한 데이터 전송시에는 method 는 post 방식으로만 가능하다

- 첨부파일을 전송하기 위해 반드시 enctype="multipart/form-data" 가 필요하다

- action 을 .do 확장자로 작성했으므로 Controller 클래스로 잘 찾아감

 

 

테이블 컬럼과 폼에서 전달되는 값 비교

- board_name, board_pass, board_subject, board_content, board_file 값만 폼에서 넘어간다

- model2 테이블 컬럼에서 댓글처리 3개 컬럼, 조회수 컬럼, 작성시간 컬럼은 form 에서 넘어가지 않는다

- 원문 글이므로 board_re_ref 컬럼은 board_num 과 똑같이 sequence 로 들어감, board_re_lev 컬럼은 0이 들어감

+ 가입 양식의 name 값, DTO 의 프로퍼티명, 테이블의 컬럼명을 일치시켜야편함, MyBatis 사용시 반드시 일치시켜야함

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (미완성, 연결 확인만 하기)

- 실제 원문 글 작성 요청을 처리하는 BoardAddAction.java 파일을 만들자

- 원문 글 작성 폼의 요청이름값과 동일하게 설정

- service 패키지에 BoardAddAction.java 클래스 생성

- BoardAddAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardAddAction.java 클래스가 있다)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 원문 글 작성 Service 클래스 BoardAddAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardAddAction.do 로 요청이 올때의 경우를 처리

		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

- Controller 클래스는 어떤 Service 클래스로 넘겨줄건지 결정

- command 값, 즉 요청 이름값이 "/BoardAddAction" 면 회원가입을 처리하는 BoardAddAction 클래스로 객체 action 생성

- 왼쪽은 부모 인터페이스, 오른쪽은 상속받는 구현 클래스가 왔다, 업캐스팅을 하고 있음

- action 객체를 통해 BoardAddAction () 클래스의 오버라이딩된 메소드인 execute() 를 실행하고 ActionForward 객체 forward 로 리턴받음

- 해당 클래스 BoardAddAction 의 객체를 만들고 execute() 를 실행하므로써 그 Service 클래스 BoardAddAction 로 가는 것임

+ 메소드 오버라이딩이 되면 오버라이딩 된 메소드만 실행된다

- execute() 를 호출하며 매개변수로는 doGet(), doPost() 의 매개변수인 request, response 를 전달함

+ try-catch 예외처리 해야한다

- DB 연동을 할 때만 Service 클래스로 넘어가고, DB 연동을 하지 않으면 바로 포워딩 객체 생성 후 포워딩 경로, 방식 설정 후 아래의 포워딩 코드로 포워딩하면 된다

* BoardAddAction 클래스는 원문 글 작성 Service 클래스이다.

 

- 이제 View 페이지, Service 클래스, Controller 에서 연결까지 했으니 원문 글 작성 폼인 qna_board_write.jsp 실행

- '등록' 을 클릭하고 콘솔창을 보자 

- BoardAddAction 이 출력되는 것을 보아, 폼 -> Controller -> Service 까지 왔음을 확인 가능

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (이어서)

- BoardAddAction.java (수정 후)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import dao.BoardDAO;
import model.BoardBean;

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결
		
		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");
		
		return forward;
	}

}

- 첨부파일이 원문 글 작성 폼 에서 넘어왔으므로 request 객체로 getParamter() 해서 값을 받을 수 없다

- 넘어온 첨부파일을 받아서 서버로 전송해야한다

 

첨부파일을 서버로 업로드 (BoardAddAction.java 부분)

		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결

1. 실제 업로드할 위치 경로를 getRealPath() 로 구해서 path 변수에 저장

- 폴더 boardupload 에 저장할 것

2. 첨부파일의 크기 구하기 (단위 : Byte)

3. MultipartRequest 객체를 생성하면 자동으로 업로드 된다

- DefaultFileRenamePolicy 객체를 생성해서 중복문제도 해결해줌

- cos 라이브러리에서 지원되는 2가지 클래스이다

- 한글 인코딩을 여기서 했으므로 따로 할 필요 없다

- Model 1 자료실 게시판, 첨부파일 업로드 : https://laker99.tistory.com/120?category=1080281 

 

전달된 나머지 값 받기 (BoardAddAction.java 부분)

		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));

- 첨부파일을 제외한 나머지 값들은 request 객체로 값을 받을 수 없다, multi 객체로 받아야함

- java 파일이거나 첨부파일 전송이 포함되어있을때는 useBean, setProperty action tag 사용 불가능

- 일일히 setter 메소드로 객체 board에넣어주기

- 클라이언트가 업로드한 파일명, 실제 서버에 저장된 파일명 2개가 있다

- 그 중 여기선 실제 서버에 저장된 파일명을 getFilesystemName("board_file") 메소드로 가져와서 board 객체에 저장

- 매개변수에 작성하는 "board_file" 은 첨부파일 업로드 양식의 name 값이다

 

DB 의 메소드 호출해서 DB 와 연동 (BoardAddAction.java 부분)

		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");

- 그 후 DAO 객체를 생성하고 DAO 의 insert() 메소드로 사용자가 입력한 값들을 DB에 삽입함

- 글 작성 이후 목록페이지

 

포워딩 방식, 포워딩 페이지 설정 (BoardAddAction.java 부분)

		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");

- 목록페이지로 가는 요청이 아니라 DB에서 목록을 가져오는 요청이다

- 목록을 가져오기 위한 Service 클래스와 목록을 출력하는 View 페이지가 따로 있다

- View 페이지로 가는게 아닌 목록을 가져오기 위한 요청인 "/BoardListAction.do" 로 목록을 가져와야함

- 글 작성 성공 후 목록페이지로 돌아가야한다,그냥 목록페이지로 가면 아무것도 없음

- 목록을 끄집어내는 요청 "/BoardAddAction" 을 먼저 해야한다, 다시 Controller 클래스로 감

 

- 이제 Service -> DAO 로 가야한다

- 작성된 원문글을 DB에 삽입하는 메소드 insert() 를 DAO 클래스에 작성

 

DAO 클래스 원문 글 작성 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 insert() 부분 코드만

	// 글 작성 : 원문작성
	public int insert(BoardBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into model2 values(model2_seq.nextval,?,?,?,?,?,";
			sql += "model2_seq.nextval,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_pass());
			pstmt.setString(3, board.getBoard_subject());
			pstmt.setString(4, board.getBoard_content());
			pstmt.setString(5, board.getBoard_file());
			pstmt.setInt(6, 0);	// board_re_lev
			pstmt.setInt(7, 0); // board_re_seq
			pstmt.setInt(8, 0); // board_readcount
			
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- 사용자가 원문 글 작성폼에 입력한 정보를 저장하고 있는 DTO 객체 board 가 매개변수로 넘어옴

- board_re_ref 값은 board_num 과 같은값이 들어가야하므로 같은 시퀀스 mode2_seq 사용하면 같은 값 들어감

- 원문이므로 board_re_lev, board_re_seq, readcount 는 0 이 들어갸아한다

- insert 수행 후 Service 클래스인 BoardAddAction.java 로 돌아간다

 

- 이제 DB와 연동이 끝났으니 원문 글 작성 폼 qna_board_write.jsp 를 실행해서 글을 작성해보자

- 아직 뒤는 구현하지 않았으므로 나오지 않음, 콘솔창 보기

- 글 작성 성공 메세지가 나타났으므로 insert 가 잘 되었음을 알 수 있다

- path : 에 찍혀진 경로가 실제 첨부파일이 업로드되는 폴더이다

C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\model2board\boardupload

- 탐색기에 이 경로를 쳐보자

- 사용자가 첨부한 첨부파일도 잘 업로드 되었다

+ 빨간 선 아래는 BoardAddAction.java 에서 요청한 "/BoardListAction.do" 이다,

- dispatcher 방식 포워딩 시 URL 주소가 바뀌지 않으므로 post 방식으로 찍혀나오므로 빨간 선 아래도 post 로 나왔음


게시판 프로그램 : 목록 가져오기

흐름

- BoardAddAction.java(글 작성) 에서 "/BoardListAction.do" 로 포워딩 페이지를 설정했다, Controller 클래스로 돌아감

- BoardFrontController.java 부분

- 아래 포워딩 처리 코드에서 "/BoardListAction.do" 로 포워딩하므로 다시 Controller 클래스의 WebContent 어노테이션으로 찾아감

- 그리고 다시 Controller 에서 command.equals("/BoardListAction.do") 인 조건문으로 와서 BoardListAction.java Service 클래스로 찾아감

- 요청이 /BoardListAction.do 로 온 것을 처리하는 코드를 추가 한 후이다.

 

Model 1 vs Model 2 목록 페이지

- Model 1 에선 list.jsp 파일에서 모두 작업했다

- Model 2 에선 목록을 가져오는 파일 (Service 클래스) 과 화면에 뿌려주는 파일 (JSP 파일) 이 따로 있다

 

- service 클래스 안에 BoardListAction.java 생성

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (미완성, 연결 확인만 하기)

- service 클래스 안에 BoardListAction.java 생성

- BoardListAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardListAction.java 클래스가 있다)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 목록 가져오기 Service 클래스 BoardListAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardListAction.do 로 요청이 올때의 경우를 처리

		// 글 목록
		} else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (이어서)

- 페이징 처리를 해서 목록을 가져와야한다, 가져온 목록을 request 객체로 공유godigksek

- View 에서 그 공유값을 가져와서 목록을 뿌린다 (화면에 출력)

- BoardListAction.java (수정 후)

package service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		int page = 1; 	// 현재 페이지 번호
		int limit = 10;	// 한 페이지에 출력할 데이터 개수
		
		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}
		
		// page=1 : startRow=1, endRow=10
		// page=2 : startRow=11, endRow=20
		// page=3 : startRow=21, endRow=30
		int startRow = (page - 1) * limit + 1;
		int endRow = page * limit;
		
		BoardDAO dao = BoardDAO.getInstance();
		int listcount = dao.getCount(); // 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		List<BoardBean> boardlist = dao.getList(startRow, endRow);
		System.out.println("boardlist : " + boardlist);
		
		// 총 페이지 수
		int pageCount = listcount/limit + ((listcount % limit == 0) ? 0 : 1);
		
		int startPage = ((page - 1) / 10) * limit + 1;	// 1, 11, 21
		int endPage = startPage + 10 - 1;			// 10, 20, 30
		
		if(endPage > pageCount) endPage = pageCount;
		
		// 공유 설정
		request.setAttribute("page", page);
		request.setAttribute("listcount", listcount);
		request.setAttribute("boardlist", boardlist);
		request.setAttribute("pageCount", pageCount);
		request.setAttribute("startPage", startPage);
		request.setAttribute("endPage", endPage);
		
		// request 객체로 공유한 경우에는 dispatcher 방식으로 포워딩되어야,
		// view 페이지에서 공유한 값에 접근 가능
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_list.jsp");
		return forward;
	}

}

1. 기본변수 page : 사용자가 클릭함으로서 넘어온 페이지값이 있다면 그 페이지값을 page 변수에 저장, 없다면 1 이 저장됨

ex) 하단의 페이지 들 중 3 페이지를 클릭하면 다시 BoardListAction.java 로 오게되어 page 변수에 3이 들어간다

2. 기본변수 limit : 한 화면에 출력할 데이터 개수, 현재 10개로 설정

3. 파생변수 startRow / endRow : 목록을 limit (10) 개씩 잘라주기 위한 변수, 페이지 번호에 따라 결정됨

ex) page가 3이면, startRow 는 21, endRow 는 30 으로서 21 ~ 30번쨰 글을 가져옴

4. 기본변수 listcount : 총 데이터 개수, DB와 연동하여 getCount() 메소드로 가져옴

5. 다음에는 DB와 연동하여 실제로 목록을 자르고 목록을 List 로 구해오는 getList() 메소드를 호출하고 결과를 리스트 boardlist 로 받자

- 나중에 출력시 DTO 객체 (주솟값) 가 콘솔창에 찍혀나온다

- DAO 에서 다시 여기로 돌아와서 돌려받은 리스트 boardlist 에 저장된 데이터를 출력해야한다

6. 파생변수 pageCount : 총 페이지 수를 구해서 저장, 총 데이터개수을 limit 으로 나누고, 나누어 떨어지지 않는다면 1을 더함

7. 파생변수 startPage / endPage : 블럭의 시작 페이지와 끝페이지

- 여기서 블럭은 페이지 10개를 출력하도록 했으므로 startPage, endPage 를 만들때 10 으로 나누고 더하고 있다

ex) 두번째 블럭은 시작페이지가 11 페이지, 끝 페이지가 20 페이지, 세번째 블럭은 시작페이지가 21 페이지

- 가장 마지막 블럭에서는 무조건 10개의 페이지 출력을 하면 안되므로 마지막 페이지를 총 페이지수로 설정함

		if(endPage > pageCount) endPage = pageCount;

8. 마지막 파생변수인 화면 출력 번호는 목록을 출력하는 View 페이지에서 만들기

- 블럭에 있는 1 2 3 ... 10 처럼 페이지를 클릭할 수 있는 것은 View 페이지에서 만들 것

9 . View 페이지에서 목록을 출력해야하므로 여기서 모든 기본변수, 파생변수, 검색결과(데이터)를 저장한 리스트를 request 객체로 공유설정해야함

- 공유되는 값의 형태를 잘 봐야함

10. ActionForward 객체 forward 를 생성 하고 포워딩 방식과 포워딩 페이지를 설정해줌

- request 공유설정 했으므로 반드시 dispatcher 방식으로 포워딩한다

- 목록(게시판) View 페이지인 qna_board_list.jsp 로 포워딩 한다

- 즉 여기 BoardListAction Service 클래스에서 DB에서 목록을 먼저 가져온 후 공유가 선행되어야 한다

- 그 다음에 목록 View 페이지로 포워딩해야 공유된 값을 가져와서 View 에서 출력 가능

11. 다음으로는 Controller 클래스로 돌아가서 forward 객체를 받아서 Controller 하단 포워딩 코드로 qna_board_list.jsp 로포워딩한다

 

값의 형태에 따라 View 페이지에서 출력하는 방법이 다르다

- page, listcount, pageCount, startPage, endPage 는 기본자료형 공유값이므로 바로 ${네임값} 으로 출력 가능

- boardlist 는 리스트이므로 forEach 의 items 안에 들어간다

 

- Model 1 게시판 목록 가져오기 부분 : https://laker99.tistory.com/123?category=1080281 

* DAO 클래스 getCount() 메소드 작성은 아래에

* DAO 클래스 getList() 메소드 작성은 아래에

 

DAO 클래스 총 데이터 개수 구하는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getCount() 부분 코드만

	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select count(*) from model2";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
//				result = rs.getInt(1); 아래와 같다
				result = rs.getInt("count(*)");
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- select 문이므로 ResultSet 객체 rs 가 필요

- 그룹함수 count(*) 를 사용해서 총 데이터 개수를 구한 후 그걸 result 변수에 저장해서 돌려준다

 

DAO 클래스 목록을 자르고 목록을 구해주는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getList() 부분 코드만

	// 글 목록
	public List<BoardBean> getList(int start, int end) {
		List<BoardBean> list = new ArrayList<BoardBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from ( select rownum rnum, board.* from ";
			sql += " (select * from model2 order by board_re_ref desc, ";
			sql += " board_re_seq asc) board ) ";
			sql += " where rnum >= ? and rnum <= ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, start);
			pstmt.setInt(2, end);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			while(rs.next()) {
				BoardBean board = new BoardBean();
				
				board.setBoard_num(rs.getInt("board_num"));
				board.setBoard_name(rs.getString("board_name"));
				board.setBoard_pass(rs.getString("board_pass"));
				board.setBoard_subject(rs.getString("board_subject"));
				board.setBoard_content(rs.getString("board_content"));
				board.setBoard_file(rs.getString("board_file"));
				board.setBoard_re_ref(rs.getInt("board_re_ref"));
				board.setBoard_re_lev(rs.getInt("board_re_lev"));
				board.setBoard_re_seq(rs.getInt("board_re_seq"));
				board.setBoard_readcount(rs.getInt("board_readcount"));
				board.setBoard_date(rs.getTimestamp("board_date"));
				
				list.add(board);
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		
		return list;
	}

- 여러개의 데이터를 구해야하므로 리턴자료형은 List 이다

- 먼저 리스트를 만들고 그 리스트에 검색한 데이터들을 저장한 다음 그 리스트를 반환

- DTO 객체 board 생성 후, 검색결과를 컬럼단위로 잘라서 setter 로 board 에 저장 후 그 board 를 리스트 list 에 저장 

<SQL문>

- 첫번째 서브쿼리문에서는 rownum 컬럼의 별칭을 rnum 으로 설정하고, 두번째 서브쿼리에서 검색한 모든 컬럼을 다 사용 (board.*)

- 두번째 서브쿼리문에서 원하는 방식으로 정렬을 한다. 두번째 서브쿼리의 별칭이 board

- board_re_ref 기준 내림차순으로 한번 정렬을 한다

- 부모와 그 부모의 댓글들은 모두 같은 ref 값을 가지고 있으므로 두번째 정렬을 board_re_seq 기준 오름차순으로 한다

- 전체 데이터를 원하는 방식으로 정렬한 곳에서 where 문으로 rnum >= startRow and rnum <= endRow 로 글들 가져오기

 

- getAttribute() 사용 후 다운캐스팅 하고 표현식 태그로 출력하는 대신 EL 로 쉽게 처리

 

- 이제 포워딩 할 페이지인 qna_board_list.jsp 에서 request 객체로 공유한 리스트, 변수들을 여기서 뿌려줘야함

- WebContent/board 폴더 안에 qna_board_list.jsp 생성 및 작성

- qna_board_list.jsp (수정 전, 아직 작성 안함)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
게시판 목록
</body>
</html>

 

- model 1 처럼 이 파일 qna_board_list.jsp 를 그냥 실행해선 안된다, 바로 실행 시 공유된 데이터가 없다

- 현재는 글 작성 후 목록 가져오기 요청을 통해 Controller 클래스로 가서 필요한 목록을 가져온 후 이 페이지로 와야함

- 매번 글 작성 후 목록으로 가기 힘드므로 개발 중간에 목록을 확인하면서 개발을 하기 위해 index.jsp 파일에 목록을 가져오기 요청을 하는 코드를 써서 쉽게 목록 페이지로 갈 수 있도록 하자

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
모델2 게시판

<script>
	location.href = "./BoardListAction.do";
</script>
</body>
</html>

- index.jsp 에서 목록 가져오기 요청을 한다

- index.jsp 실행 시 목록을 가져오는 요청을 Controller 클래스에 하고 Service -> DAO -> Service 로 목록을 가져왔다가 qna_board_list.jsp 로 포워딩 됨

- index.jsp 실행 시

- 목록 구하는 요청 부터 qna_board_list.jsp 까지 온 것 이다, "게시판 목록" 출력은 qna_board_list.jsp 의 코드이다

- boardlist 가 찍힌 것을 봐서 Controller -> Service -> DAO -> 다시 Service 까지 왔다

 

- 다시 qna_board_list.jsp 로 돌아오자

- qna_board_list.jsp (수정 후 1, 목록 뿌리고 있다, 아직 블럭관련은 안함, 미완성)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %> 개 <br>

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<td>번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>날짜</td>
		<td>조회수</td>
	</tr>
	
	<c:set var="num" value="${listcount - (page - 1) * 10}"/>
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>
			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEE요일"/>
		</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>
</table>

 

공유 값 가져오기 (qna_board_list.jsp 부분)

- BoardListAction 에서 공유한 값들을 사용해서 출력만 시켜야한다, 테이블을 이용해서 출력

- 공유된 값이 기본자료형이라면 바로 ${네임값} 으로 출력 가능하다

- 공유된 값이 리스트일 때는 JSTL forEach 태그의 items 속성에 공유 네임이 들어가야한다

	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>${b.board_date}</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>

- 그 forEach 태그 안에서 리스트의 값(글)들을 변수 b 로 받아서 반복문으로 출력

- 그 글의 제목, 작성자 들을 출력하기 위해서 ${b.board_subject}, ${b.board_writer} 사용

- 필드들은 private 임, 직접 접근이 아니라 이 EL ${b.board_subject}, ${b.board_writer} 은 내부적으로 b.getBoard_subject , b.getBoard_writer() 과 같은 의미임

- 주의 : 표현식 태그 <%=number --%> 와 달리 EL 에서는 증감연산자 지원안되므로 ${num --} 불가능, 화면 출력시 ${num} 출력 후 set 태그로 재정의해서 1 을 감소시켜야함

 

화면 출력 번호 (qna_board_list.jsp 부분)

	<c:set var="num" value="${listcount - (page - 1) * 10}"/>

- 화면에 출력되는 번호는 마지막 파생변수로서 여기서 JSTL set 태그로 그 변수 num 을 만들어준다

+ 10 은 limit 값

 

원문 작성 폼으로 가는 링크 (qna_board_list.jsp 부분)

<a href="./BoardForm.do">글작성</a> <br>

- '글작성' 에 링크를 걸어서 원문 글 작성 폼으로 가도록 하자, 바로 갈 수 없고 먼저 Controller 클래스로 요청해야함

- Controller 클래스에 "/BoardForm.do" 로 요청시 원문 글 작성 폼으로 가는 코드를 작성해야한다

* Controller 클래스 코드는 아래에

* qna_board_list.jsp 모두 작성 후 실행 해서 확인하는 것은 아래에

 

원하는 포맷으로 날짜 출력 (qna_board_list.jsp 부분)

			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEEE"/>

- JSTL 국제화 라이브러리 사용

- EEEE 는 자동으로 "요일" 까지 뒤에 붙여줌

- EL 과 SimpleDateFormat 은 같이 사용 불가, 이제부터는 JSTL 국제화 라이브러리로 원하는 포맷으로 날짜 출력하기

 

댓글인 경우에는 제목 앞에 여백주기 (qna_board_list.jsp 부분)

		<td>
			<!-- 댓글 제목 앞에 여백 처리 -->
			<c:if test="${b.board_re_lev > 0}">
				<c:forEach var="i" begin="0" end="${b.board_re_lev}">
					&nbsp;
				</c:forEach>
			</c:if>
			${b.board_subject}
		</td>

- 댓글을 구별하기 위해 여백주기

- 공유로 넘어온 리스트에서 forEach 로 가져온 글 하나의 board_re_lev 가 0 보다 크면 댓글이라는 의미

- 댓글인 경우 forEach 루프를 돌리며 댓글 깊이(board_re_lev) 만큼 여백을 주기

 

제목 클릭시 링크로 상세페이지로 넘어가기 (qna_board_list.jsp 부분)

<td>
	<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>

- 상세페이지로 갈때의 요청은 "/BoardDetailAction.do" 로 하자

+ 상세페이지 Service 클래스명도 BoardDetailAction.java

- 요청하면서 현재 글 번호와 페이지 번호를 가져가야한다

- ?board_num=${b.board_num}&page=${page} 를 뒤에 붙여서 상세페이지로 넘어갈때 get 방식으로 상세페이지로 이 값들을 전달한다

 

- 공유 설정을 했던 BoardListAction.java 부분 (글 목록 가져오는 Service 클래스)

- 현재 페이지 번호 page 는 BoardListAction.java (글 목록 가져오기 Service 클래스) 에서 기본자료형으로 공유설정되었으므로 ${page} 로 가져올 수 있다

- 현재 글 번호는 BoardListAction.java 에서 기본자료형으로 공유설정되지 않았고, 공유설정된 리스트 boardlist 에서 가져와야하므로 ${b.board_name} 으로 가져왔다


EL 을 쓰지 않고 공유된 값을 출력하려면?

글갯수 : ${listcount } 개 <br>

- 이 코드를 EL 로 작성 (아래)

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %>

- getAttribute() 를 사용해야하고, 다운캐스팅 명시와 언박싱을 해야한다

- EL을 사용하는 것이 훨씬 간편하다


Controller 클래스에서 원문 글 작성폼으로 전달 (이동)

		// 글 작성폼
		} else if(command.equals("/BoardForm.do")) {
			try {
				forward = new ActionForward();
				forward.setRedirect(false);
				forward.setPath("./board/qna_board_write.jsp");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

- DB 연동을 하지 않으므로 Service 클래스로 가지 않고 바로 폼으로 감

- ActionForward 객체 foward 를 여기서 생성 후 바로 포워딩 방식, 포워딩 페이지(원문 글 작성폼) 설정

 

- index.jsp 를 실행해서 목록인 qna_board_list.jsp 로 이동 한 후 '글작성' 클릭해보자

- 글 작성폼으로 잘 넘어간다


- qna_board_list.jsp 작성이 끝난 후 확인을 위해 index.jsp 를 실행해서 목록을 가져온 뒤 View qna_board_list.jsp 로 오자

* 아직 블럭에서 페이지 1 2 3 ... 10 중 페이지를 선택하는 건 구현하지 않았음

- '글작성' 을 눌러서 원문 글 작성도 한번 더 해보자

- 최신순으로 정렬되어있다, 화면의 번호는 글 번호 board_num 이 아니다

- 현재 페이지의 가장 위 게시물의 번호를 구하고, 그 번호를 1씩 감소시켜서 나타낸 번호이다

+ Recent posts