반응형
SMALL

게시글 검색 기능

검색어 색션에 검색어를 입력하면 해당하는 게시글만 보이게 하자.

[ 만들 기능 ]
1. 게시글 전체 검색 기능
2. 내 블로그에서 내 게시글로만 검색기능

1. 블로그 게시글 전체 검색 기능

블로그 게시글 전체 검색

검색을 블로그 메인 페이지에서 진행하면 전체 게시글 중에 검색하고(내것도 나옴), 내 블로그 안에서 하면 내가 작성한 게시글들만 검색되게 해보자.

 

 1) jsp

mainPage.jsp 에서 검색 시 form 으로 보내주도록 하자. 옵션값을 줘서 제목 또는 내용으로 검색할 수 있게 할 것이다.

<div class="card-header">블로그 전체 검색</div>
  	<div class="card-body">
  		<form method="get" action="searchWordAction.me">
  			<div class="input-group">
                 <select name="option">
					<option value="01" selected="selected">제목</option>
					<option value="02">내용</option>
				</select>
                <input class="form-control" type="text" id="searchWord" name="searchWord" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                <button class="btn btn-primary" id="button-search" type="submit">검색</button>
            </div>
        </form>
     </div>
</div>

옵션값을 만든다

form을 통해 입력한 값을 searchWordAction.me로 보내줄 것임.

 

2) Controller

어차피 메인페이지에 게시글 리스트 띄우는 방식과 동일하다. 조건만 바뀐 것일 뿐. PostListAction을 복사해서 약간만 수정해서 쓰도록 할 것이다.

//-------------------- 메인페이지에서 검색하기 ---------------------------
else if (command.equals("/member/searchWordAction.me")) {	
//get 방식으로 던졌고 아래 Action 페이지에서 getParameter로 받을 수 있음.
//name 값이 있어야 받음. id만 있어서는 못받음.
	action = new SearchWordAction();
	// SearchWordAction, 검색페이지
	try {
		forward = action.execute(req, resp);
		System.out.println("검색 표시");
	} catch (Exception e) {
		e.printStackTrace();
	}
}

3) Action

request.getParameter는 전부 String 형태로 받아지므로 Int형으로 변환해준다. 옵션값과 키워드 값을 Service에 들려보낸다.

또한 검색이 다 되면 검색페이지로 데이터를 뿌릴꺼니까 mainPage_search.jsp를 하나 만들어서 거기다 뿌려주자.

public class SearchWordAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 로그인할 때는 setSession해서 가져오고
		// 여기 리스트에서는 get으로 뿌린다
		String sessionId = (String) session.getAttribute("id");

		//option값과 검색어 값을 form에서 받아온다.
		//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
		int option = Integer.parseInt(req.getParameter("option"));
		String searchWord = req.getParameter("searchWord");
		System.out.println("검색어: "+searchWord);
		
		
		ActionForward forward = null; //어디로 갈지?

		
		//1. 로그인 상태인지 확인
		if (sessionId == null) {
			// 세션에 저장된 아이디가 없다면~
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./memberLogin.me");

		//2. 게시글 목록 보여짐	
		} else {
			// 검색된 게시글 목록 표시.
			forward = new ActionForward();
			PostListService postListService = new PostListService();
			//배열로 검색한 게시글목록을 가져온다.
			ArrayList<PostBean> postSearchList = postListService.getSearchList(option,searchWord);

			req.setAttribute("postSearchList", postSearchList);
			
			//mainPage로 게시글 목록을 던져준다.
			forward.setPath("./mainPage_search.jsp");
		}
		return forward;
	}
}

 

▼ mainPage_search.jsp 전체 소스

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="ko">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>Blog Home</title>
        <!-- Favicon-->
      <!--   <link rel="icon" type="image/x-icon" href="assets/favicon.ico" />  -->
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link" href="myBlogAction.me?id=${sessionScope.id}">MyBlog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page header with logo and tagline-->
        <header class="py-5 bg-light border-bottom mb-4">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> 검색결과를 확인합니다.</h1>
                    <p class="lead mb-0">내 게시글을 포함한 모든 포스팅을 검색합니다.</p>
                </div>
            </div>
        </header>
        <!-- Page content-->
        <div class="container">
            <div class="row">
                <!-- Blog entries-->
                <div class="col-lg-8">	 
                <br>
               <form method="get" action="searchWordAction.me">
                 <div class="input-group">
                       <select name="option">
						<option value="01" selected="selected">제목</option>
						<option value="02">내용</option>
						</select>
                        <input class="form-control" type="text" id="searchWord" name="searchWord" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                        <button class="btn btn-primary" id="button-search" type="submit">검색</button>
                 </div>
                </form>
	        	
	        	<br>
                  <c:forEach var="postSearchList" items="${postSearchList}">
                    <!-- Featured blog post -->
                    <div class="card mb-4">
                        <div class="card-body">
                           <a href="#!"><img class="card-img-top" src="../resources/img/thumbnail/${postSearchList.POST_THUMBNAIL}" alt="..." /></a>
                          
                          <div class="small text-muted">${postSearchList.POST_UPLOADTIME} / 조회수 : ${postSearchList.visit_cnt}</div>
                            <h2 class="card-title">${postSearchList.POST_TITLE} -----  ${postSearchList.MEM_NO}</h2>
                            <p class="card-text">${postSearchList.POST_CONTENT}</p>
                            <a class="btn btn-primary" href="postViewAction.me?postno=${postSearchList.POST_NO}">Read more →</a>
                        </div>
                    </div>
                    </c:forEach>
                    
                    <!-- Pagination-->
                    <nav aria-label="Pagination">
                        <hr class="my-0" />
                        <ul class="pagination justify-content-center my-4">
                            <li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">Newer</a></li>
                            <li class="page-item active" aria-current="page"><a class="page-link" href="#!">1</a></li>
                            <li class="page-item"><a class="page-link" href="#!">2</a></li>
                            <li class="page-item"><a class="page-link" href="#!">3</a></li>
                            <li class="page-item disabled"><a class="page-link" href="#!">...</a></li>
                            <li class="page-item"><a class="page-link" href="#!">15</a></li>
                            <li class="page-item"><a class="page-link" href="#!">Older</a></li>
                        </ul>
                    </nav>
                </div>
                <!-- Side widgets-->
                <div class="col-lg-4">
                <br>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">${sessionScope.id} 님</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a class="btn btn-primary" href="myBlogAction.me?id=${sessionScope.id}">내 블로그</a></li>
                                        <li><a href="myBlog.jsp">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a class="btn btn-primary" href="postInsert.me">새 게시물 작성</a></li>
                                        <li><a href="logout">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
           
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
       <!-- <script src="js/scripts.js"></script> -->
    </body>
</html>

 

4) Service

기존에 메인페이지에 게시글 리스트를 뿌렸던 PostListService에 클래스를 하나 추가해주자.(getSearchList)

public class PostListService {

	//메인페이지 게시글 리스트 가져오기.
	public ArrayList<PostBean> getPostList(String sessionId) {
    
    ...
 
 	}
    
    //메인페이지에서 검색했을 때 전체 검색된 목록 가져오기.
	public ArrayList<PostBean> getSearchList(int option, String searchWord) {
		//커넥션 풀 : DB와 미리 연결해놓은 커넥션 객체들을 풀에 저장해뒀다가 클라에서 요청오면 객체 빌려주고 볼일 끝나면 다시 반납받아 풀에 저장하는 방식
		Connection con = getConnection();
		// DB와 연결을 얻어내고.
		PostDAO postDAO = PostDAO.getInstance();
		// DB접속용 DAO 객체 참조 얻어오기.(싱글톤 패턴 : 인스턴스가 1개만 생성됨)
		// import static db.JdbcUtil.*;
		postDAO.setConnection(con);
		// 얻어온 커넥션 저장. -> 연결해서 할일 함.
		ArrayList<PostBean> postList = postDAO.selectPostSearchList(option, searchWord);
		//커넥션으로 DB에서 게시글 목록 받아서 배열에 저장

		close(con);
		// 사용을 마친 커넥션 반환
		return postList;
		// 게시글 목록 리턴.
	}

5)DAO

