MVC - 서블릿(컨트롤러)/답변형 게시판 1(계층구조)
웹 애플리케이션 개발은 일반적으로 많이 사용하는 표준화 소스 구조를 만들어 개발을 진행하며, 이렇게 표준화된 소스 구조를 웹 애플리케이션 모델이라고 한다.
웹 애플리케이션 모델의 종류에는 모델 1과 모델 2 방식이 있다.
모델1 방식은 DB 연동 같은 비즈니스 로직 작업과 그 결과를 나타내는 작업을 동일한 jsp에서 수행한다. 즉, 모든 클라이언트의 요청과 비즈니스 로직 처리를 jsp가 담당하는 구조이다. 이 방식은 기능 구현이 쉽고 편리하지만 유지보수에 문제가 생긴다.
모델2 방식은 요청 처리, 응답 처리, 비즈니스, 로직 처리를 분리해서 구현하는 것으로 객체 지향 프로그래밍에서 각각의 기능을 모듈화해서 개발하자는 것과 같은 원리다.
모델2 구조에서 가장 자주 사용되는 개념은 MVC 패턴이다. 웹 애플리케이션을 화면 부분, 요청 처리 부분, 로직 처리 부분으로 나누어 개발하는 방법이라고 볼 수 있다.
Controller : 사용자의 요청 및 흐름 제어 담당
Model : 비즈니스 로직 처리
View : 사용자에게 보여줄 화면 담당
회원 관리
컨트롤러
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
@WebServlet("/member/*")
public class MemberController extends HttpServlet{
MemberDAO memberDAO;
@Override
public void init() throws ServletException {
//memberDAO 생성
memberDAO = new MemberDAO();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String nextPage = null;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
String action = request.getPathInfo();//URL에서 요청명을 가져옴
//최초 요청이거나 action 값이 "/listMembers.do"일 경우 회원 목록 출력
if (action == null || action.equals("/listMembers.do")) {
List<MemberVO> membersList = memberDAO.listMembers();
request.setAttribute("membersList", membersList);
nextPage = "/test01/listMembers.jsp";//이 경로로 포워딩
}
//action 값이 이거일 때 전송된 회원 정보 가져와서 테이블에 추가
else if(action.equals("/addMember.do")){
String id = request.getParameter("id");
String pwd = request.getParameter("pwd");
String name = request.getParameter("name");
String email = request.getParameter("email");
MemberVO memberVO = new MemberVO(id, pwd, name, email);
memberDAO.addMember(memberVO);
nextPage = "/member/listMembers.do";//등록 후 다시 회원 목록 페이지로 이동
}
//action 값이 이거일 때 회원 가입 창을 출력
else if(action.equals("/memberForm.do")) {
nextPage = "/test01/memberForm.jsp";//회원 가입 페이지로 포워딩
}
//수정창 요청 시 ID로 회원 정보를 조회한 후 수정창으로 포워딩
else if(action.equals("/modMemberForm.do")){
String id = request.getParameter("id");
MemberVO memInfo = memberDAO.findMember(id);//id로 회원정보 조회
request.setAttribute("memInfo", memInfo);//수정하기 전 회원정보 전달
nextPage = "/test01/modMemberForm.jsp";
}
else if(action.equals("/modMember.do")) {
String id = request.getParameter("id");
String pwd = request.getParameter("pwd");
String name = request.getParameter("name");
String email = request.getParameter("email");
MemberVO memberVO = new MemberVO(id, pwd, name, email);
memberDAO.modMember(memberVO);
request.setAttribute("msg", "modified");//회원 목록 창으로 수정 작업 완료 메시지 전달
nextPage = "/member/listMembers.do";
}
else if(action.equals("/delMember.do")) {
String id = request.getParameter("id");//삭제할 회원 ID 받아옴
memberDAO.delMember(id);
request.setAttribute("msg", "deleted");//회원 목록 창으로 삭제 완료 메시지 전달
nextPage = "/member/listMembers.do";
}
else {
List<MemberVO> membersList = memberDAO.listMembers();
request.setAttribute("membersList", membersList);
nextPage = "/test01/listMembers.jsp";//이 경로로 포워딩
}
//nextPage에 지정한 요청명으로 다시 서블릿에 요청
RequestDispatcher dispatch = request.getRequestDispatcher(nextPage);
dispatch.forward(request, response);
}
}
|
cs |
DAO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
public class MemberDAO {
private static MemberDAO instance = new MemberDAO();
private Connection con = null;
private PreparedStatement pstmt = null;
private ResultSet rs = null;
public static MemberDAO getInstance() {
return instance;
}
public Connection getConnection() throws Exception {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/oracle");
return ds.getConnection();
}
public List<MemberVO> listMembers(){
List<MemberVO> membersList = new ArrayList<MemberVO>();
String sql = "select * from t_member order by joindate desc";
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
while(rs.next()) {
String id = rs.getString("id");
String pwd = rs.getString("pwd");
String name = rs.getString("name");
String email = rs.getString("email");
Date joinDate = rs.getDate("joindate");
MemberVO memberVO = new MemberVO(id,pwd,name,email,joinDate);
membersList.add(memberVO);
}
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return membersList;
}
public void addMember(MemberVO vo){
try {
con = getConnection();
String id = vo.getId();
String pwd = vo.getPwd();
String name = vo.getName();
String email = vo.getEmail();
String sql = "insert into t_member(id,pwd,name,email) values(?,?,?,?)";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,id);
pstmt.setString(2,pwd);
pstmt.setString(3,name);
pstmt.setString(4,email);
pstmt.executeUpdate();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public MemberVO findMember(String _id){
MemberVO memInfo = null;
try {
con = getConnection();
String sql = "select * from t_member where id=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,_id);
rs = pstmt.executeQuery();
if (rs.next()) {
String id = rs.getString("id");
String pwd = rs.getString("pwd");
String name = rs.getString("name");
String email = rs.getString("email");
Date joinDate = rs.getDate("joindate");
memInfo = new MemberVO(id, pwd, name, email, joinDate);
rs.close();
pstmt.close();
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return memInfo;
}
public void modMember(MemberVO memberVO) {
String id = memberVO.getId();
String pwd = memberVO.getPwd();
String name = memberVO.getName();
String email = memberVO.getEmail();
try {
con = getConnection();
String sql = "update t_member set pwd=?, name=?, email=? where id=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,pwd);
pstmt.setString(2,name);
pstmt.setString(3,email);
pstmt.setString(4,id);
pstmt.executeUpdate();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void delMember(String id) {
try {
con = getConnection();
String sql = "delete from t_member where id=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, id);
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
cs |
VO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class MemberVO {
private String id;
private String pwd;
private String name;
private String email;
private Date joinDate;
public MemberVO() {
System.out.println("생성자 호출");
}
public MemberVO(String id, String pwd, String name, String email) {
this.id = id;
this.pwd = pwd;
this.name = name;
this.email = email;
}
public MemberVO(String id, String pwd, String name, String email, Date joinDate) {
this.id = id;
this.pwd = pwd;
this.name = name;
this.email = email;
this.joinDate = joinDate;
}
//각 필드에 대한 getter/setter 아래에
|
cs |
회원 목록 페이지(listMembers.jsp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<%request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<!-- 회원 추가, 수정, 삭제 작업 후 컨트롤러에서 넘긴 msg 값에 따라 작업 결과를 alert 창에 출력 -->
<c:choose>
<c:when test="${msg=='addMember'}">
<script type="text/javascript">
window.onload = function () {
alert("회원을 등록했습니다.");
}
</script>
</c:when>
<c:when test="${msg=='modified'}">
<script type="text/javascript">
window.onload = function () {
alert("회원을 정보를 수정했습니다.");
}
</script>
</c:when>
<c:when test="${msg=='deleted'}">
<script type="text/javascript">
window.onload = function () {
alert("회원을 삭제했습니다.");
}
</script>
</c:when>
</c:choose>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">회원정보</h1>
<table align="center" border="1">
<tr>
<th width="7%">아이디</th>
<th width="7%">비밀번호</th>
<th width="7%">이름</th>
<th width="7%">이메일</th>
<th width="7%">가입일</th>
<th width="7%">수정</th>
<th width="7%">삭제</th>
</tr>
<c:choose>
<c:when test="${empty membersList}">
<tr>
<td colspan="5"><b>등록된 회원이 없습니다.</b></td>
</tr>
</c:when>
<c:when test="${!empty membersList}">
<c:forEach var="mem" items="${membersList}">
<tr>
<td>${mem.id}</td>
<td>${mem.pwd}</td>
<td>${mem.name}</td>
<td>${mem.email}</td>
<td>${mem.joinDate}</td>
<td>
<a href="${contextPath}/member/modMemberForm.do?id=${mem.id}">수정</a>
</td>
<td>
<a href="${contextPath}/member/delMember.do?id=${mem.id}">삭제</a>
</td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</table>
<a href="${contextPath}/member/memberForm.do">
<p align="center">회원가입하기</p>
</a>
</body>
</html>
|
cs |
회원 정보 수정 페이지(modMemberForm.jsp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<%request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<form action="${contextPath}/member/modMember.do?id=${memInfo.id}" method="post">
<h1 align="center">회원 정보 수정창</h1>
<table>
<tr>
<td width="200">아이디</td>
<td width="400"><input type="text" name="id" value="${memInfo.id}" disabled></td>
</tr>
<tr>
<td width="200">비밀번호</td>
<td width="400"><input type="password" name="pwd" value="${memInfo.pwd}"></td>
</tr>
<tr>
<td width="200">이름</td>
<td width="400"><input type="text" name="name" value="${memInfo.name}"></td>
</tr>
<tr>
<td width="200">이메일</td>
<td width="400"><input type="text" name="email" value="${memInfo.email}"></td>
</tr>
<tr>
<td width="200">가입일</td>
<td width="400"><input type="text" name="joinDate" value="${memInfo.joinDate}" disabled=></td>
</tr>
<tr>
<td width="200"> </td>
<td width="400" colspan="2">
<input type="submit" value="수정하기">
<input type="reset" value="다시입력">
</td>
</tr>
</table>
</form>
</body>
</html>
|
cs |
회원 가입 페이지(memberForm.jsp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<form action="${contextPath}/member/addMember.do" method="post">
<h1 align="center">회원 가입창</h1>
<table>
<tr>
<td width="200">아이디</td>
<td width="400"><input type="text" name="id"></td>
</tr>
<tr>
<td width="200">비밀번호</td>
<td width="400"><input type="password" name="pwd"></td>
</tr>
<tr>
<td width="200">이름</td>
<td width="400"><input type="text" name="name"></td>
</tr>
<tr>
<td width="200">이메일</td>
<td width="400"><input type="text" name="email"></td>
</tr>
<tr>
<td width="200"> </td>
<td width="400">
<input type="submit" value="가입하기">
<input type="reset" value="다시입력">
</td>
</tr>
</table>
</form>
</body>
</html>
|
cs |
답변형 게시판
답변형 게시판을 제작하기 이전에 먼저 MVC 구조에 변화가 생긴다. 원래 컨트롤러, JSP, DAO만 있었지만 여기에 서비스 클래스가 더해진다.
개발을 할 때에는 서비스 클래스를 거쳐서 DAO 클래스의 기능을 수행하도록 한다.
그 이유는 DAO는 DB에 접근하는 기능을 수행하고, 서비스는 실제 프로그램을 업무에 적용하는 업무 단위인 트랜잭션으로 작업을 수행한다.
업무 단위는 사용자 입장에서 하나의 논리적인 기능을 뜻한다.
웹 애플리케이션에서 보통 묶어서 처리하는 단위 기능에는 아래의 것이 있다.
- 게시판 글 조회 시 해당 글을 조회하는 기능과 조회 수를 갱신하는 기능
- 쇼핑몰에서 상품 주문 시 주문 상품을 테이블에 등록 후 주문자의 포인트를 갱신하는 기능
- 은행에서 송금 시 송금자의 잔고를 갱신하는 기능과 수신자의 잔고를 갱신하는 기능
개발 시에 서비스 클래스의 메서드를 이용해 큰 기능을 단위 기능으로 나눈 후 서비스 클래스의 각 메서드는 자신의 기능을 더 세부적인 기능을 하는 DAO의 SQL 문들을 조합하여 구현한다.
답변형 게시판을 만들기 위해서 우선 위와 같은 테이블을 하나 생성한다.
답변형 게시판에서 중요한 점은 글 목록을 그냥 나열하는 것이 아니라 부모 글에 대한 답변글을 계층 구조로 보여줘야 한다는 점이다. 이 문제점은 쿼리를 통해서 해결할 수 있다.
게시판 목록 계층형 구조로 나타내기
컨트롤러
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
@WebServlet("/board/*")
public class BoardController extends HttpServlet {
BoardService boardService;
ArticleVO articleVO;
@Override
public void init() throws ServletException {
//서블릿 초기화 시 BoardService 객체 생성
boardService = new BoardService();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String nextPage ="";
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=utf-8");
String action = request.getPathInfo();//요청명을 가져옴
try {
List<ArticleVO> articleList = new ArrayList<ArticleVO>();
if(action == null) {
articleList = boardService.listArticles();
request.setAttribute("articleList", articleList);
nextPage = "/board01/listArticles.jsp";
}
//액션 값이 아래와 같으면 전체 글을 조회
else if(action.equals("/listArticles.do")) {
articleList = boardService.listArticles();//전체 글 조회
request.setAttribute("articleList", articleList);//articleList로 바인딩
nextPage = "/board01/listArticles.jsp";//listArticles.jsp로 포워딩
}
RequestDispatcher dispatch = request.getRequestDispatcher(nextPage);
dispatch.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
cs |
컨트롤러 클래스를 위와 같이 작성. 목록 매핑 경로를 입력할 경우 글 목록을 출력하는 역할을 한다. getPathInfo 메서드를 이용해서 action 값을 가져오고 그 값이 null이거나 listArticles.do일 경우 서비스의 listArticles 메서드를 호출해 전체 글을 조회하고, 뷰로 포워딩
DAO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
public class BoardDAO {
private static BoardDAO instance = new BoardDAO();
private Connection con = null;
private PreparedStatement pstmt = null;
private ResultSet rs = null;
public static BoardDAO getInstance() {
return instance;
}
public Connection getConnection() throws Exception {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/oracle");
return ds.getConnection();
}
public List<ArticleVO> selectAllArticles() {
List<ArticleVO> articleList = new ArrayList<ArticleVO>();
try{
con = getConnection();
//오라클 계층형 SQL문 실행
String sql = "select level, articleno, parentno, title, content, id, writedate from t_board "
+ "start with parentno=0 "
+ "connect by prior articleno=parentno "
+ "order siblings by articleno desc";
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
int level = rs.getInt("level");//각 글의 계층을 저장
int articleno = rs.getInt("articleno");
int parentno = rs.getInt("parentno");
String title = rs.getString("title");
String content = rs.getString("content");
String id = rs.getString("id");
Date writedate = rs.getDate("writedate");
//글 정보를 ArticleVO 객체의 속성에 설정
ArticleVO article = new ArticleVO();
article.setLevel(level);
article.setArticleno(articleno);
article.setParentno(parentno);
article.setTitle(title);
article.setContent(content);
article.setId(id);
article.setWritedate(writedate);
articleList.add(article);
}
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return articleList;
}
}
|
cs |
서비스 클래스에서 selectAllArticle 메서드를 호출하면 계층형 sql문을 이용해 계층형 구조로 전체 글을 조회한 후 반환
서비스
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class BoardService {
BoardDAO boardDAO;
public BoardService() {
//생성자 호출 시 BoardDAO()객체 생성
boardDAO = new BoardDAO();
}
public List<ArticleVO> listArticles() {
List<ArticleVO> articleList = boardDAO.selectAllArticles();
return articleList;
}
}
|
cs |
DAO 객체를생성한 후 selectAllArticle 메서드를 호출하여 전체 글을 가져옴
VO
1
2
3
4
5
6
7
8
9
10
11
12
|
public class ArticleVO {
private int level;
private int articleno;
private int parentno;
private String title;
private String content;
private String imagefilename;
private Date writedate;
private String id;
}
/*getter/setter, 생성자 */
|
cs |
listArticles.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}"/>
<%request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<table border="1" align="center" width="80%">
<tr height="10" align="center">
<th>글번호</th>
<th>작성자</th>
<th>제목</th>
<th>작성일</th>
</tr>
<c:choose>
<c:when test="${empty articleList}">
<tr height="10">
<td colspan="4" align="center">
<b>등록된 글이 없습니다.</b>
</td>
</tr>
</c:when>
<c:when test="${!empty articleList}">
<c:forEach var="article" items="${articleList}" varStatus="articleNum">
<tr align="center">
<!-- <forEach>의 varStatus의 count 속성을 이용해 글번호 1부터 자동으로 표시 -->
<td width="5%">${articleNum.count}</td>
<td width="10%">${article.id}</td>
<td align="left" width="35%">
<!-- 왼쪽으로 30px만큼 여백을 준 후 글 제목들을 표시 -->
<span style="padding-left: 30px"></span>
<c:choose>
<!-- level 값이 1보다 큰 경우는 자식 글이므로 level 값만큼 부모 글 및에 -->
<c:when test="${article.level>1}">
<!-- 부모 글 기준으로 왼쪽 여백을 level 값만큼 채워 답글을 부모 글에 대해 들여쓰기 -->
<c:forEach begin="1" end="${article.level}" step="1">
<span style="padding-left: 20px"></span>
</c:forEach>
<span>[답변]</span>
<!-- 공백 다음에 자식글을 표시 -->
<a href="${contextPath}/board/viewArticle.do?articleno=${article.articleno}">
${article.title}
</a>
</c:when>
<c:otherwise>
<a href="${contextPath}/board/viewArticle.do?articleno=${article.articleno}">
${article.title}
</a>
</c:otherwise>
</c:choose>
</td>
<td width="10%">
<fmt:formatDate value="${article.writedate}"/>
</td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</table>
<a href="#">글쓰기</a>
</body>
</html>
|
cs |
반복 시 각글의 level 값이 1보다 크면 답글이기 때문에 다시 내부 반복문 태그를 이용해 1부터 level 값까지 반복하면서 들여쓰고, 답글을 표시
결과는 위와 같이 출력