가져온 옵션값에 따라 검색하는 SQL문을 달리해준다. SQL의 like 구문을 사용해 키워드를 검색한다.

		//=========================== 메인페이지에서 전체게시글 검색하는 SQL로직 ===============================
		public ArrayList<PostBean> selectPostSearchList(int option, String searchWord) {

			String sql = "";
			//옵션값에 따라 SQL문을 달리한다.
			if(option == 01) {
				//메인페이지에서 전체게시글 검색하는 SQL문(DB 이름 확인하기***)
				sql = "select * from post_info where POST_TITLE like ?";
				
			}else if(option == 02) {

				sql = "select * from post_info where POST_CONTENT like ?";
			}else {}
						
			ArrayList<PostBean> PostSearchList = new ArrayList<PostBean>();
			PostBean pb = null;
			
			try {
				pstmt = con.prepareStatement(sql);
				
				//검색어가 어디에있던지 검색되게끔 앞뒤에 %를 붙여줌.
				pstmt.setString(1, "%"+searchWord+"%");
				
				rs = pstmt.executeQuery(); //executeQuery : resultSet 객체 반환
				//select는 executeQuery()를 사용한다.
				//쿼리문 처리결과 ResultSet의 객체인 rs에 저장.
				

				if (rs.next()) {//조회된 결과가 있다면 아래 문장 수행.
					do {//한 번 수행하고 또 수행할 게 있으면 수행.
						pb = new PostBean();
						//게시글 1개의 정보를 저장할 수 있는 PostBean 객체 생성.
						pb.setPOST_NO(rs.getInt("POST_NO")); //게시글번호
						pb.setMEM_NO(rs.getInt("MEM_NO")); //회원번호
						pb.setPOST_TITLE(rs.getString("POST_TITLE")); //게시글제목
						pb.setPOST_THUMBNAIL(rs.getString("POST_THUMBNAIL")); //게시글섬네일
						pb.setPOST_VIDEO(rs.getString("POST_VIDEO")); //게시글비디오
                        
						String preStr=rs.getString("POST_CONTENT"); 
						pb.setPOST_CONTENT(preStr); //게시글내용 미리보기
                        
						pb.setVisit_cnt(rs.getInt("Visit_cnt")); //게시글조회수
						pb.setPOST_UPLOADTIME(rs.getString("POST_UPLOADTIME")); //게시글업로드타임
						//조회된 결과를 PostBean객체에 저장.
						PostSearchList.add(pb);
						//저장하면서 생성된 것을 이제 List에 담아냄.(ArrayList)
						//반복문이 실행될 때마다 게시글 1개씩 누적 시킴.
					} while (rs.next());
					//rs.next때문에 어레이 리스트 다음 값으로 넘어간다.(차례차례 읽어옴)
				}
			} catch (Exception ex) {
				System.out.println("PostSearchList 에러 : " + ex);
			} finally {
				close(rs);
				close(pstmt);
			}
			return PostSearchList;
		}

 

전체적으로 검색하기 위해 %%로 묶어서 검색하려고 하는데 어렵더라... 구글링해서 찾아낸 답...WOW

String sql = "select * from post_info where POST_TITLE like '%?%'"; //에러

//구글링 한건데 미쳤다...
sql="select * from table where name like ?";
pstmt=conn.prepareStatement(sql);   
pstmt.setString(1,"%"+name+"%");

전체 포스팅 검색(제목,내용 조건)

 


 

2. 내 블로그에서 내 게시글로만 검색기능

로직이 동일하다. SQL문 조건만 다를 뿐.

 

 1) jsp

<div class="card-header">내 블로그에서 검색</div>
<div class="card-body">
    <form method="get" action="searchWordMyAction.me">
        <div class="input-group">
            <select name="option">
                <option value="01" selected="selected">제목</option>
                <option value="02">내용</option>
            </select>
            <input class="form-control" type="text" id="searchWord" name="searchWord" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
            <button class="btn btn-primary" id="button-search" type="submit">검색</button>
        </div>
    </form>
</div>

 

▼ myBlog_search.jsp 전체 소스

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="ko">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>myBlog</title>
        <!-- Favicon-->
      <!--   <link rel="icon" type="image/x-icon" href="assets/favicon.ico" />  -->
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">MyBlog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- 블로그 헤더 및 블로그 배경화면 -->
        <header class="py-5 bg-light border-bottom mb-4" style="background-image: url('https://www.urbanbrush.net/web/wp-content/uploads/edd/2022/01/urbanbrush-20220105101328484351.jpg')">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> 검색결과를 확인합니다.</h1>
                    <p class="lead mb-0">내 게시글을 검색합니다.</p>
                </div>
            </div>
        </header>
        <!-- Page content-->
        <div class="container">
       
            <div class="row">
            <!-- 왼쪽 블로그 소개 위젯-->
                <div class="col-lg-3">
                <!-- Side widget-->
                    <div class="card mb-4">
                    	<img class="card-img-top" src="../resources/img/blog/${mySearchList[0].MEM_PIC}" alt="..." />
                        <div class="card-header">${mySearchList[0].MEM_ID}</div>
                        <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다! 이미지는 일단 고정으로 박아뒀지롱...</div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">카테고리?</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">내 블로그</a></li>
                                        <li><a href="#">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postInsert.me">새글작성</a></li>
                                        <li><a href="logout">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- 오른쪽 Blog 게시글 내용-->
                <div class="col-lg-9">
                  <form method="get" action="searchWordMyAction.me">
	                 <div class="input-group">
	                    <select name="option">
							<option value="01" selected="selected">제목</option>
							<option value="02">내용</option>
						</select>
	                    <input class="form-control" type="text" id="searchWord" name="searchWord" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
	                    <button class="btn btn-primary" id="button-search" type="submit">검색</button>
	                 </div>
	              </form>
	        	<br>
                        <c:forEach var="mySearchList" items="${mySearchList}">
                    <!-- Featured blog post -->
                    <div class="card mb-4">
                        <div class="card-body">
                           <a href="#!"><img class="card-img-top" src="../resources/img/thumbnail/${mySearchList.POST_THUMBNAIL}" alt="..." /></a>
                          
                          <div class="small text-muted">${mySearchList.POST_UPLOADTIME} / 조회수 : ${mySearchList.visit_cnt}</div>
                            <h2 class="card-title">${mySearchList.POST_TITLE} -----  ${mySearchList.MEM_NO}</h2>
                            <p class="card-text"> ${mySearchList.POST_CONTENT} </p>
                            <a class="btn btn-primary" href="postViewAction.me?&postno=${mySearchList.POST_NO}">Read more →</a>
                        </div>
                    </div>
                    </c:forEach>

                </div>
                
            </div>
           
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
       <!-- <script src="js/scripts.js"></script> -->
    </body>
</html>

 

 

2) controller

//-------------------- 내블로그에서 내 게시글 검색하기 ---------------------------
else if (command.equals("/member/searchWordMyAction.me")) {	
//get 방식으로 던졌고 아래 Action 페이지에서 getParameter로 받을 수 있음.
//name 값이 있어야 받음. id만 있어서는 못받음.
	action = new SearchWordMyAction();
	// SearchWordMyAction, 검색페이지
	try {
		forward = action.execute(req, resp);
		System.out.println("검색 표시");
	} catch (Exception e) {
		e.printStackTrace();
	}
}

 

3) Action

세션 ID 값까지 총 3개의 값을 보내줘야함.

public class SearchWordMyAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 세션에서 id값 가지고 있기.
		String sessionId = (String) session.getAttribute("id");
		//option값과 검색어 값을 form에서 받아온다.
		//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
		int option = Integer.parseInt(req.getParameter("option"));
		String searchWord = req.getParameter("searchWord");
		System.out.println("검색어: "+searchWord);
		
		ActionForward forward = null; //어디로 갈지?

			// 내 게시글 목록 및 내용 표시.
			forward = new ActionForward();
			MyPostListService myPostListService = new MyPostListService();
			//배열로 게시글 목록을 가져온다. id는 같이 던져줘야 읽음.
			ArrayList<PostBean> mySearchList = myPostListService.getMySearchList(sessionId,option,searchWord);

			req.setAttribute("mySearchList", mySearchList);
			
			//검색된 게시글 목록을 던져준다.
			forward.setPath("./myBlog_search.jsp");
		
		return forward;
	}
}

 

4) Service

public class MyPostListService {

	public ArrayList<PostBean> getMyPostList(String sessionId) {
    
    ...
 
 	}
    
    //내 게시글 검색
	public ArrayList<PostBean> getMySearchList(String sessionId, int option, String searchWord) { //리스트이므로 배열로 가져온다.
		//커넥션 풀 : DB와 미리 연결해놓은 커넥션 객체들을 풀에 저장해뒀다가 클라에서 요청오면 객체 빌려주고 볼일 끝나면 다시 반납받아 풀에 저장하는 방식
		Connection con = getConnection();
		// DB와 연결을 얻어내고.
		PostDAO postDAO = PostDAO.getInstance();
		// DB접속용 DAO 객체 참조 얻어오기.(싱글톤 패턴 : 인스턴스가 1개만 생성됨)
		// import static db.JdbcUtil.*;
		postDAO.setConnection(con);
		// 얻어온 커넥션 저장. -> 연결해서 할일 함.
		ArrayList<PostBean> mySearchList = postDAO.selectMySearchList(sessionId,option, searchWord); //selectMyPostList는 PostDAO에서 받아온다.
		//커넥션으로 DB에서 게시글 목록 받아서 배열에 저장

		close(con);
		// 사용을 마친 커넥션 반환
		return mySearchList;
		// 게시글 목록 리턴.
	}
}

 

5)  DAO

where m.mem_id = [세션ID값] AND POST_CONTENT like [키워드]

조건 두개를 AND로 묶어서 검색한다.

//=========================== 내 블로그 게시글을 검색하는 SQL로직 ===============================
		public ArrayList<PostBean> selectMySearchList(String sessionId, int option, String searchWord) {
			// ArrayList: 객체 배열 비슷, 컬렉션 프로임워크
			// 여러 개의 게시글 정보를 저장한다.

			String sql = "";
			
			if(option == 01) {
				sql = "select p.*,m.mem_id,m.mem_pic from post_info p "
						+ "join memberinfo m on p.mem_no = m.mem_no where m.mem_id = ? AND POST_TITLE like ?";
				
			}else if(option == 02) {

				sql = "select p.*,m.mem_id,m.mem_pic from post_info p "
						+ "join memberinfo m on p.mem_no = m.mem_no where m.mem_id = ? AND POST_CONTENT like ?";
			}else {}
			
			ArrayList<PostBean> mySearchList = new ArrayList<PostBean>();
			PostBean pb = null; //Bean(vo)은 그릇이다. 뭘 가져올지는 vo에 목록이 있다.
			

			try {
				pstmt = con.prepareStatement(sql);
				
				//세션 id값 가져와서 SQL 문에 넣어줌.
				pstmt.setString(1, sessionId);
				//검색어가 어디에있던지 검색되게끔 앞뒤에 %를 붙여줌.
				pstmt.setString(2, "%"+searchWord+"%");
                
                ...

내 블로그 내 포스팅 검색(제목,내용 조건)

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

게시글 수정하기

게시글 수정 버튼을 누르면 네이버 스마트 에디터를 연결해놓은 페이지로 이동한다. 그리고 이전에 작성했던 내용이 나오고, 그 내용을 수정하면 다시 update 될 수 있게 하자.

사진은 수정 할수도, 안할 수도 있기 때문에 따로 분기처리를 해줘야할 것 같다.

[ 만들 기능 ]
1. 게시글 수정 버튼 누르면 수정페이지로 이동
2. 게시글 수정 페이지에 기존에 작성한 내용 띄우기
3. 게시글 수정 내용을 저장하면 DB에 업데이트(단, 사진은 분기처리)

1. 게시글 수정 버튼 누르면 수정페이지로 이동

 1) jsp

blogPost.jsp는 게시글 상세보기 페이지이다. 세션에 접속된 유저가 보고 있는 게시글의 작성자라면, 수정/삭제버튼이 보이게 해놓았다. 수정버튼을 누르면 postUpdateAction 페이지로 이동한다.

내 게시글이면 수정/삭제 버튼이 보인다.

<a class="badge bg-secondary text-decoration-none link-light" href="postUpdateAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_update">수정</a>
<a class="badge bg-secondary text-decoration-none link-light" href="postDeleteAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_delete">삭제</a>

 

 2) controller

두가지 링크가 필요하다.

첫번째, 게시글 수정 페이지에 기존에 저장한 내용을 띄워주는 페이지

두번째, 기존 내용을 수정하여 저장버튼을 누르면 수정한 내용을 update 해주는 페이지

둘 다 링크를 다르게 진행해준다. 수정은 게시글 insert 때와 동일하게 ajax로 할 것이므로 ajax 이름을 붙여주었다. 

//-------------------- 게시글 안에 수정 누르면 게시글 수정 페이지로 이동 ---------------------------
else if(command.equals("/member/postUpdateAction.me")) {
    //게시글 수정 페이지로 이동함
    forward = new ActionForward();
    //객체 생성
    forward.setRedirect(false);
    //이동 허락 안함
    forward.setPath( "./updateEdit.jsp"))
    //거기 주소는 updateEdit.jsp으로 해라.(이동할 주소 저장)
    action = new PostEditAction(); //edit 페이지에 기존 정보 출력
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "기존 내용 띄우기 실패"))
    }
}

//-------------------- 수정한 게시글을 저장하는 PostUpdateAction 페이지 생성 ---------------------------
else if(command.equals("/member/postUpdateAjax")) {
    action = new PostUpdateAction();
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "포스팅 수정 실패"))
    }
}

 3) Action1 - DB에 저장된 기존 내용을 띄우는 PostEditAction

또 다시 연결할 필요없이, 기존에 [게시글 상세페이지] 내용과 동일하다. post_info 정보를 전부 select 해온 게시글 상세페이지 DB 연결을 그대로 사용한다. get 방식으로 URL에 담아온 파라미터인 postno 를 DAO에 던져 게시글의 정보를 전부 불러온다.

[삭제] 버튼과 마찬가지로 작성자가 아닌 유저가 버튼을 활성화해서 눌렀을 경우를 대비하여 sessionId와 작성자를 비교한다.

public class PostEditAction implements Action {
	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		ActionForward forward = null; //어디로 갈지?

			forward = new ActionForward();
			
			//session을 써서 서버 생성함.
    		HttpSession session = req.getSession();
    		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.

			//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
    		String sessionId = (String) session.getAttribute("id");
            System.out.println("수정 sessionId "+sessionId);
    		String post_writer = req.getParameter("writer");
    		int postNo = Integer.parseInt(req.getParameter("postno"));
            System.out.println("수정 postNo "+postNo);
    		

    		// 로그인 상태인지 확인 -> 아니면 로그인화면 고고
    		if (sessionId == null) {
    			forward = new ActionForward();
    			forward.setRedirect(true);
    			forward.setPath("./memberLogin.me");
    			
    		// 사용자가 작성자가 맞는지 확인 -> 아니면 alert를 띄우고 해당 포스트로 다시 이동
    		} else if (!sessionId.equals(post_writer)) {
    			resp.setContentType("text/html;charset=UTF-8");
    			PrintWriter out = resp.getWriter();
    			out.println("<script>");
    			out.println("alert('작성자가 아닌 사람은 게시글을 수정할 수 없습니다.')");
    			out.println("location.href='./postViewAction.me?postno="+postNo+"'");
    			out.println("</script>");
    			
    		}else {
					
			PostViewService postViewService = new PostViewService();
			//게시글 정보 띄워주는건 그냥 원래 게시글 상세페이지 보여줬던 정보 똑같이 가져오면 됨.
			PostBean postinfo = postViewService.getPostinfo(postNo);

			req.setAttribute("postinfo", postinfo);
			//수정페이지로 게시글 정보를 던져준다.
			forward.setPath("./updateEdit.jsp");
		
    		}
		return forward;
	}
}

3-1) updateEdit.jsp

제목 부분, 내용부분에 value 값으로 DB에서 가져온 기본 내용값을 넣어준다. textarea는 value 가 아닌 태그 안에 값을 넣어주면 된다.

주의할 점은 제이쿼리로 값을 보여지게 할 때 따음표로 묶어주는 것! 안묶어줬더니 제목이 잘려서 나오더라.(아래 결과창은 제목부분을 안묶어서 잘려나옴)

...  value='${postinfo.POST_TITLE}' >

file의 경우, 보안상 기본 값을 넣기 어려우므로 미리보기 이미지에만 띄워준다.

<div class="row">
<!-- 포스트 추가하는 곳-->
<!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
<!-- 파일 전송을 위해 encType = "multipart/form-data" -->
<form action="insertStudentInfoForm" id="form"  enctype="multipart/form-data" method="post">
	<!-- 제목 -->
  <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px'value='${postinfo.POST_TITLE}' >
  
  <div id="smarteditor">
  <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠.-->
	<textarea name="editorTxt" id="editorTxt" 
			  rows="20" cols="10" 
			  placeholder="내용을 입력하세요"
			  style='width: 600px'>${postinfo.POST_CONTENT}</textarea>
			  
  </div>
  <img id="preview" src="../resources/img/thumbnail/${postinfo.POST_THUMBNAIL}" alt="image" style="width:100px" />
  <input type="file" name="fileName" id='fileName' onchange="readURL(this);">
  <!-- 파일쪽엔 기본경로 불가(https://okky.kr/articles/260830) -->
  
  <input type='hidden' id='postno' value='${postinfo.POST_NO}'>
  <input type="button" value ="저장" onclick="submitPost()"/>
</form>
<!-- 포스트 추가하는 곳-->
</div>

수정 버튼을 누르면 DB에서 저장된 내용을 가져온다.

 4) Action2 - 수정한 내용을 DB에 update 하는 PostUpdateAction

PostUpdateAction는 Insert 하는 부분과 동일하다. 단, 사진 파일을 선택하지 않았을 경우(=null) 파일 이름은 업데이트 하지 않는다.

//파일 첨부를 한 경우에만 post 객체에 넣어준다. 없으면 sysout만 찍음.
if(fileName != null) {
	post.setPOST_THUMBNAIL(fileName);
}else {
	System.out.println("파일 첨부 안함");
}

 

PostUpdateAction.java 전체

더보기
package action;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

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

import svc.PostUpdateService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 작성한 포스트 저장 처리를 위한 클래스이다.*/
public class PostUpdateAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		PostBean post = new PostBean(); //vo에 선언한 변수들 import한거.
		//작성한 게시글 내용을 저장하고 DB로 전달.

		ActionForward forward = null;

  
		//------- 파일 업로드 구현 --------------------------------------
	    //업로드 파일 사이즈
        int fileSize = 5*1024*1024;

        String uploadPath = req.getServletContext().getRealPath("/resources/img/thumbnail");
        		
        //git 링크는 이렇지만 실제 소스 경로는 위와 같다.
        //"C:\\Users\\User\\git\\BlogPrj\\Test1\\WebContent\\resources\\img\\thumbnail";

        System.out.println("uploadpath는?  "+uploadPath);
        try {
            //파일업로드
            MultipartRequest multi = new MultipartRequest(req, uploadPath, fileSize, "UTF-8", new DefaultFileRenamePolicy());
        
            //파일 이름 초기화
            String fileName  = "";
            
            //파일 이름 가져오기
            Enumeration<String> names = multi.getFileNames();
            if(names.hasMoreElements()) {
                String name = names.nextElement();
                fileName = multi.getFilesystemName(name);
            }

    		//---------------------------------------------	
            

    			int postNo = Integer.parseInt(multi.getParameter("postno"));
	    		post.setPOST_NO(postNo);
	    		//입력 목록 적어주기(vo에서 받아옴.)
	    		post.setPOST_TITLE(multi.getParameter("title"));
	    		
	    		//파일 첨부를 한 경우에만 post 객체에 넣어준다. 없으면 sysout만 찍음.
	    		if(fileName != null) {
		    		post.setPOST_THUMBNAIL(fileName);
	    		}else {
	    			System.out.println("파일 첨부 안함");
	    		}
	    		
	    		post.setPOST_CONTENT(multi.getParameter("content"));
	    		
	    		PostUpdateService postUpdateService = new PostUpdateService();
	    		boolean result = postUpdateService.updatePost(post); //vo에서 받은 변수 보내줌.
	    		//게시글 저장이 잘 되었는지 여부
	    		if(result) {
	                forward.setRedirect(true);
	                forward.setPath("myBlogAction.me");
	            }
    		
	     } catch (Exception e) {
	        e.printStackTrace();
	     }
	        return forward;
    }
}

 

4-1) updateEdit.jsp

컨트롤러에 보내줘야 하는 데이터는 수정한 제목, 내용, 썸네일 파일 이외에도 작성자id, 게시글 번호 도 있다.

<input type='hidden' id='postno' value='${postinfo.POST_NO}'>

이런식으로 hidden 타입으로 데이터를 출력해 함께 ajax로 보내주자.

ajax 통신 부분에서도 사진 파일을 선택하지 않았을 경우(=null) 파일 이름은 보내주지 않는다.

// 등록할 파일 리스트를 formData로 데이터 입력
var form = $('#form');
var formData = new FormData(form[0]);

//파일을 새로 첨부한 경우에만 파일명을 컨트롤러에 보내준다.
var fileCheck = $("#fileName").val();
if(fileCheck != ''){ //파일 첨부를 했을경우에만
	formData.append('files',fileObject[0]);
}else{
	console.log("사진파일 변경이 없으므로 기존 썸네일을 유지합니다.")
}

formData.append('title', $("#title")[0].value);
formData.append('content', content);
formData.append('postno', $("#postno")[0].value);

분기처리해서 사진파일이 없으면 안보내준다. 게시글 번호도 폼데이터에 넣어서 보내준다.

▼ updateEdit.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 참고 : https://beforb.tistory.com/53 -->
    <!-- 참고 : https://www.wrapuppro.com/programing/view/ZPLdxEBiyJG38IG -->
    <!-- 에디터 : Naver SmartEditor 2.0 -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>게시글 작성</title>
        <!-- 제이쿼리 -->
		<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js"></script>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
      <script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog - 게시글 수정</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                    <!-- 포스트 추가하는 곳-->
                    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
                    <!-- 파일 전송을 위해 encType = "multipart/form-data" -->
				    <form action="insertStudentInfoForm" id="form"  enctype="multipart/form-data" method="post">
				    	<!-- 제목 -->
				    	
				      <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px' value='${postinfo.POST_TITLE}' >
				      
				      <div id="smarteditor">
				      <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠.-->
				        <textarea name="editorTxt" id="editorTxt" 
				                  rows="20" cols="10" 
				                  placeholder="내용을 입력하세요"
				                  style='width: 600px'>${postinfo.POST_CONTENT}</textarea>
				                  
				      </div>
				      <img id="preview" src="../resources/img/thumbnail/${postinfo.POST_THUMBNAIL}" alt="image" style="width:100px" />
				      <input type="file" name="fileName" id='fileName' onchange="readURL(this);">
				      <!-- 파일쪽엔 기본경로 불가(https://okky.kr/articles/260830) -->
				      
				     <input type='hidden' id='postno' value='${postinfo.POST_NO}' >
				      <input type="button" value ="저장" onclick="submitPost()"/>
				    </form>
              <!-- 포스트 추가하는 곳-->
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
	     <script>
	     
	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })
	    
	    
	    
	    //첨부한 파일 내용 전역변수로 가지고 있기
	    let fileObject = null;
	    //파일 선택 시 파일 내용 변수에 넣기
 	    $(document).ready(function() {
			$("#fileName").bind('change', function() {
				fileObject = this.files;
				//this.files[0].size gets the size of your file.
				//alert(this.files[0].size);
			});
		}); 
	    
 	   
	    /* 버튼 클릭 이벤트 */
	    function submitPost(){
	    	console.log(fileCheck);
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
			 
				// 등록할 파일 리스트를 formData로 데이터 입력
				var form = $('#form');
				var formData = new FormData(form[0]);
				
				//파일을 새로 첨부한 경우에만 파일명을 컨트롤러에 보내준다.
			 	   var fileCheck = $("#fileName").val();
					if(fileCheck != ''){ //파일 첨부를 했을경우에만
					formData.append('files',fileObject[0]);
					}else{
						console.log("사진파일 변경이 없으므로 기존 썸네일을 유지합니다.")
					}
					
					formData.append('title', $("#title")[0].value);

					console.log($("#title")[0].value)
					formData.append('content', content);
					formData.append('postno', $("#postno")[0].value);
			
			
			 
			 
			 if(confirm('게시글을 수정하시겠습니까?')){
			 
			    //ajax 통신으로 서버로 보내 데이터 저장함
			    $.ajax({
		          url: "postUpdateAjax"
		          , data: formData
		          , type: 'POST'
		          , enctype : 'multipart/form-data'
		          , contentType : false //false 로 선언 시 content-type 헤더가 multipart/form-data로 전송되게 함
		          , processData : false //false로 선언 시 formData를 string으로 변환하지 않음
		          , success: function(data) {
		            console.log('success')
		            alert('저장되었습니다.')
		            location.href='./myBlogAction.me'
		          }
		          , error: function(jqXHR, textStatus, errorThrown) {
		            console.log(jqXHR)
		            alert('오류가 발생하였습니다.')
		          }
		        })
			}else{
				alert('취소되었습니다.');
			}
		  }
		}
	    
	    
	    /* 이미지 미리보기 */
	    function readURL(input) {
		  if (input.files && input.files[0]) {
		    var reader = new FileReader();
		    reader.onload = function (e) {
		      $('#preview')
		      .attr('src', e.target.result);
		    };
		    reader.readAsDataURL(input.files[0]);
		  }
		}
	    
	    
	  </script>
     
    </body>
</html>

5) Service

DB 연결한다.

package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.commit;
import static db.JdbcUtil.getConnection;
import static db.JdbcUtil.rollback;

import java.sql.Connection;

import DAO.PostDAO;
import vo.PostBean;

/* 게시글을 DB에 수정후 저장하는 역할을 담당하는 클래스(Service) */
public class PostUpdateService {
	
	public boolean updatePost(PostBean post) {
		
		boolean updateSuccess = false; //수정완료 기본값 false
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		int updateCount = postDAO.updatePost(post);
		// DB추가 쿼리를 메소드로 구현.

		if (updateCount > 0) {
			updateSuccess = true;
			// 정상 처리 되었음을 호출했던 곳으로 리턴.
			commit(con); //DB 명령 완료 확인
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}
		close(con);
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return updateSuccess;
	}
}

6) DAO

update를 한다.썸네일 파일이 없을 경우 sql문에 들어가는 값의 갯수도 달라진다. 조건문으로 분기처리 해주었다...(더 좋은 방법이 있을까?)

// =========================== 게시글 수정하는 SQL로직 ===============================
// postUpdateService에서  게시글 수정할 때 DB와 JSP를 연결할 때 인자로 쓰임.
public int updatePost(PostBean post) {

    int updateCount = 0;
    System.out.println("파일첨부" + post.getPOST_THUMBNAIL());

    //파일 이름이 있는 경우 --------------------------------------
    if (post.getPOST_THUMBNAIL() != null) {
        String sql = 
		"UPDATE post_info SET "
		+ "post_title = ?,"
		+ "post_thumbnail = ?,"
		+ "post_content = ?"
		+ "WHERE POST_NO = ?";

        try {
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, post.getPOST_TITLE());
            pstmt.setString(2, post.getPOST_THUMBNAIL());
            //pstmt.setInt(4, post.getPOST_VIDIO());
            pstmt.setString(3, post.getPOST_CONTENT());
            pstmt.setInt(4, post.getPOST_NO()); //파라미터로 가져온 POST_NO 찾기

            updateCount = pstmt.executeUpdate();
            System.out.println("게시글 수정");
        } catch (Exception ex) {
            System.out.println("게시글 수정 안됨" + ex);
        } finally {
            close(pstmt); // import static db.JdbcUtil.*;
        }

    } else {
        //파일 이름이 없는 경우 --------------------------------------
        String sql2 = 
			"UPDATE post_info SET "
			+ "post_title = ?,"
		//	+ "post_thumbnail = ?,"
			+ "post_content = ?"
			+ "WHERE POST_NO = ?";

        try {
            pstmt = con.prepareStatement(sql2);
            pstmt.setString(1, post.getPOST_TITLE());
            
            // pstmt.setString(2, post.getPOST_THUMBNAIL()); //썸네일 없음
            
            pstmt.setString(2, post.getPOST_CONTENT());
            pstmt.setInt(3, post.getPOST_NO()); //파라미터로 가져온 POST_NO 찾기

            updateCount = pstmt.executeUpdate();
            System.out.println("게시글 수정");
        } catch (Exception ex) {
            System.out.println("게시글 수정 안됨" + ex);

        } finally {
            close(pstmt); // import static db.JdbcUtil.*;
        }
    }

    // --------------------------------------
    return updateCount;
}

 

수정할 부분)

1. 테스트해보니 제목이 잘려서 나온다.(따옴표로 묶으면 됨)

2. 썸네일을 변경했을 경우 파일이 잘 저장되는지 다시 확인해봐야한다. -> 와...set 다음에 띄어쓰기 안되어있어서 안되었었음.

▼ SQL 문 쓸 때 띄어쓰기 누락 주의할 것.

에러발생

java.sql.SQLSyntaxErrorException: ORA-00971: missing SET keyword 에러 발생....

	String sql = 
	"UPDATE post_info SET" //set 다음에 띄어쓰기 없어서 안됨....;;
	+ "post_title = ?,"
	+ "post_thumbnail = ?,"
	+ "post_content = ?"
	+ "WHERE POST_NO = ?";

3. 메인페이지 context 내용을 가져와서 15자를 잘라 사용했는데 editor 때문에 글씨 미리보기가 안됨..

-> 정규식으로 태그를 걷어내려 했으나 잘 안걷어져서 일단은 텍스트가 보이게 만들어놓음. 추 후 변경할 예정.

 

결과창

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

Naver SmartEditor 2.0으로 게시글 만들기(+DB연결)

네이버 블로그처럼 블로그 내용을 다채롭게 작성하고 싶었다. 찾아보니 Naver SmartEditor가 오픈소스로 나와있길래 사용하기로 했다.

현재 네이버 블로그에 적용된 3.0은 오픈소스로 공개가 되어있지 않고, 2.8까지 사진 업로드가 가능하다고 해서 2.8로 사용하기로 함.

[ 만들 기능 ]
1. 게시글 작성 페이지에 발행 누르면 DB 저장
2. 저장 후 게시글 상세보기 페이지에서 읽을 수 있도록 연결

1. Naver SmartEditor 2.0 설치하기

요 블로그 참고했다.(https://beforb.tistory.com/53)

스마트 에디터 2.8.2 다운로드 : https://github.com/naver/smarteditor2/releases/tag/v2.8.2.3

 

Release v2.8.2.3: archive v2.8.2.4259f59 · naver/smarteditor2

2.8.2.3 보안 패치 file_uploader_html5.php의 null byte injection 취약점 추가 보완 file_uploader.php의 리다이렉트 취약점 보완 sample.php에서 XSS filtering을 위해 HTMLPurifier라이브러리를 적용 sample.php -> sample/viewer/in

github.com

resources > static > smarteditor 에 다운받은 파일들을 넣어주었다.

resources > static > smarteditor 붙여넣기

스마트 에디터를 출력할 페이지에 js를 import 해준다.

<script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>

포스트 추가할 곳에 textArea 설정해주자.

<div class="row">
    <!-- 포스트 추가하는 곳-->
    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
    <h3>Naver Smart Editor 2.0</h3>
    <form action="insertStudentInfoForm" method="post">
        <div id="smarteditor">
            <textarea
                name="editorTxt"
                id="editorTxt"
                rows="20"
                cols="10"
                placeholder="내용을 입력해주세요"
                style="width: 500px"></textarea>
        </div>
        <input type="button" value="내용콘솔에" onclick="submitPost()"/>
    </form>
    <!-- 포스트 추가하는 곳-->
</div>

스크립트를 설정해준다. 아까 resources > static > smarteditor 경로에 있던 SmartEditor2Skin.html 파일이 에디터 화면이다. sSkinURI 경로를 주의해서 스크립트를 작성해준다.

	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      console.log("Naver SmartEditor")
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })

 

++추가) html도 못읽길래 sever>web.xml에 추가해줬다.

    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
        <!-- 아래에 js 및 css 확장자 추가 -->
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>*.jpg</url-pattern>
        <url-pattern>*.ico</url-pattern>
        <url-pattern>*.gif</url-pattern>
        <url-pattern>*.png</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

 

저장 버튼 누르면 DB로 스마트에디터에 작성한 내용이 저장되야하므로 일단 콘솔에서 띄워본다. 버튼클릭 이벤트를 만들어서 console.log를 찍어보자.

 /* 버튼 클릭 이벤트 */
	    submitPost = function() {
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", [])
		  let content = document.getElementById("editorTxt").value
		
		  //if(content == '') {
            if(content == '<p>&nbsp;</p>') { //비어있어도 기본 P태그가 붙더라.
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return
		  } else {
		    console.log(content)
		  }
		}

결과창을 확인하면 잘 나온다.

결과

 

2. 스마트 에디터에 작성한 내용 ajax로 DB에 저장해주기

 1) jsp

일단 제목과 내용을 제대로 저장시켜주는 것이 중요하므로, 썸네일과 기타 다른 데이터들은 정적데이터로 넣어주고 연결부터 하기로 함.

   /* 버튼 클릭 이벤트 */
	    function submitPost(){

		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //console.log(content);
			 let writePost = {
	          title: $("#title")[0].value
	          ,content: content
	        }
			 
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: writePost
	          , type: 'POST'
	         , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR)
	            alert('오류가 발생하였습니다.')
	          }
	        })
		  }
		}

간단하게 writePost 라는 변수를 선언해서 여기에 title이랑 content를 key,value 형태로 담아서 보내주었다.

 2) controller

[새글쓰기] 버튼을 눌렀을 때에도 바로 jsp 페이지가 아니라 me로 끝나는 페이지로 이동하도록 설정해주자.

또한 입력한 데이터를 DB에 저장해주는 Action 페이지도 만든다.

//-------------------- 새글작성 버튼 누르면 게시글 작성 페이지로 이동 ---------------------------
else if(command.equals("/member/postInsert.me")) {
    //게시글 작성 페이지로 이동함
    forward = new ActionForward();
    //객체 생성
    forward.setRedirect(false);
    //이동 허락 안함
    forward.setPath( "./postEdit.jsp"))
    //거기 주소는 postEdit.jsp으로 해라.(이동할 주소 저장)
}
//-------------------- 작성한 블로그 포스팅을 저장하는 postInsertAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postInsertAjax")) {
    action = new PostInsertAction();
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "포스팅 저장 실패"))
    }
}

 3) Action

postInsertAjax 로 접속했을 때 이동할 PostInsertAction 페이지를 만들어준다.

/* 인터페이스를 함께 추가했다. 작성한 포스트 저장 처리를 위한 클래스이다.*/
public class PostInsertAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		PostBean post = new PostBean(); //vo에 선언한 변수들 import한거.
		//작성한 게시글 내용을 저장하고 DB로 전달.

		ActionForward forward = null;

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.
		String sessionId = (String) session.getAttribute("id");
		
		post.setMEM_ID(sessionId);
		//입력 목록 적어주기(vo에서 받아옴.)
		post.setPOST_TITLE(req.getParameter("title"));
		post.setPOST_THUMBNAIL(req.getParameter("thumbnail"));
		post.setPOST_CONTENT(req.getParameter("content"));
		
		PostInsertService postInsertService = new PostInsertService();
		boolean InsertResult = postInsertService.insertPost(post); //vo에서 받은 변수 보내줌.
		//게시글 저장이 잘 되었는지 여부
		       
		//ajax 통신이라 서버에서 안먹히나? 이 아래로 안됨...
		System.out.println(InsertResult);
		if (InsertResult) { //게시글 저장 후 내 블로그로 이동

			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter();
			out.println("<script>");
			out.println("alert('저장되었습니다.')");
			out.println("location.href='./myBlogAction.me");
			out.println("</script>");
		} else { //게시글 저장 실패 시
			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter(); //위에 있음
			out.println("<script>");
			out.println("alert('게시글 저장 실패')");
			out.println("history.back()");
			out.println("</script>");
		}
		return forward;
	}
}

4) Service

DB 연결한다.

/* 게시글을 DB에 저장하는 역할을 담당하는 클래스(Service) */
public class PostInsertService {
	
	public boolean insertPost(PostBean post) {
		
		boolean joinSuccess = false; //저장완료 기본값 false
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		int insertCount = postDAO.insertPost(post);
		// DB추가 쿼리를 메소드로 구현.

		if (insertCount > 0) {
			joinSuccess = true;
			// 정상 처리 되었음을 호출했던 곳으로 리턴.
			commit(con); //DB 명령 완료 확인
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}
		close(con);
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return joinSuccess;
	}
}

4) DAO

update를 한다. 받아온 데이터는 두개이고 나머지는 나중에 세팅해줄 것이므로 정적으로 넣어준다.

//=========================== 게시글 저장하는 SQL로직 ===============================
		// PostInsertService에서  게시글 저장할 때 DB와 JSP를 연결할 때 인자로 쓰임.
		public int insertPost(PostBean post) {
			//게시글 저장할 때 SQL문(DB 이름 확인하기***)
			String sql = 
				"INSERT INTO post_info("
						+ "post_no,"
						+ "mem_no,"
						+ "post_title,"
						+ "post_thumbnail,"
						+ "POST_VIDEO,"
						+ "POST_CONTENT,"
						+ "VISIT_CNT,"
						+ "POST_UPLOADTIME"
				+ ") "
				+ "VALUES( "
						+ "(SELECT NVL(MAX(post_no), 0) + 1 FROM post_info)," //post_no
						+ "(SELECT mem_no FROM memberinfo WHERE mem_id = ?)," //mem_no 찾기
						+ "?," //post_title
						+ "'섬네일.jpg'," //post_thumbnail
						+ "'비디오.avi',"
						+ "?," //POST_CONTENT
						+ "0," //VISIT_CNT
						+ "TRUNC(SYSDATE)" //POST_UPLOADTIME
				+ ")";
			
			int insertCount=0;

			try {
				pstmt = con.prepareStatement(sql); 
				//prepareStatement : SQL문 실행하는 기능을 갖는 객체(변수는 ?로, setString으로 아래에 지정함.)
				pstmt.setString(1, post.getMEM_ID()); //sessionId로 mem_no 찾기
				pstmt.setString(2, post.getPOST_TITLE());
				pstmt.setString(3, post.getPOST_CONTENT());

				insertCount=pstmt.executeUpdate(); //executeUpdate : 데이터베이스 변경할 때
				//select는 executeQuery()를 사용한다.
				// insert, update, delete는 executeUpdate()를 사용한다.
				//정상적으로 된다면 insertCount가 1이 된다.
				System.out.println("게시글 저장2");
			} catch (Exception ex) {
				System.out.println("게시글 저장 안됨" + ex);
				

			} finally {
				close(pstmt); // import static db.JdbcUtil.*;
			}
			return insertCount;
		}

Update 및 delete 등 데이터베이스를 변경할 때에는 executeUpdate를 사용한다.

SQL문을 뜯어보자.

a) post_no : 최댓값 +1 을 한다.

"(SELECT NVL(MAX(post_no), 0) + 1 FROM post_info)," //post_no

b) mem_no :  세션에 있는 id로 찾아서 넣어줬다.

"(SELECT mem_no FROM memberinfo WHERE mem_id = ?)," //mem_no 찾기

4) VISIT_CNT:  기본값 0이라 그냥 0 넣어줌.

5) POST_UPLOADTIME:  현재 날짜 넣어줌

"TRUNC(SYSDATE)" //POST_UPLOADTIME

DB 설정값 다시 확인


다음은 이 ajax 통신을 멀티파트로 해서 이미지도 함께 업로드 해주겠다.

 ▼ postEdit.jsp 소스 전체보기

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 참고 : https://beforb.tistory.com/53 -->
    <!-- 참고 : https://www.wrapuppro.com/programing/view/ZPLdxEBiyJG38IG -->
    <!-- 에디터 : Naver SmartEditor 2.0 -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>게시글 작성</title>
        <!-- 제이쿼리 -->
		<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js"></script>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
      <script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog - 새 게시글 작성</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                    <!-- 포스트 추가하는 곳-->
                    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
                    
				    <form action="insertStudentInfoForm" method="post">
				    	<!-- 제목 -->
				      <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px'>
				      
				      <div id="smarteditor">
				      <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠. -->
				      <input type="hidden" value="일단 썸네일임" id="thumbnail">
				      
				        <textarea name="editorTxt" id="editorTxt" 
				                  rows="20" cols="10" 
				                  placeholder="&#13;&#10;내용을 입력하세요"
				                  style='width: 600px'></textarea>
				                  
				      </div>
				      <input type="button" value ="저장" onclick="submitPost()"/>
				    </form>
                    
                
              <!-- 포스트 추가하는 곳-->
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
	     <script>
	     
	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })
	    
	    
	    /* 버튼 클릭 이벤트 */
	    function submitPost(){
	 
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
			console.log(content);
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //console.log(content);
			 let writePost = {
	          title: $("#title")[0].value
	          ,content: content
	        }
			 console.log(writePost);
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: writePost
	          , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR)
	            alert('오류가 발생하였습니다.')
	          }
	        })
	        
		  }
		}
	    
	  </script>
    </body>
</html>

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

내 블로그 페이지 만들기

이젠 반복작업이다. 저번 포스팅의 위젯을 그대로 따고, 내 블로그 페이지에는 세션에 있는 회원의 정보를 띄워주도록 하자.

[ 만들 기능 ]
1. 내 블로그 클릭하면 내가 쓴 게시글 목록이 나오게 하기
2. 게시글 목록을 클릭하면 게시글 상세페이지로 이동
3. 위젯에 내 프로필 사진과 ID를 띄움
4. 세션 ID = 게시글 쓴 사람 이면 삭제버튼이 보이게 하기
5. 게시글 삭제 기능 구현

1. 메인페이지에서 내 블로그 클릭하면 내가 쓴 게시글 목록이 나오게 하기

간단하게 링크를 걸어준다.

1) JSP

<li class="nav-item"><a class="nav-link" href="myBlogAction.me?id=${sessionScope.id}">MyBlog</a></li>

로그인한 '내' 블로그에 가는 것이므로 sessionScope.id를 사용해준다.

2) Controller

//-------------------- 내 블로그 페이지를 보여주는 myBlogAction 페이지 생성 ---------------------------
else if (command.equals("/member/myBlogAction.me")) {
    action = new MyBlogAction();
    // MyBlogAction, 내 블로그 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "내 블로그 페이지 표시"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

내 블로그에는 내가 쓴 게시글 목록을 띄울 것이다. sessionId가 있어야 내 블로그인 줄 알테니까 sessionId 생성 후 Service에 같이 던져준다.

package action;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import svc.MyPostListService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 내 블로그 표시를 위한 클래스이다.*/
public class MyBlogAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 세션에서 id값 가지고 있기.
		String sessionId = (String) session.getAttribute("id");
		ActionForward forward = null; //어디로 갈지?

			// 내 게시글 목록 및 내용 표시.
			forward = new ActionForward();
			MyPostListService myPostListService = new MyPostListService();
			//배열로 게시글 목록을 가져온다. id는 같이 던져줘야 읽음.
			ArrayList<PostBean> myPostList = myPostListService.getMyPostList(sessionId);

			req.setAttribute("myPostList", myPostList);
			
			//mainPage로 게시글 목록을 던져준다.
			forward.setPath("./myBlog.jsp");
		
		return forward;
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기

package svc;
import static db.JdbcUtil.close;
import static db.JdbcUtil.getConnection;
import java.sql.Connection;
import java.util.ArrayList;
import DAO.PostDAO;
import vo.PostBean;


/* 내 게시글를 띄울 시 DB와 JSP를 연결해주는 역할을 담당하는 클래스(Service) */
public class MyPostListService {
	
	public ArrayList<PostBean> getMyPostList(String sessionId) { //리스트이므로 배열로 가져온다.
		//커넥션 풀 : DB와 미리 연결해놓은 커넥션 객체들을 풀에 저장해뒀다가 클라에서 요청오면 객체 빌려주고 볼일 끝나면 다시 반납받아 풀에 저장하는 방식
		Connection con = getConnection();
		// DB와 연결을 얻어내고.
		PostDAO postDAO = PostDAO.getInstance();
		// DB접속용 DAO 객체 참조 얻어오기.(싱글톤 패턴 : 인스턴스가 1개만 생성됨)
		// import static db.JdbcUtil.*;
		postDAO.setConnection(con);
		// 얻어온 커넥션 저장. -> 연결해서 할일 함.
		ArrayList<PostBean> myPostList = postDAO.selectMyPostList(sessionId); //selectMyPostList는 PostDAO에서 받아온다.
		//커넥션으로 DB에서 게시글 목록 받아서 배열에 저장

		close(con);
		// 사용을 마친 커넥션 반환
		return myPostList;
		// 내 게시글 목록 리턴.
	}
}

5) DAO

내 블로그 게시글 목록을 가져온다. 목록이므로 미리보기처럼 보일 수 있게 게시글 내용은 15자 이내로 자르고, 회원 테이블에서 ID값과 pic을 가져와서 넣어준다.

//=========================== 내 블로그 게시글을 가져와 보여주는 SQL로직 ===============================
public ArrayList<PostBean > selectMyPostList(String sessionId) {
    // ArrayList: 객체 배열 비슷, 컬렉션 프로임워크
    // 여러 개의 게시글 정보를 저장한다.

    //디비에 저장된 내 게시글 목록을 확인하는 SQL문(DB 이름 확인하기***)
    String sql =  "select p.*,m.mem_id,m.mem_pic from post_info p join memberinfo m on p.mem_no = m.mem_no where m.mem_id = ?"

    ArrayList<PostBean> myPostList = new ArrayList<PostBean>();
    PostBean pb = null; //Bean(vo)은 그릇이다. 뭘 가져올지는 vo에 목록이 있다.

    try {
        pstmt = con.prepareStatement(sql);

        //세션 id값 가져와서 SQL 문에 넣어줌.
        pstmt.setString(1, sessionId);
        System.out.println( "sessionId : "+sessionId))

        rs = pstmt.executeQuery(); //executeQuery : resultSet 객체 반환
        //select는 executeQuery()를 사용한다.
        //쿼리문 처리결과 ResultSet의 객체인 rs에 저장.

        if (rs.next()) {
            //조회된 결과가 있다면 아래 문장 수행.
            do {
                //한 번 수행하고 또 수행할 게 있으면 수행.
                pb = new PostBean();
                //게시글 1개의 정보를 저장할 수 있는 PostBean 객체 생성.
                pb.setPOST_NO(rs.getInt( "POST_NO"))) //게시글번호
                pb.setMEM_NO(rs.getInt( "MEM_NO"))) //회원번호
                pb.setPOST_TITLE(rs.getString( "POST_TITLE"))) //게시글제목
                pb.setPOST_THUMBNAIL(rs.getString( "POST_THUMBNAIL"))) //게시글섬네일
                pb.setPOST_VIDEO(rs.getString( "POST_VIDEO"))) //게시글비디오					

                //--- 게시글 내용 가져와서 15자만 미리보기로 보여줌 ---
                String preStr=rs.getString( "POST_CONTENT"))
                if(preStr.length() > 30) {
                    preStr=preStr.substring(0,15)+ "..." //자르고 ... 붙이기
                };
                pb.setPOST_CONTENT(preStr); //게시글내용 미리보기
                //----------------------------------------

                pb.setVisit_cnt(rs.getInt( "Visit_cnt"))) //게시글조회수
                pb.setPOST_UPLOADTIME(rs.getString( "POST_UPLOADTIME"))) //게시글업로드타임

                //위젯에 띄울 내 회원정보
                pb.setMEM_id(rs.getString( "MEM_ID"))) //게시글 쓴 회원 id
                pb.setMEM_PIC(rs.getString( "MEM_PIC"))) //게시글 쓴 회원 사진

                //조회된 결과를 PostBean객체에 저장.
                myPostList.add(pb);
                //저장하면서 생성된 것을 이제 List에 담아냄.(ArrayList)
                //반복문이 실행될 때마다 게시글 1개씩 누적 시킴.
            }
            while (rs.next());
            //rs.next때문에 어레이 리스트 다음 값으로 넘어간다.(차례차례 읽어옴)
        }
    }
    catch (Exception ex) {
        System.out.println( "selectMyPostList 에러 : " + ex))
    }
    finally {
        close(rs);
        close(pstmt);
    }
    return myPostList;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고. 아래 두가지를 추가했다.

private String MEM_ID;
private String MEM_PIC;

결과창

2. 게시글 목록을 클릭하면 게시글 상세페이지로 이동

동일하게 상세보기 페이지로 연결해주면 된다. 그럼 상세보기 페이지이지만 위젯 내역은 똑같이 보이니 페이지가 이동하지 않은 상태로 내용만 바뀐 것처럼 보인다.

  <a class="btn btn-primary" href="postViewAction.me?postViewAction.me?postno=${myPostList.POST_NO}">Read more →</a>

 

3. 위젯에 내 프로필 사진과 ID를 띄움

1번 항목 참조.

 

나만 내 게시글 삭제하기

 

4. 세션 ID = 게시글 쓴 사람 이면 삭제버튼이 보이게 하기

요걸 버튼으로 사용해보자.

세션 ID를 어떻게 가져올까 하다가...공부하는 겸 해서 자바스크립트 단에서 display : none 처리로 간단하게 해보기로 했다.

자바스크립트에 java 코드를 사용해본다. 서버 session에 있는 id값을 가져왔다.

<div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} 
by <b id="memId">${postObj.MEM_ID}</b></div>

<!--회원id를 b태그로 감싸서 id를 부여해줬다.-->

<script>
        var sessionId = '<%=(String)session.getAttribute("id")%>';
        var post_writer = document.getElementById('memId').innerText;
        console.log(typeof(sessionId),typeof(post_writer)); //string,string
        //세션 id가 포스트작성자가 아니면, 수정버튼과 삭제버튼 안보임.
        if (sessionId != post_writer){
        	 document.getElementById("post_update").style.display ='none';
        	 document.getElementById("post_delete").style.display ='none';
        	 console.log("내 게시글 아님. 수정 불가");
        };
		
</script>

세션 아이디와 다름
세션 아이디와 같음

물론 좋은 방법은 아니다.

말 그대로 css로 감춘 것이기 때문에 스크립트에 노출되어 있으며, display : none 속성을 브라우저에서 없애주면 버튼이 보여지게 된다.

개발자 도구에서 버튼 발굴(?) 가능

하지만 이렇게 한 이유는 귀찮은 건 아니고 자바스크립트에서 세션값을 가져오는 방법을 사용해보기 위함이며, 수정 화면에 이동할 시, session에 있는 사용자가 수정권한이 있는지 컨트롤러 단에서 다시 확인할 것이기 때문이다.

컨트롤러 단에서 이걸 어떻게 받을까 고민하다가 postNo와 같이 get 방식으로 mem_id값도 보내주기로 했다.(2번 참고)

 

5. 게시글 삭제 기능 구현

삭제기능만 우선 구현해보자.

1) JSP

삭제버튼 클릭 시 해당 게시글 번호와 작성자를 쿼리스트링으로 보내줌

<a class="badge bg-secondary text-decoration-none link-light" href="postDeleteAction.me?writer=${postObj.MEM_ID}&postno=${postObj.POST_NO}" id="post_delete">삭제</a>

get 방식으로 보낼 때 파라미터에 작성자 id값도 함께 보내준다. 컨트롤러 단에서 확인할 것이기 때문!!

쿼리스트링을 보낼 때에는 &로 묶어서 보내준다.

2) Controller

//-------------------- 내 게시글을 삭제하는 postDeleteAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postDeleteAction.me")) {
    action = new PostDeleteAction();
    // PostDeleteAction, 삭제 후 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "게시글 삭제"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

로그인 상태인지, 사용자가 게시글 작성자가 맞는지를 확인한다.

브라우저 alert를 띄우기 위해 resp.getWriter()를 이용해서 println으로 스크립트를 띄워주었다.

가져온 작성자 id값과 게시글 번호를 통해 분기한다.

package action;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import svc.PostDeleteService;
import vo.ActionForward;

public class PostDeleteAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		HttpSession session = req.getSession();
		String id = (String) session.getAttribute("id");
		// 로그인하면, id는 session에 저장되어 있다.
		String post_writer = req.getParameter("writer");
		int postNo = Integer.parseInt(req.getParameter("postno"));
		System.out.println("writer"+post_writer);

		ActionForward forward = null;
		// 로그인 상태인지 확인 -> 아니면 로그인화면 고고
		if (id == null) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./memberLogin.me");
			
		// 사용자가 작성자가 맞는지 확인 -> 아니면 alert를 띄우고 해당 포스트로 다시 이동
		} else if (!id.equals(post_writer)) {
			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter();
			out.println("<script>");
			out.println("alert('작성자가 아닌 사람은 게시글을 삭제할 수 없습니다.')");
			out.println("location.href='./postViewAction.me?postno="+postNo+"'");
			out.println("</script>");		

		//사용자가 작성자면 삭제가 가능하다.
		} else {
			//Service에 삭제한 게시물 번호를 함께 넘겨준다.
			PostDeleteService postDeleteService = new PostDeleteService();
			boolean deleteResult = postDeleteService.deletePost(postNo);
			
			// delete 하면 내 블로그로 이동
			if (deleteResult) {
				resp.setContentType("text/html;charset=UTF-8");
				PrintWriter out = resp.getWriter(); 
				out.println("<script>");
				out.println("alert('삭제되었습니다.');");
				out.println("location.href='./myBlogAction.me'");
				out.println("</script>");

			// delete 실패하면 alert 발생 후 해당 포스트로 다시 이동
			} else {
				resp.setContentType("text/html;charset=UTF-8");
				PrintWriter out = resp.getWriter(); 
				out.println("<script>");
				out.println("alert('삭제 실패');");
				out.println("location.href='./postViewAction.me?postno="+postNo+"'");
				out.println("</script>");
			}
		}
		return forward;	
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기. 게시글 번호는 계속 가지고 간다.

package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.commit;
import static db.JdbcUtil.getConnection;
import static db.JdbcUtil.rollback;
import java.sql.Connection;
import DAO.PostDAO;

public class PostDeleteService {

	public boolean deletePost(int postNo) {
		boolean deleteResult = false;
		Connection con = getConnection();
		PostDAO postDAO = PostDAO.getInstance();
		postDAO.setConnection(con);
		int deleteCount = postDAO.deletePost(postNo);

		//지우면 지웠다고 결과 1 리턴 아니면 롤백
		if (deleteCount > 0) { 
			commit(con);
			deleteResult = true;
		} else {
			rollback(con);
		}
		close(con);
		return deleteResult;
	}

}

5) DAO

내 블로그 게시글 목록을 가져온다. 목록이므로 미리보기처럼 보일 수 있게 게시글 내용은 15자 이내로 자르고, 회원 테이블에서 ID값과 pic을 가져와서 넣어준다.

//=========================== 게시글을 삭제하는 SQL로직 ===============================
// PostDeleteService에서 게시글 삭제 시 DB와 JSP를 연결할 때 인자로 쓰임.
public int deletePost(int postNo) {
    //우클릭 Create method
    // 해당 게시글을 삭제하는 SQL문(DB 이름 확인하기***)
    String sql =  "delete from post_info where post_No=?"
    int deleteCount = 0; //삭제 유무

    try {
        pstmt = con.prepareStatement(sql);
        pstmt.setInt(1, postNo); //가져온 게시글 번호 보내주기
        pstmt.executeUpdate();
        deleteCount = 1;
    }
    catch(Exception ex) {
        System.out.println( "삭제 안됨"))
        deleteCount = 0;
    }
    finally {
        close(rs);
        close(pstmt);
    }
    return deleteCount;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고.

결과창 : 유저가 자기 게시글을 삭제함

css로 버튼을 감췄기 때문에 display:none을 풀어서(?) 삭제 버튼을 활성화시킬 수도 있다. 이 때 alert가 발생하면서 다시 해당 페이지로 이동한다.

결과창 : 유저가 다른사람 게시글을 삭제할 경우 alert 발생

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

+ Recent posts

반응형
LIST