작성일자 : 2021 . 08. 15 일요일

개발도구 : STS 4.1.0

 

개인 정리용으로 작성한 글이라 틀린부분이 많을 수 있습니다 ~~

 

 

SpringBoot 프로젝트의 폴더 구조

 

 

 

 

 


1.  src/main/java 

스프링 프레임워크!! 라고 하면 필수적으로 나오는게 MVC 구조인데요 그 중 Model, Controller 를 저장하는 곳입니다.

 

처음 Spring Stater Project를 생성하면 

기본 패키지명 "com.example.demo" 와 하위 java파일 "[프로젝트명]Application.java" 가 있습니다.

( TestController.java 는 제가 임의로 만든파일 )

그리고 패키지명은 보통 자기가 만들 도메인이나 회사명이나 등등을 거꾸로 쓴다고 하네요.

예를들면 com.naver.webtoon     (?)

 

 

자동으로 만들어진 자바파일 입니다~ 메인함수에 run 함수 하나만 달랑 있는 아주 간단한 파일이네요

프로젝트를 SpringBootApp 으로 실행시키면 이 함수가 호출이 되겠군요 

 

만들어진 패키지 하위에서 작업을 하셔야 위와 같은 main함수를 찾아내기 때문에 작업하실 때 번거롭지 않습니다!!

굳이 다른패키지에서 하고싶으시다면 자세한 내용은 @ComponentScan 어노테이션을 찾아보시기 바랍니다 ~

 


2. src/main/resources

서버가 실행될 때 필요한 파일들을 저장하는 공간입니다.

기본적으로 application.properties 라는 환경설정 파일이 만들어져있을텐데

properties 보다 yml 확장자의 문법이 좀 더 가독성이 좋기때문에 저는 파일이름을 변경하여 yml 로 변경한 상태입니다

 

 

 

application.properties 문서

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.server

 

 

 

더욱 공부하게 된다면 java 파일에서 환경설정에서 세팅한 값을 불러와서 사용할 수 있다고 하네요

( 예를들면 테스트서버 / 실제 운영서버의 세팅 값 )

 


3. src/main

/webapp/WEB-INF/views

MVC 구조에서 Views 를 저장하는 곳입니다.

 

Spring 에서는 jsp 파일을 사용하려면 필수로 설정 해야할게 몇 가지 있습니다.

파일경로를 줄바꿈으로 차별을 준 이유는 프로젝트를 처음 만들면 src/main 밖에 없어서 그 하위의

/webapp/WEB-INF/views 는 직접 폴더를 생성해주셔야 하기때문입니다.

 

 

왜 하필 경로는  /webapp/WEB-INF/views  고정이어야 하나요?

나중에 pom.xml 에 추가해야할 tomcat-embed-jasper 라이브러리와  application.yml 에서 추가해야할 prefix

와 관련이 있습니다 

 

pom.xml 에 추가해야할 tomcat-embed-jasper

pom.xml에 관한 내용은 마지막에 설명하겠습니다 !! 의존성관리하는 파일 이라고만 알아두세요 ~

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
		<dependency>
		    <groupId>org.apache.tomcat.embed</groupId>
		    <artifactId>tomcat-embed-jasper</artifactId>
		    <version>9.0.40</version>
		</dependency>

 

pom.xml을 변경하고 난 뒤에는 Project Clean 하는거 잊지마세요 ~

아무리해도 추가한 라이브러리가 인식이 안된다면 서버를 재시작 해보세요 !!!

 

 

application.yml 에서 추가해야할 prefix

prefix 와 suffix 는 각각 접두어, 접미어 라는 뜻을 지니고 있는데요

tomcat-embed-jasper 에서 jsp 파일에 접근하는 경로가 src/main/webapp 까지였는데

 

yml 파일에서 접두어 접미어 설정을 미리 해둔다면

여기에 접두에 /WEB-INF/views/ 가 붙게 되고 접미에 .jsp 가 붙게됩니다.

 

그런데 어디의 접두 접미 라는것일까요?

바로 Controller 에서 return 하는 View의 이름입니다.

yml 파일에 prefix , suffix 가 없다면 index2 함수처럼 view의 전체경로와 확장자를 모두 입력해줘야 합니다.

 

하지만 yml 파일에 prefix,  suffix 가 설정되어 있다면 !!!

index 함수처럼 view 의 파일이름만 반환하면 알아서 찾아가게 됩니다.

 

이 부분이 더 궁금하시다면 ViewResolver 를 찾아보시면 될 것 같습니다!

 

 

 

4. pom.xml

pom.xml 에 필요한 라이브러리를 작성하면 프로젝트 빌드시에 자동으로 다운로드받아 관리해주는 파일입니다.

맨 처음 프로젝트 생성할 때

pom.xml 파일이 없으시다면 의존성 관리타입을 Maven 으로 해주셔야 합니다 !!

이 외의 타입으로는 Gradle 이 있는데 관리방법과 사용빈도도 Maven이랑 거의 비슷해서 자신에게 맞는 관리도구를 사용하시면 됩니다.

 

그리고 자신이 원하는 라이브러리를 검색하여 즉시 추가할 수 있습니다.

제가 알고있는 스프링 개발에 반드시필요한 라이브러리는

Spring Web

Spring Web Services 이고요 ( 전반적인 스프링 기능이 다 담겨있어서 자세하게는 어떤 기능이 있는지 잘 모르겠습니다)

 

SpringBoot Dev Tools : 프로젝트의 파일 변겅후 저장을 감지하여 서버를 자동으로 재실행 해줍니다 (편의성)

 

이 외에 자주 쓰이므로 공부하면 도움이되는 라이브러리들은 

MySQL , MyBatis , OAuth2.0 , Security , JPA .. 가 있습니다 !!

 

 

 


'프로젝트 구조' 만 설명하려고 했는데 자꾸 뭐라도 더 쓰고싶다는 생각에 정보를 계속 눌러담은 느낌이 드네요 .

꾸준히 정리 해보겠습니다.

list.jsp

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" /> 
			<input type="hidden" name="page" value="0" /> 
			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>
	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: ${currentPosition}%"></div>
	</div>

위의 form 태그를 유심히 보면 action="/blog/board" 이후에

그 밑에 <input> 들의 name, value가 서버로 전송 될 것임을 알 수 있다.

 

action = "blog/board?cmd=search&page=0&keyword=[검색한단어]"

 

 

 

BoardController.java

else if(cmd.equals("search")) {
			String keyword = request.getParameter("keyword");
			int page = Integer.parseInt(request.getParameter("page"));

			List<Board> boards = boardService.글검색(keyword, page);
			request.setAttribute("boards", boards);

			int boardCount = boardService.글개수(keyword);
			int lastPage = (boardCount-1)/4; // 2/4 = 0, 3/4 = 0, 4/4 = 1, 9/4 = 2 ( 0page, 1page, 2page) 
			double currentPosition = (double)page/(lastPage)*100;



			request.setAttribute("lastPage", lastPage);
			request.setAttribute("currentPosition", currentPosition);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

cmd.equals("list")와 코드가 유사하다.

글검색() 의 결과가 저장된 boards 를 setAttribute 하여 dispatcher 를 이용해 list페이지로 이동하게 한다.

 

글검색 된 boards 도 페이지처리가 적용되어야 하기 때문에 글개수(keyword) 함수를 오버로딩하여 새로 만들어야 한다.

 

 

BoardService.java

public List<Board> 글검색(String keyword, int page){
		return boardDao.findByKeyword(keyword, page);
	}

public int 글개수(String keyword) {
		return boardDao.count(keyword);
	}

 

BoardDao.java

public List<Board> findByKeyword(String keyword, int page){
		String sql = "SELECT * FROM  board WHERE title like ? ORDER BY id DESC LIMIT ?, 4"; // 0,4   4,4   8,4
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%"+keyword+"%");
			pstmt.setInt(2, page*4); // 0 -> 0, 1 ->4, 2->8
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}

		return null;
	}
    
    
    public int count(String keyword) {
		String sql = "SELECT count(*) FROM board WHERE title like ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%"+keyword+"%");
			rs =  pstmt.executeQuery();
			if(rs.next()) {
				return rs.getInt(1);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return -1;
	}

BoardDao.list 와 코드가 유사하다.

 

 

 

list.jsp

<!-- disabled -->
	<ul class="pagination justify-content-center">

		<c:choose>
			<c:when test="${empty param.keyword}">
				<c:set var ="pagePrev" value="/blog/board?cmd=list&page=${param.page-1 }"></c:set>
				<c:set var="pageNext" value="/blog/board?cmd=list&page=${param.page+1}"/>
			</c:when>
			<c:otherwise>
				<c:set var="pagePrev" value="/blog/board?cmd=search&page=${param.page-1}&keyword=${param.keyword}"/>
				<c:set var="pageNext" value="/blog/board?cmd=search&page=${param.page+1}&keyword=${param.keyword}"/>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${param.page == 0}">
				<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>	
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="${pageScope.pagePrev}">Previous</a></li>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${lastPage == param.page}">
				<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>		
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="${pageScope.pageNext}">Next</a></li>
			</c:otherwise>
		</c:choose>

		
	</ul>
</div>
</body>
</html>

페이지 버튼도 동적으로 바뀌게 한다. 이 부분이 가장 머리를 많이 쓰는 부분이긴 한데...

ㅠㅠ

detail.jsp

<div class="m-2">
	<c:if test="${sessionScope.principal.id == reply.userId }">
		<i onclick="deleteReply(${reply.id})" class="material-icons">delete</i>
	</c:if>
    </div>
</li>
</c:forEach>

delete 버튼이 나에게만 보이도록 c:if 문으로 userId를 비교함

 

 

boardDetail.js

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	alert("댓글 아이디 : "+id);
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=delete&id="+id,
		dataType : "json"
	}).done(function(result) { //  { "statusCode" : 1 }
		if (result.statusCode == 1) {
			console.log(result);
			$("#reply-"+id).remove();
		} else {
			alert("댓글삭제 실패");
		}
	});
}


post 요청을 함, body 데이터는 필요없어서 data, ContentType 은 작성하지 않았음

 

 

 

 

ReplyDao.java

public int deleteById(int id) {
		String sql = "DELETE FROM reply WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;

		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

 

ReplyService.java

public int 댓글삭제(int id) {
		return replyDao.deleteById(id);
	}

 

 

ReplyController.java

else if(cmd.equals("delete")) {
			int id = Integer.parseInt(request.getParameter("id"));
			int result = replyService.댓글삭제(id);

			CommonRespDto commonDto = new CommonRespDto<>();
			commonDto.setStatusCode(result);  //1, -1

			Gson gson = new Gson();
			String jsonData = gson.toJson(commonDto);
			// { "statusCode" : 1 }
			Script.responseData(response, jsonData);
		}

쿼리스트링에 있는 id값을 id변수에 저장한다.

replyService.댓글삭제 의 return 값을 result 변수에 저장한다.

CommonRespDto<T>{

  int statusCode

  int T data

}

CommonRespDto 객체를 하나 만든다

새로만든 객체의 statusCode 의 값을 result로 set

data는 null 일 것이다.

 

객체를 toJson( Json하고싶은 JAVA Object ) 해서 응답한다. -> { "statusCode" : 1 }

( ajax deleteById 에게 jsonData를 result 로 응답한다. ) (항상 JSON으로 통신해야 함)

boardDetail.js - deleteById()

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	//alert("댓글 아이디 : "+id);
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=delete&id="+id,
		dataType : "json"
	}).done(function(result) { //  { "statusCode" : 1 }
		if (result.statusCode == 1) {
			console.log(result);
			$("#reply-"+id).remove();
		} else {
			alert("댓글삭제 실패");
		}
	});
}

Script.responseData()

public static void responseData(HttpServletResponse response, String jsonData) {

		PrintWriter out;
		try {
			out = response.getWriter();
			out.print(jsonData);
			out.flush(); // 버퍼 비우기
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

 

detail.jsp

<!-- 댓글 리스트 시작-->
	<ul id="reply__list" class="media-list">

		<c:forEach var="reply" items="${replys}">
			<!-- 댓글 아이템 -->
			<li id="reply-${reply.id}" class="media">
				<div class="media-body">
					<strong class="text-primary">${reply.userId}</strong>
					<p>${reply.content}</p>
				</div>
				<div class="m-2">
					<i onclick="deleteReply(${reply.id})" class="material-icons">delete</i>

				</div>
			</li>

		</c:forEach>


	</ul>
<!-- 댓글 리스트 끝-->

forEach 문으로 댓글들을 불러옵니다.

 

 

 

boardDetail.js

function replySave(userId, boardId) {
	var data = {
		userId : userId,
		boardId : boardId,
		content : $("#content").val()
	}
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=save",
		data : JSON.stringify(data),
		contentType : "application/json; charset=utf-8",
		dataType : "json"
	}).done(function(result) {
		if (result.statusCode == 1) {
			console.log(result);

			addReply(result.data);
			location.reload();
		} else {
			alert("댓글쓰기 실패");
		}
	});
}

location.reload(); 한 줄 추가

 

 

 

ReplyDao.java

public List<Reply> findAll(int boardId){
		String sql = "SELECT * FROM reply WHERE boardId = ? ORDER BY id DESC";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		List<Reply> replys = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, boardId);
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Reply reply = new Reply();
				reply.setId(rs.getInt("id"));
				reply.setUserId(rs.getInt("userId"));
				reply.setBoardId(rs.getInt("boardId"));
				reply.setContent(rs.getString("content"));
				replys.add(reply);
			}
			return replys;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}

 

 

 

ReplyService.java

public List<Reply> 글목록보기(int boardId){
		return replyDao.findAll(boardId);
	}

 

 

 

BoardController.java

protected void doProcess(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String cmd = request.getParameter("cmd");
		BoardrService boardService = new BoardrService();
        
		ReplyService replyService = new ReplyService();
        
		// http://localhost:8080/blog/board?cmd=saveForm
		HttpSession session = request.getSession();

doProcess 상단에 ReplyService 선언

 

else if(cmd.equals("detail")) {
			int id = Integer.parseInt(request.getParameter("id"));

			DetailRespDto dto = boardService.글상세보기(id); // board테이블+user테이블 = 조인된 데이터!!
			List<Reply> replys = replyService.글목록보기(id);

			if(dto == null) {
				Script.back(response, "상세보기에 실패하였습니다");
			}else {
				request.setAttribute("dto", dto);
				request.setAttribute("replys", replys);
				//System.out.println("DetailRespDto : "+dto);
				RequestDispatcher dis = request.getRequestDispatcher("board/detail.jsp");
				dis.forward(request, response);
			}
		}

detail (게시글 상세보기) 호출할 때 댓글 목록도 함께 가져오게하기

setAttribute replys 하여서 detail.jsp에서  jstl 사용할 수 있게하기

완전히 이해하려면 영상을 봐야할듯 합니다..

www.youtube.com/watch?v=9zJSkdY10oE&list=PL93mKxaRDidHvJs0PvxcZnUCrUYQZSzBT&index=30

 

detail.jsp

<!-- 댓글 박스 -->
  <div class="row bootstrap snippets">
    <div class="col-md-12">
      <div class="comment-wrapper">
        <div class="panel panel-info">
          <div class="panel-heading m-2"><b>Comment</b></div>
            <div class="panel-body">
              <input type="hidden" name="userId" value="${sessionScope.principal.id}" />
              <input type="hidden" name="boardId" value="${dto.id}" />
              <textarea id="content" id="reply__write__form" class="form-control" placeholder="write a comment..." rows="2"></textarea>
              <br>

              <button onClick="replySave(${sessionScope.principal.id}, ${dto.id})" class="btn btn-primary pull-right">댓글쓰기</button>

              <script>
							

		function replySave(userId, boardId){

                var data = {
                  userId: userId,
                  boardId: boardId,
                  content: $("#content").val()
                }
                $.ajax({
                  type: "post",
                  url: "/blog/reply?cmd=save",
                  data: JSON.stringify(data),
                  contentType: "application/json; charset=utf-8",
                  dataType: "json"
                }).done(function(result){
                  if(result.statusCode == 1){
                  $("#reply__list").prepend("<div>"+data.content+"</div>")
                }else{
                  alert("댓글쓰기 실패");
                }
               	 });
                }


                </script>
              <div class="clearfix"></div>
              <hr />

<!-- 댓글 리스트 시작-->

ajax를 사용하면 나중에 Controller에서 BufferedReader 로 읽을 수 있습니다.

 

 

 

ReplyDao.java

public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO reply(userId, boardId, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
        ResultSet rs = null;
		int generateKey;
		try {
			pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setInt(2, dto.getBoardId());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			
            rs = pstmt.getGeneratedKeys();
			if(rs.next()) {
				generateKey = rs.getInt(1);
				System.out.println("생성된 키(ID) : "+generateKey);
				if(result == 1) {
					return generateKey;	
				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
    
    public Reply findById(int id){
		String sql = "SELECT * FROM reply WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			rs =  pstmt.executeQuery();

			// Persistence API
			if(rs.next()) { // 커서를 이동하는 함수
				Reply reply = new Reply();
				reply.setId(rs.getInt("id"));
				reply.setUserId(rs.getInt("userId"));
				reply.setBoardId(rs.getInt("boardId"));
				reply.setContent(rs.getString("content"));
				return reply;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}

sql 입니다.

 

 

SaveReqDto.java

import lombok.Data;

@Data
public class SaveReqDto {
	private int userId;
	private int boardId;
	private String content;
}

글저장에 필요한 데이터 전송 객체

 

 

ReplyService.java

import com.cos.blog.domain.reply.ReplyDao;
import com.cos.blog.domain.reply.dto.SaveReqDto;

public class ReplyService {

	private ReplyDao replyDao;

	public ReplyService() {
		replyDao = new ReplyDao();
	}

	public int 댓글쓰기(SaveReqDto dto) {
		return replyDao.save(dto);
	}
    
    public Reply 댓글찾기(int id) {
		return replyDao.findById(id);
	}
}

 

 

 

 

ReplyController.java (서블릿)

@WebServlet("/reply")
public class ReplyController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    public ReplyController() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String cmd = request.getParameter("cmd");
		ReplyService replyService = new ReplyService();
		// http://localhost:8080/blog/reply?cmd=save
		HttpSession session = request.getSession();

		if (cmd.equals("save")) {
			// ajax 요청으로 얻은 data를 Buffer로 받음
			BufferedReader br = request.getReader();
			String reqData = br.readLine();
			Gson gson = new Gson();
			SaveReqDto dto = gson.fromJson(reqData, SaveReqDto.class);
			System.out.println("dto : "+dto);

			CommonRespDto<Reply> commonRespDto = new CommonRespDto<>();
			Reply reply = null;
			int result = replyService.댓글쓰기(dto);
			if(result != -1) {
				reply = replyService.댓글찾기(result);
				commonRespDto.setStatusCode(1); //1, -1
				commonRespDto.setData(reply);
			}else {
				commonRespDto.setStatusCode(-1); //1, -1
			}
            
			String responseData = gson.toJson(commonRespDto); 
			System.out.println("responseData : "+responseData);
			Script.responseData(response, responseData);
		}
	}

}
function replySave(userId, boardId){

                var data = {
                  userId: userId,
                  boardId: boardId,
                  content: $("#content").val()
                }
                $.ajax({
                  type: "post",
                  url: "/blog/reply?cmd=save",
                  data: JSON.stringify(data),
                  contentType: "application/json; charset=utf-8",
                  dataType: "json"
                }).done(function(result){
                  if(result.statusCode == 1){
                  $("#reply__list").prepend("<div>"+data.content+"</div>")
                }else{
                  alert("댓글쓰기 실패");
                }
               	 });
                }

댓글쓰기 ajax가 호출되면 (cmd=save) 

BurrferedReader br에 ajax로 요청하면서 넘기는 값 { userId: ooo, boardId: ooo, content: #("content").val() }을

String 으로 reqData 변수 에 저장합니다

reqData = " { userId: ooo, boardId: ooo, content: #("content").val() } "

 

방금 만든 SaveReqDto 객체에 String reqData 를 gson.fromJson( json형태의 String , 변환할 객체.class ) 을 이용해서

JSON 데이터를 JAVA Object 화 시킵니다. ( 자바코드로 써먹을수가 있다. )

 

if-else : setStatusCode, setData해서 commonRespDto를 만든다.

 

Script.responseData() 를 써서 다시 gson.toJson( Json으로 변환하고싶은 JAVA Object ) 해서 응답한다.

(JAVA Object를 JSON으로 변환 시킵니다.)

 

 

 

 

 

Script.java

public static void responseData(HttpServletResponse response, String jsonData) {

		PrintWriter out;
		try {
			out = response.getWriter();
			out.print(jsonData);
			out.flush(); // 버퍼 비우기
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

.jsp 를 응답하는게아니라 data를 응답하기위해서 만든 함수입니다.

ReplyController 에서 사용하기위해 만들었습니다.

 

jsonData를 받아서 그냥 그대로 다시 PrintWriter 해서 응답해줍니다.

 

 

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

detail.jsp

<script> ajax 부분을 모두 지우고, 따로 외부 js파일로 만들것입니다.

 

<script src="/blog/js/boardDetail.js"></script>

 

boardDetail.js

function addReply(data){

	var replyItem = `<li id="reply-${data.id}" class="media">`;
	replyItem += `<div class="media-body">`;
	replyItem += `<strong class="text-primary">${data.userId}</strong>`;
	replyItem += `<p>${data.content}.</p></div>`;
	replyItem += `<div class="m-2">`;

	replyItem += `<i onclick="deleteReply(${data.id})" class="material-icons">delete</i></div></li>`;

	$("#reply__list").prepend(replyItem);
}

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	alert("댓글 아이디 : "+id);
}

function replySave(userId, boardId) {

	var data = {
		userId : userId,
		boardId : boardId,
		content : $("#content").val()
	}

	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=save",
		data : JSON.stringify(data),
		contentType : "application/json; charset=utf-8",
		dataType : "json"
	}).done(function(result) {
		if (result.statusCode == 1) {
			console.log(result);
			addReply(result.data);
			$("#content").val("");
		} else {
			alert("댓글쓰기 실패");
		}
	});
}


function deleteById(boardId){

	$.ajax({
		type: "post",
		url: "/blog/board?cmd=delete&id="+boardId,
		dataType: "json"
	}).done(function(result){
		console.log(result);
		if(result.statusCode == 1){
			location.href="index.jsp";
		}else{
			alert("삭제에 실패하였습니다.");
		}
	});
} 

댓글 삭제는 아직 미구현.

댓글이 prepend 되는것을  addReply 함수로 만들어서 적용시킴.

ㅎㅇ

 

 

detail.jsp

삭제버튼 옆에 수정버튼을 만들어줍시다

<div class="container">
	<c:if test="${sessionScope.principal.id == dto.userId}">
		<a href="/blog/board?cmd=updateForm&id=${dto.id}" class="btn btn-warning" >수정</a>
		<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>
	</c:if>

 

 

 

updateForm.jsp

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<!-- 해당 페이지로 직접 URL(자원에 직접 파일.확장자) 접근을 하게 되면 또 파일 내부에서 세션 체크를 해야함. -->
<!-- 필터에 .jsp로 접근하는 모든 접근을 막아버리면 됨. -->

<div class="container">
	<form action="/blog/board?cmd=update" method="POST">
		<input type="hidden" name="id" value="${dto.id}" />
		<div class="form-group">
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title"  value="${dto.title}" />
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content">
				${dto.content}
			</textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 수정</button>
	</form>
</div>

  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

saveForm 복붙해서 만든겁니다. 

title 과 content에 작성했던 내용들이 있어야하니까 value = ${dto.title} 이렇게 넣어줬습니다

나중에 컨트롤러에서 setAttribute 해서 키값 dto로 줘야겠지요??

 

 

BoardDao.java

public int update(UpdateReqDto dto) {
		String sql = "UPDATE board SET title = ?, content = ? WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getTitle());
			pstmt.setString(2, dto.getContent());
			pstmt.setInt(3, dto.getId());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

무수한 SQL문의 반복

 

 

UpdateReqDto.java

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class UpdateReqDto {
	private int id;
	private String title;
	private String content;
}

글 수정에만 필요한 데이터 전송객체

 

 

 

BoardService.java

public int 글수정(UpdateReqDto dto) {
		return boardDao.update(dto);
	}

 

 

 

 

 

BoardController.java

else if(cmd.equals("updateForm")) {
			int id = Integer.parseInt(request.getParameter("id"));
			DetailRespDto dto = boardService.글상세보기(id);
			request.setAttribute("dto", dto);
			RequestDispatcher dis = request.getRequestDispatcher("board/updateForm.jsp");
			dis.forward(request, response);
		}else if(cmd.equals("update")) {
			int id = Integer.parseInt(request.getParameter("id"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			UpdateReqDto dto = new UpdateReqDto();
			dto.setId(id);
			dto.setTitle(title);
			dto.setContent(content);

			int result = boardService.글수정(dto);

			if(result == 1) {
				// 고민해보세요. 왜 RequestDispatcher 안썻는지... 한번 써보세요. detail.jsp 호출
				response.sendRedirect("/blog/board?cmd=detail&id="+id);
			}else {
				Script.back(response,"글 수정에 실패하였습니다.");
			}
		}

updateForm 은 글 수정하기 페이지를 들어갔을 때 ,

update는 글 수정 완료 버튼을 눌렀을 때.

 

 

결과

 

 

하이요

 

 

detail.jsp

detail.jsp 상단에 삭제 버튼을 만들어 줍니다.

그리고 자바스크립트, 제이쿼리를 이용한 ajax 문법입니다.

 

이전의 게시물에 ajax 정의를 했었지만 또 하겠습니다.

ㅡㅡㅡㅡAjax 통신 정의ㅡㅡㅡㅡㅡ

Ajax (Async Javascript And XML)는 웹 페이지에서 새로운 데이터를 보여주려고 할 때 웹페이지 전체를 새로고침 하지 않고, 보여주고자 하는 데이터가 포함된 페이지의 일부 만을 로드 하기 위한 기법입니다.

Ajax는 비동기 처리 모델 (또는 non-blocking 이라고도 함)을 사용하여 데이터를 처리합니다.

 

동기 처리 모델에서 브라우저는 자바스크립트 코드를 만나면 스크립트를 처리하기 전까지 다른 작업을 일시 중지하고, 자바스크립트 코드의 처리가 끝난 후 기존 작업을 진행합니다.

반면에 Ajax를 사용하면 브라우저는 서버에 데이터를 요청한 뒤 페이지의 나머지를 로드하고 페이지와 사용자의 상호작용을 처리합니다.

 

Ajax 동작방식

 

  • 요청(request) - 브라우저가 서버에 정보를 요청한다.
    type : 요청방식 (get, post, delete, put)   //  url : 요청주소 ( Controller 에서 Servlet이 처리함 )

    data : 서버로 보낼 데이터. 

    contentType : 응답받을 데이터의 형태, 보통은 데이터를 처리하기위해 JSON 이겠지만 
    Controller의 결과값을 보면 ok , fail 의 단순 문자열이기때문에 text

    응답에 성공을 한다면, done(function(data){  '대충 실행될 명령'   } ) 분기로 가서
    응답받은 데이터( data << Ajax 함수의 data 아님. Ajax의 결과값임 )로 해야할 일을 처리합니다.

  • 서버의 동작 - 서버는 JSON, XML 등의 형식으로 데이터를 전달한다.
    최근에는 JSON을 가장 많이 사용하고 있습니다.

  • 응답(response) - 브라우저에서 이벤트가 발생하여 콘텐츠를 처리한다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

더보기

<c:if test="${sessionScope.principal.id == dto.userId}">

<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>

</c:if>

 

<script>

function deleteById(boardId){

$.ajax({

type: "post",

url: "/blog/board?cmd=delete&id="+boardId,

dataType: "json"

}).done(function(result){

console.log(result);

if(result.statusCode == 1){

location.href="index.jsp";

}else{

alert("삭제에 실패하였습니다.");

}

});

}

</script>

 

<c:if test="${sessionScope.principal.id == dto.userId}">
		<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>
	</c:if>

	<script>
		function deleteById(boardId){
			$.ajax({
				type: "post",
				url: "/blog/board?cmd=delete&id="+boardId,
				dataType: "json"
			}).done(function(result){
				console.log(result);
				if(result.statusCode == 1){
					location.href="index.jsp";
				}else{
					alert("삭제에 실패하였습니다.");
				}
			});
		}
	</script>

 

 

 

BoardDao.java

실제로 글이 지워지도록 sql작성

public int deleteById(int id) { // 회원가입
		String sql = "DELETE FROM board WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

 

BoardService.java

public int 글삭제(int id) {
		return boardDao.deleteById(id);
	}

 

 

CommonRespDto.java

모든 요청,응답을 이 통합Dto 하나로 관리합니다.

제네릭을 사용하네요.

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class CommonRespDto<T> {
	private int statusCode; // 1, -1
	private T data;
}

 

 

BoardController.java

else if(cmd.equals("delete")) {
			int id = Integer.parseInt(request.getParameter("id"));

			// DB에서 id값으로 글 삭제
			int result = boardService.글삭제(id);

			// 응답할 json 데이터를 생성
			CommonRespDto<String> commonRespDto = new CommonRespDto<>();
			commonRespDto.setStatusCode(result);
			commonRespDto.setData("성공");

			Gson gson = new Gson();
			String respData = gson.toJson(commonRespDto);
			System.out.println("respData : "+respData);
			PrintWriter out = response.getWriter();
			out.print(respData);
			out.flush();
		}

 

CommonRespDto 는 알아두면 편리한데 제가 아직 개념정리가 덜되어있어서 설명이 어렵습니다.

gston.toJson( JSON으로 변환하고싶은 객체 )

 

 

 

 

결과

 

자신이 작성한 글에만 버튼이 보임.

ㅎㅇ

 

 

detail.jsp

${dto} 는 나중에 Controller 에서 setAttribute 해서 주는 key 값입니다.

댓글창은 아직 구현하지 않고 임시로 만들어놓습니다.

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

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<br />
	<br />
	<h6 class="m-2">
		작성자 : <i>${dto.username}</i> 조회수 : <i>${dto.readCount}</i> 글 번호 : <i>${dto.id}</i>
	</h6>
	<br />
	<h3 class="m-2">
		<b>${dto.title}</b>
	</h3>
	<hr />
	<div class="form-group">
		<div class="m-2">${dto.content}</div>
	</div>

	<hr />

	<!-- 댓글 박스 -->
	<div class="row bootstrap snippets">
		<div class="col-md-12">
			<div class="comment-wrapper">
				<div class="panel panel-info">
					<div class="panel-heading m-2"><b>Comment</b></div>
					<div class="panel-body">
						<textarea id="reply__write__form" class="form-control" placeholder="write a comment..." rows="2"></textarea>
						<br>
						<button onclick="#" class="btn btn-primary pull-right">댓글쓰기</button>
						<div class="clearfix"></div>
						<hr />

						<!-- 댓글 리스트 시작-->
						<ul id="reply__list" class="media-list">

								<!-- 댓글 아이템 -->
								<li id="reply-1" class="media">		
									<div class="media-body">
										<strong class="text-primary">홍길동</strong>
										<p>
											댓글입니다.
										</p>
									</div>
									<div class="m-2">

										<i onclick="#" class="material-icons">delete</i>

									</div>
								</li>

						</ul>
						<!-- 댓글 리스트 끝-->
					</div>
				</div>
			</div>

		</div>
	</div>
	<!-- 댓글 박스 끝 -->
</div>

</body>
</html>

 

 

 

header.jsp

? 뭐에 쓰이는지 모르겠네요

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

 

 

 

 

DetailRespDto.java

글 상세보기 기능의 데이터운반을 도와주는 객체

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class DetailRespDto {
	private int id;
	private String title;
	private String content;
	private int readCount;
	private String username;
	private int userId;
    
	public String getTitle() {
		return title.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
	}
}

 

 

 

BoardDao.java

findById(int id)

글 상세보기를 하기 위해서 Board 와 User를 join 한 결과에서 데이터를 가져옵니다.

왜냐하면 글에 대한 정보도 필요하고, 유저(작성자) 에 대한 정보도 필요하기 때문입니다.

 

updateReadCount(int id)

조회수를 1 증가시켜서 업데이트합니다.

public DetailRespDto findById(int id){
		StringBuffer sb = new StringBuffer();
		sb.append("select b.id, b.title, b.content, b.readCount, b.userId, u.username ");
		sb.append("from board b inner join user u ");
		sb.append("on b.userId = u.id ");
		sb.append("where b.id = ?");

		String sql = sb.toString();
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			rs =  pstmt.executeQuery();

			// Persistence API
			if(rs.next()) { // 커서를 이동하는 함수
				DetailRespDto dto = new DetailRespDto();
				dto.setId(rs.getInt("b.id"));
				dto.setTitle(rs.getString("b.title"));
				dto.setContent(rs.getString("b.content"));
				dto.setReadCount(rs.getInt("b.readCount"));
                dto.setUserId(rs.getInt("b.userId"));
				dto.setUsername(rs.getString("u.username"));
				return dto;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}
    
    
    
    
    public int updateReadCount(int id) { // 회원가입
		String sql = "UPDATE board SET readCount = readCount+1 WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

BoardService.java

// 하나의 서비스안에 여러가지 DB관련 로직이 섞여 있죠.
	public DetailRespDto 글상세보기(int id) {
		// 조회수 업데이트치기
		int result = boardDao.updateReadCount(id);
		if(result == 1) {
			return boardDao.findById(id);
		}else {
			return null;
		}
	}

 

 

BoardController.java

list.jsp에서 상세보기 버튼을 누르게되면 cmd=detail 이 호출됩니다.

//list.jsp

<c:forEach var="board" items="${boards}">
		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title}</h4>
				<a href="/blog/board?cmd=detail&id=${board.id}" class="btn btn-primary">상세보기</a>
			</div>
			<div>글 번호 : ${board.id }</div>
		</div>
	</c:forEach>

id값을 getParameter로 가져와서 자바코드에서 사용합니다.

글 상세보기(id)로 가져온 DetailRespDto 객체를 setAttribute 하여 dto 키값으로 jsp파일로 넘깁니다.

 

이 작업을 했기 때문에 detail.jsp 에서 ${dto} 를 사용할 수 있는것입니다.

else if(cmd.equals("detail")) {
			int id = Integer.parseInt(request.getParameter("id"));
			DetailRespDto dto = boardService.글상세보기(id); // board테이블+user테이블 = 조인된 데이터!!
			if(dto == null) {
				Script.back(response, "상세보기에 실패하였습니다");
			}else {
				request.setAttribute("dto", dto);
				//System.out.println("DetailRespDto : "+dto);
				RequestDispatcher dis = request.getRequestDispatcher("board/detail.jsp");
				dis.forward(request, response);
			}
		}

 

 

 

 

 

 

글 상세보기 결과

 

ㅎㅇㅎㅇㅎㅇ ㅠㅠ

 

 

 

list.jsp

jstl 문법의 choose - when / otherwise 를 이용해서 페이지 버튼의 disable 구현을 해보겠습니다.

currentPosition 과 lastpage 를 연산하는 코드는 아래에 있습니다 .

<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: ${currentPosition}%"></div>
	</div>

	<!-- JSTL foreach문을 써서 뿌리세요. el표현식과 함께 -->
@@ -34,8 +34,25 @@
	<br />
	<!-- disabled -->
	<ul class="pagination justify-content-center">
		<c:choose>
			<c:when test="${param.page == 0}">
				<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>	
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page-1}">Previous</a></li>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${lastPage == param.page}">
				<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>		
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page+1}">Next</a></li>
			</c:otherwise>
		</c:choose>


	</ul>
</div>

 

 

BoardDao.java

board의 레코드 개수 (count 함수) 를 가져오는 함수입니다.

public int count() {
		String sql = "SELECT count(*), id FROM board";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		try {
			pstmt = conn.prepareStatement(sql);
			rs =  pstmt.executeQuery();
			if(rs.next()) {
				return rs.getInt(1);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return -1;
	}

 

 

BoardService.java

서비스 추가~

public int 글개수() {
		return boardDao.count();
	}

 

 

BoardController.java

list가 호출될때마다 lastpage 와 currentPosition 을 연산해줍니다. 

jsp에서도 사용할 수 있도록 setAttribute를 이용하여 키값과 데이터를 넘겨줍니다.

else if (cmd.equals("list")) {
			int page = Integer.parseInt(request.getParameter("page"));  // 최초 : 0, Next : 1, Next: 2
			List<Board> boards = boardService.글목록보기(page);
			request.setAttribute("boards", boards);

			// 계산 (전체 데이터수랑 한페이지몇개 - 총 몇페이지 나와야되는 계산) 3page라면 page의 맥스값은 2
			// page == 2가 되는 순간  isEnd = true
			// request.setAttribute("isEnd", true);
			int boardCount = boardService.글개수();
			int lastPage = (boardCount-1)/4; // 2/4 = 0, 3/4 = 0, 4/4 = 1, 9/4 = 2 ( 0page, 1page, 2page) 
			double currentPosition = (double)page/(lastPage)*100;

			request.setAttribute("lastPage", lastPage);
			request.setAttribute("currentPosition", currentPosition);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

 

 

 

3페이지 (3/4 페이지)

 

 

 

 

 

 

4페이지(마지막페이지)

 

ㅎㅇㅎㅇ

 

List.jsp

페이지 하단에 previous , next 를 완성시켜줍니다.

버튼을 누르면 page값을 변동하여 이동하게 되는거같네요

 

jstl 문법 : ${param.변수명} 으로 변수를 바로 사용할 수 있다.

( 이렇게 말해야 되는게 맞는가 ? )

<ul class="pagination justify-content-center">
		<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page-1}">Previous</a></li>
		<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page+1}">Next</a></li>
	</ul>

 

Index.jsp

인덱스 페이지도 페이지 관리를 해주고.

<%
	RequestDispatcher dis = 
		request.getRequestDispatcher("board?cmd=list&page=0"); 
	dis.forward(request, response); // 톰켓이 생성하는 request와 response를 재사용한다. 다시 접근하는게 아니라 내부적으로 움직인다는 뜻.
%> 

 

 

BoardDao.java

LIMIT 을 추가해서 한 페이지에 게시글 4개만 불러와지도록 findAll 함수를 수정하겠습니다.

page 파라미터가 생겼어요.

public List<Board> findAll(int page){
		String sql = "SELECT * FROM  board ORDER BY id DESC LIMIT ?, 4"; // 0,4   4,4   8,4
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, page*4); // 0 -> 0, 1 ->4, 2->8
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		
		return null;
	}

BoardService.java

page 파라미터가 생겼어요.

public List<Board> 글목록보기(int page){
		return boardDao.findAll(page);
	}

 

BoardController.java

page 파라미터가 생겼어요.

cmd=list 에서 page를 함께 받는것이기 때문에 이렇게 수정했어요.

else if (cmd.equals("list")) {
			int page = Integer.parseInt(request.getParameter("page"));  // 최초 : 0, Next : 1, Next: 2
			List<Board> boards = boardService.글목록보기(page);
			request.setAttribute("boards", boards);

			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

 

 

 

첫 페이지

 

 

마지막페이지

 

저는 미리 완성된 블로그로 사진을 캡쳐하고있어서, 양 끝 페이지에 버튼이 disable 되었습니다.

다음 게시물에서 소개해드리겠습니다.

ㅎㅇ

일단 티스토리 글쓰기 << ㅈㄴ 이상합니다 .

원래 지원하던 다음블로그 ? 에디터가 좋았는데 Flash 가 2021년에 지원중단되면서 티스토리가 지원하는 신 에디터로 글을 써야하거든요

신 에디터 사진넣는거, 이건 좋ㄷ ㅏ이말이야. 근데 글자 폰트조절을 못하겠다는거 >> 화난다 .

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

글 목록 보기를 구현해봅시다.

'글 목록' 을 볼려면 글이 저장되어있는 데이터베이스를 Select 해야할것입니다.

데이터베이스가 어떻게 생겼었는지 다시 면상이나 한 번 보죠 ~

사실 제가 글쓰는 텀이 너무 길어서 까먹었습니다.

 

MySQL

 

create user 'bloguser'@'%' identified by '비밀번호';
GRANT ALL PRIVILEGES ON *.* TO 'bloguser'@'%';
create database blog; 
use blog;


drop table user;
drop table board;
drop table reply;

CREATE TABLE user(
	id int primary key auto_increment,
    username varchar(100) not null unique,
    password varchar(100) not null,
    email varchar(100) not null,
    address varchar(100),
    userRole varchar(20),
    createDate timestamp
) ;

CREATE TABLE board(
	id int primary key auto_increment,
    userId int,
    title varchar(100) not null,
    content longtext,
    readCount int default 0,
    createDate timestamp,
    foreign key (userId) references user (id)
);

CREATE TABLE reply(
	id int primary key auto_increment,
    userId int,
    boardId int,
    content varchar(300) not null,
    createDate timestamp,
    foreign key (userId) references user (id) on delete set null,
    foreign key (boardId) references board (id) on delete cascade
);

우리가 봐야할 데이터는 Board .

 

 

BoardDao.java

public List<Board> findAll(){
		String sql = "SELECT * FROM  board ORDER BY id DESC";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}

		return null;
	}

BoardDao에 findAll() 함수 추가.

DB에서 Board를 SELECT 해서 싸ㅡ악 가져오네요

 

BoardService.java

public List<Board> 글목록보기(){
		return boardDao.findAll();
	}

BoardService에 글목록보기() 함수 추가. ( List 를 반환합니다 ~ )

 

BoardController.java

else if (cmd.equals("list")) {
			List<Board> boards = boardService.글목록보기();
			request.setAttribute("boards", boards);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

BoardController에 cmd.equlas("list") 코드 추가.

글목록보기 함수의 결과값을 boards  List변수에 담습니다.

jsp에서 boards 데이터를 쓸 수 있도록 하기위해서 request.setAttribute(key , value) 를 해줍니다.

 

 

 

List.jsp

 저 부분에 DB에서 가져온 Board 데이터를 뿌려보겠습니다. 

 

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" />
			<input type="hidden" name="page" value="0" />

			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">			
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>

	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: 70%"></div>
	</div>

		<!-- JSTL foreach문을 써서 뿌리세요. el표현식과 함께 -->

	<c:forEach var="board" items="${boards}">
		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title}</h4>
				<a href="/blog/board?cmd=detail&id=${board.id}" class="btn btn-primary">상세보기</a>
			</div>
            <div>글 번호 : ${board.id }</div>
		</div>
	</c:forEach>

	<br />
	<ul class="pagination justify-content-center">
		<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
		<li class="page-item"><a class="page-link" href="#">Next</a></li>
	</ul>
</div>

</body>
</html>

Controller 에서 jsp에서 사용하기위해서 setAttribute를 사용했다고 했었죠?

boards 라는 key 로 반복문(forEach) 을 사용하고있네요. 그리고 중간중간 ${board.title} 으로 

데이터를 get 쓰듯이 불러오고 있어요.

 

 

index.jsp에 sendRedirect를 dispatcher 로 고치셨나요 ??

<%
	RequestDispatcher dis = 
		request.getRequestDispatcher("board?cmd=list"); 
	dis.forward(request, response); // 톰켓이 생성하는 request와 response를 재사용한다. 다시 접근하는게 아니라 내부적으로 움직인다는 뜻.
%> 

 

 

 

 

 

결과

자바스크립트 배열 CRUD  (map, filter, slice, concat, spread연산자)

 

 

스프레드(전개)연산 ...
추가하기 concat
삭제하기(걸러내기) filter 
잘라내기 slice
반복하기 map (배열 전체복사)
수정하기 ...연산 응용

 

 

 

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
 <script>
  // concat, filter, map, slice, 스프레드(전개) 연산자
  
  console.log("1. =============== 스프레드 연산자");
  const a = [1,2,3];
  const b = [...a];
  b.push(4); // b의 데이터 변경
  console.log(`a의 값은 : ${a}`); // 1,2,3
  console.log(`b의 값은 : ${b}`); // 1,2,3,4

  console.log("2. =============== 추가하기");
  const a2 = [1,2,3];
  const b2 = a2.concat(4);
  console.log(`a2의 값은 : ${a2}`); // 1,2,3
  console.log(`b2의 값은 : ${b2}`); // 1,2,3,4
  const c2 = [0, ...a2, 4];
  console.log(`c2의 값은 : ${c2}`); // 0,1,2,3,4

  console.log("3. =============== 걸러내기"); // 삭제하기
  const a3 = [1,2,3];
  const b3 = a3.filter((n)=> { return n !=1}); // bool을 return 받는다. -> true만 걸러낸다.
  console.log(`b3의 값은 : ${b3}`); // 2,3

  console.log("4. =============== 잘라내기");
  const a4 = [1,2,3];
  const b4 = a4.slice(0,2);
  console.log(b4); // [1,2]
  const c4 = [...a4.slice(0,2), 4, ...a4.slice(2,3)];
  console.log(c4); // [1,2,4,3]

  console.log("5. =============== 반복하기");
  const a5 = [1,2,3];
  //for(let i=0; i<a5.length; i++){
  //  console.log(a5[i]);
  //}
  // a5.forEach((n) => {console.log(n);}); // 리턴 못함.
  const b5 = a5.map((n) => n+10);  // const b5 = [...a5];
  console.log(b5);

const data = {phone:"2222"};
  const a6 = { id:1, name:"홍길동", phone:"1111", age:17, gender:"남"};
  const b6 = { ...a6, ...data}; //기존 데이터에 덮어씌움
  console.log(b6);


  const users = [
    {id:1, name:"구태모", phone:"2222"},
    {id:2, name:"이대엽", phone:"3333"},
    {id:3, name:"오승훈", phone:"4444"}
  ];

  const updateUserDto = {
    id:2, name:"홍길동"
  };

  const newUsers = users.
    map(user => user.id === updateUserDto.id ? {...user, ...updateUserDto} :user); // const newUser = {...users};
  
  console.log("newUsers", newUsers);
 </script>
</body>
</html>

결과 ( 깊은복사 )

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

import { useState } from 'react';
import './App.css';

function App() {
  // 다운로드 받음
  const [users, setUsers] = useState([
    { id: 1, name: '홍길동' },
    { id: 2, name: '임꺽정' },
    { id: 3, name: '장보고' },
    { id: 4, name: '코스' },
  ]);

  const download = () => {
    let sample = [
      { id: 1, name: '홍길동' },
      { id: 2, name: '임꺽정' },
      { id: 3, name: '장보고' },
      { id: 4, name: '코스' },
    ];

    setUsers([...sample]);
  };

  // 랜더링 시점 = 상태값 변경
  return (
    <div>
      <button onClick={download}>다운로드</button>
      {users.map((u) => (
        <h1>
          {u.id}, {u.name}
        </h1>
      ))}
    </div>
  );
}

export default App;

 

 

리엑트의 렌더링 시점 = 상태값 변경시점.
일반 변수를 상태 변수로 바꾸는법

React의 hooks 라이브러리를 사용함.
const [요소, set요소] = useState( );

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

상태변수 이기때문에 원래있던 값에 변경된 값을 덮어씌우는 형식이다.

조금 더 다듬어서 저장하겠습니다.

npm start 명령어를 실행하면

프로젝트 내부에있는 index.js 가 실행된다. 그속에는

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

라는 render 함수가 있고, 중간에있는 App 은 [컨트롤 + 클릭] 해보면 App.js를 가리키는 문법입니다.

 

  document.getElementById('root') 는 프로젝트 폴더가 만들어질때 같이 만들어진 public 이라는 폴더를 보시면

 

index.html 이라는 파일에서 root라는 id의 태그를 찾겠다는 뜻입니다.

index.html 생긴거보면 주석빼면 걍 아무것도없는 파일입니다

index.js의 최종 목적은 저 사이에 App.js 가 return 하고있는 코드를 집어넣겠다는 뜻입니다.

 

그렇기에 JS파일(App.js)에 HTML 문법을 써넣는 기술이 필요하고 그것을 JSX 문법이라고 합니다. 

 

 

옛날 리액트는 클래스 컴포넌트 방식을 사용했지만, 요즘은 함수형 컴포넌트 방식(Hooks)를 더 많이 사용합니다.

앞으로 사용하는 컴포넌트들은 모두 함수형 컴포넌트 방식을 사용할것입니다.

 

JSX 기본 문법

1. return은 하나의 태그만 가능하다.

2. 자바스크립트의 변수를 HTML 코드안에서 사용하려면 { } 중괄호 문법을 사용해야한다.

3. if문은 사용할 수 없고 삼항연산자를 사용해야한다. ( 조건 ?  참 실행문 : 거짓 실행문 )

4. 조건부 렌더링이 가능하다.(if문 처럼생김) ( 조건 && 참 실행문 )

 

 

React(리액트) 란 프론트엔드 개발 기술로,

브라우저에 변경된 데이터를 감지하여 그림을 그리는데 최적화 된 기술입니다.

 

 

리액트를 실습해 보기 위해서는 '변경 감지'를 무자비하게 인식해줄 '서버'가 필요합니다.

요청을 하면 응답을 해주는것 = 서버

 

 

node.js 서버를 사용하겠습니다.

nodejs.org/ko/

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

작성시간 기준으로 LTS 버전이 14.15.4 버전이네요.

최신버전 보다는 LTS로 다운받아주세요

 

 

프론트엔드는 MS Visual Code 를 사용할겁니다.

백엔드는 나중에 Spring으로 사용할 예정이지만 지금은 필요없습니다.

 

 

원하는 경로에 프로젝트용 새 폴더를 만들고 MS Code 에서 Add WorkSpace 를 합니다

 

 

create-react-app.dev/docs/getting-started/

 

Getting Started | Create React App

Create React App is an officially supported way to create single-page React

create-react-app.dev

새 터미널을 여시고 (저는 cmd를 사용했습니다. 탭에 Terminal)

npx create-react-app my-app
cd my-app
npm start

my-app 이라는 이름으로 프로젝트를 만드는 명령어입니다.

npm start는 서버를 실행하는거구요

 

 

예상경로 :   C:\workspace\my-app> npm start

성공하셨다면 리액트로고가 빙글빙글 돌아가는 화면이 나올겁니다~ 

 

 

 

다음은 리액트 사용에 편리한 확장프로그램들 입니다

ESLint 는 리액트 문법을 검사해주는 프로그램이고,

자바스크립트는 인터프리터 언어이기때문에 실행 전까지는 오류를 알 수가 없습니다.

 

Reactjs code snippets 는 리액트에서 자주쓰이는 문법들을 자동완성 시켜주는 프로그램입니다.

 

 

 

 

 

 

 

ㅎㅇ

 

글쓰기 작성페이지 SaveForm.jsp 에서는

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<!-- 해당 페이지로 직접 URL(자원에 직접 파일.확장자) 접근을 하게 되면 또 파일 내부에서 세션 체크를 해야함. -->
<!-- 필터에 .jsp로 접근하는 모든 접근을 막아버리면 됨. -->

<div class="container">
	<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
		<div class="form-group">
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>
	
		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>
	
		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>
</div>
  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

type="hidden " userId 값을 전송해야한다.

그렇다면 value값은 어디서 정해지는걸까 ?

답 : ${ } (EL표현식)을 사용하여 EL내장객체인 SessionScope에 접근하여 가져온다.

 

 

else if(cmd.equals("login")) { //로그인 완료
			// 서비스 호출
			String username = request.getParameter("username");
			String password = request.getParameter("password");
			LoginReqDto dto = new LoginReqDto();
			dto.setUsername(username);
			dto.setPassword(password);
			
			User userEntity = userService.로그인(dto);
			if(userEntity != null) {
				HttpSession session = request.getSession();
				session.setAttribute("principal", userEntity); //인증주체
				//response.sendRedirect("index.jsp"); //로그인 성공
				RequestDispatcher dis = request.getRequestDispatcher("index.jsp");
				dis.forward(request, response);
			}else {
				Script.back(response, "로그인 실패");
			}
			
			
		}

key값인 principal 은 UserController에서 cmd.equals("login") 기능 내부에서

session.setAttribute() 되어서 RequestDispatcher를 사용하여 저장된다

 

 

 

이번 글에서 설명하고자 하는것은

"로그인을 하지않은 사용자가 글쓰기를 시도하려한다면?" 에 대한 방법입니다.

 

config 패키지 

ForbiddenUrlConfig.java

package com.cos.blog.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 이제부터는 내부에서의 모든 요청은 RequestDispatcher로 해야한다. 
// 그래야 다시 필터를 타지 않는다.

public class ForbiddenUrlConfig implements Filter{

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
    	HttpServletResponse response = (HttpServletResponse) resp;

    	System.out.println("ForbiddenUrlConfig 접근");
    	System.out.println(request.getRequestURL());
    	System.out.println(request.getRequestURI());

    	if(request.getRequestURI().equals("/blog/") || request.getRequestURI().equals("/blog/index.jsp")) {
    		chain.doFilter(request, response);
    	}else {
    		PrintWriter out = response.getWriter();
    		out.print("잘못된 접근입니다.");
    		out.flush();
    	}

	}
}

 

WEB-INF 폴더에 있는

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                       http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
	
	<description>MySQL Test App</description>
	<resource-ref>
		<description>DB Connection</description>
		<res-ref-name>jdbc/TestDB</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>
	
 	<filter>
		<filter-name>charConfig</filter-name>
		<filter-class>com.cos.blog.config.CharConfig</filter-class>
	</filter>

	<filter>
		<filter-name>forbiddenUrlConfig</filter-name>
		<filter-class>com.cos.blog.config.ForbiddenUrlConfig</filter-class>
	</filter>

	<!-- 매핑순서가 1 -->
	<filter-mapping>
		<filter-name>charConfig</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>



	<!-- 매핑순서가 2	 -->
	<filter-mapping>
		<filter-name>forbiddenUrlConfig</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
</web-app>

만들었던 필터를 등록시킨다. 완전히 적용시키려면 서버를 재실행해야한다.

 

 

페이지 이동방법 설명

sendRedirect() = 외부로 갔다가 내부필터에 다시 들어옴

RequestDispatcher() = 톰캣이 생성하는 request, response를 재사용하여 덮어씌움

 

그래서 프로젝트 중에 사용했던 sendRedirect()를 모두 RequestDispatcher()로 바꿔주어야한다.

그렇지않으면 방금 만든 필터때문에 잘못된 접근 경고페이지를 보게될것이다.

바꾸어야할 파일 : UserController.java , BoardController.java .... 등등 sendRedirect() 사용했던 모든곳

 

예시

else if(cmd.equals("joinForm")) {
			//response.sendRedirect("user/joinForm.jsp");
			RequestDispatcher dis = 
					request.getRequestDispatcher("user/joinForm.jsp");
				dis.forward(request, response);
		}

 

 

 

ㅎㅇㅎㅇ

내용 정리할 시간이 너무 부족하네요 오랜만에 씁니다

 

blog 2 에서만들었던 빈깡통파일 list.jsp을 조금 채워봅시다.

list.jsp

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

<%@ include file="../layout/header.jsp" %>

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" />
			<input type="hidden" name="page" value="0" />

			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">			
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>

	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: 70%"></div>
	</div>

		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">제목</h4>
				<a href="#" class="btn btn-primary">상세보기</a>
			</div>
		</div>

	<br />
	<ul class="pagination justify-content-center">
		<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
		<li class="page-item"><a class="page-link" href="#">Next</a></li>
	</ul>
</div>

</body>
</html>

지금은 데이터가 있는 척~ 하면서 div - card 를 만들었지만 이후에, JSTL을 사용해서 글 목록을 불러와서 뿌릴겁니다.

 

 

글작성을 위한 페이지 작업

header.jsp

헤더부분에 summernote 를 사용하기위해 몇 줄 추가해줍니다.

<link
	href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
	rel="stylesheet">
<script
	src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<%@ 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 lang="en">
<head>
<title>Cos 블로그</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script
	src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<link
	href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
	rel="stylesheet">
<script
	src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
</head>
<body>

	<nav class="navbar navbar-expand-md bg-dark navbar-dark">
		<a class="navbar-brand" href="<%=request.getContextPath()%>/index.jsp">블로그</a>
		<button class="navbar-toggler" type="button" data-toggle="collapse"
			data-target="#collapsibleNavbar">
			<span class="navbar-toggler-icon"></span>
		</button>
		<c:choose>
			<c:when test="${sessionScope.principal != null}">
				<div class="collapse navbar-collapse" id="collapsibleNavbar">
					<ul class="navbar-nav">
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/board?cmd=saveForm">글쓰기</a></li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=updateForm">회원정보</a>
						</li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=logout">로그아웃</a></li>
					</ul>
				</div>
			</c:when>
			<c:otherwise>
				<div class="collapse navbar-collapse" id="collapsibleNavbar">
					<ul class="navbar-nav">
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=joinForm">회원가입</a></li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=loginForm">로그인</a></li>
					</ul>
				</div>
			</c:otherwise>
		</c:choose>
	</nav>
	<br>

여기서 이미 JSTL이 사용되고 있었네요 파일 상단에

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

이렇게 선언을 해주시면 jsp에서 java문법과 유사한 JSTL을 사용하실 수 있습니다.

<c: choose> , <c:when> , <c:otherwise> 가 사용되었네요 이 놈들은 if - else 처럼 사용되고있습니다.

 

 

<c:when test = "${sessionScope.principal != null}">

test 가 조건문입니다.  (principal : 로그인 인증 주체)

principal 이 null 이면 로그인 안한 상태 -> 로그인, 회원가입이 있는 View를 보여줌

principal 이 null 이 아니면 로그인 한 상태 -> 로그아웃, 글작성이 있는 View를 보여줌

 

saveForm.jsp

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<form action="#" method="POST">

		<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>
</div>

  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

 

 

 

 

 

 

글작성준비 완료

제법 그럴싸해보이죠

이것이 라이브러리의 힘??

 

 

 

saveForm의 action에 cmd=save 를 해줬으니 Controller에 cmd.equals("save")의 기능을 만들어줘야합니다.

 

BoardController.java (Servlet 파일입니다)

package com.cos.blog.web;

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;
import javax.servlet.http.HttpSession;

import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.User;
import com.cos.blog.service.BoardrService;
import com.cos.blog.util.Script;

// http://localhost:8080/blog/board
@WebServlet("/board")
public class BoardController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    public BoardController() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String cmd = request.getParameter("cmd");
		BoardrService boardService = new BoardrService();
		// http://localhost:8080/blog/board?cmd=saveForm
		HttpSession session = request.getSession();
		if(cmd.equals("saveForm")) {
			User principal = (User) session.getAttribute("principal");
			if(principal != null) {
				response.sendRedirect("board/saveForm.jsp");
			}else {
				response.sendRedirect("user/loginForm.jsp");
			}	
		}else if(cmd.equals("save")) {
			int userId = Integer.parseInt(request.getParameter("userId"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			SaveReqDto dto = new SaveReqDto();
			dto.setUserId(userId);
			dto.setTitle(title);
			dto.setContent(content);
			int result = boardService.글쓰기(dto);
			if(result == 1) { //정상
				response.sendRedirect("index.jsp");
			}else {
				Script.back(response, "글쓰기실패");
			}
		}
	}



}

SaveReqDto.java (글 저장'만'을 위한 Data Transfer Object)

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class SaveReqDto {
	private int userId;
	private String title;
	private String content;
}

 

BoardDao.java (DB 작업)

package com.cos.blog.domain.board;

import java.sql.Connection;
import java.sql.PreparedStatement;

import com.cos.blog.config.DB;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.dto.JoinReqDto;

public class BoardDao {

	public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO board(userId, title, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setString(2, dto.getTitle());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
}

BoardService.java

package com.cos.blog.service;

import com.cos.blog.domain.board.BoardDao;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.UserDao;

public class BoardrService {

	private BoardDao boardDao;

	public BoardrService() {
		boardDao = new BoardDao();
	}

	public int 글쓰기(SaveReqDto dto) {
		return boardDao.save(dto);
	}
}

 

 

 

글 작성의 흐름 설명

1. 사용자(새 클라이언트)가 블로그를 방문합니다

2. 회원가입 로그인을 이미 마친 상태이고, 글쓰기 버튼을 누릅니다. 

3. cmd=saveForm 이기 때문에 Controller의 cmd.equals("saveForm") 기능으로 분기됩니다. (글쓰기 페이지로 이동)

4. 로그인을 했으므로 userId 가 기본적으로 있고, 제목(title) , 내용 (content)를 작성하고 글작성완료 버튼을 누릅니다.

이때 cmd=save 이기 때문에 Controller의 cmd.equals("save") 기능으로 분기됩니다.

 

jsp에서 <form> 안에 있는 양식을 submit으로 웹 서버에 전송하게 되면 input타입에 name속성이 붙여진 값들을 getParameter함수로 값을 받아서 변수로 사용할 수 있게 됩니다.

 

saveForm.jsp

<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>

input 타입의 name속성 : UserId, title, content

전송하고 싶은 값인데, 보이지 않았으면 좋겠다 -> type="hidden" 을 사용합니다.

 

BoardController.java

else if(cmd.equals("save")) {
			int userId = Integer.parseInt(request.getParameter("userId"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			SaveReqDto dto = new SaveReqDto();
			dto.setUserId(userId);
			dto.setTitle(title);
			dto.setContent(content);
			int result = boardService.글쓰기(dto);
			if(result == 1) { //정상
				response.sendRedirect("index.jsp");
			}else {
				Script.back(response, "글쓰기실패");
			}
		}

 

request.getParameter( [form 안에있는 input의 name 값] ) 을 변수에 넣어서

내맘대로 사용할 수 있습니다.

글쓰기만을 위한 SaveReqDto에 넣어서 new SaveReqDto를 만들어주고 

boardService의 글쓰기함수에서 바로 방금만든 dto를 인수로 받아 사용합니다.

 

글쓰기 함수는 save(SaveReqDto)를 호출합니다

BoardService.java

package com.cos.blog.service;

import com.cos.blog.domain.board.BoardDao;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.UserDao;

public class BoardrService {

	private BoardDao boardDao;

	public BoardrService() {
		boardDao = new BoardDao();
	}

	public int 글쓰기(SaveReqDto dto) {
		return boardDao.save(dto);
	}
}

save함수를 보겠습니다

BoardDao.java

package com.cos.blog.domain.board;

import java.sql.Connection;
import java.sql.PreparedStatement;

import com.cos.blog.config.DB;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.dto.JoinReqDto;

public class BoardDao {

	public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO board(userId, title, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setString(2, dto.getTitle());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
}

방금 만든 dto값으로 sql문을 만들어서 DB INSERT작업을 합니다.

INSERT의 결과값은 성공했으면 1 실패했다면 1 이외의 값을 return 하게 됩니다.

 

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

회원가입은 어떤식으로 작동할것 같은가?

1. VIEW에 회원가입을위한 정보를 작성한다.

2. 버튼을 눌러 서버로 DB에 저장해달라고 '요청' 한다.

3. 작성한 정보는 Model 객체로 만들어지고 , DAO를 통해 DB에 저장이 된다.

 

 

로그인은 어떤식으로 작동할것 같은가?

1. VIEW에 로그인을 위한 정보를 작성한다 (아이디, 패스워드)

2. DAO를 통해 작성한 정보를 바탕으로 SELECT를 한다.

3. 실행결과가 있다면 분기하여 세션을 저장하여 다른페이지로 이동할때 그 세션을 계속 불러오도록 한다(?)

 

제가 이해하기 편하도록, 제가 이해한대로 써봤습니다..

반드시 이렇다. 라고 생각하지는 말아주세요 불확실한 정보입니다

 

 

ㅎㅇ

 

정리해야될거 선(先)정리

1. 로그인페이지 , 회원가입 페이지로 이동하게 해주는 페이지

2. 로그인 Form , 회원가입 Form (아이디 중복검사, 주소검색)

3. 로그인과 회원가입만을 위한 객체 LoginReqDto , JoinReqDto 생성, 이해하기

 

나중에 게시판이되어 게시글을 보여줄

list.jsp 를 생성합니다.

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

<%@ include file="../layout/header.jsp" %>

</body>
</html>

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>
<h1>인덱스 페이지입니다.</h1>
</body>
</html> 

<%
	response.sendRedirect("board/list.jsp");    
%> 

header.jsp (부트스트랩, 제이쿼리 선언)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Cos 블로그</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <a class="navbar-brand" href="#">블로그</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" href="#">회원가입</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">로그인</a>
      </li>    
    </ul>
  </div>  
</nav>
<br> 

 

 

 

대충 이런모양이 나옵니다

list.jsp

 

로그인을 누르면 나올 화면인 

loginFrom.jsp

를 생성합니다.

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

<%@include file="../layout/header.jsp" %>

<!-- x-www-form-urlencoded -->
<div class="container">
	<form action="/blog/user?cmd=login" method="post">
		<div class="form-group">
			<input type="text" id="username" name="username" class="form-control"
				placeholder="Enter Username" required"/>
		</div>

		<div class="form-group">
			<input type="password" name="password" class="form-control"
				placeholder="Enter Password" required />
		</div>
		<button type="submit" class="btn btn-primary">로그인완료</button>
	</form>
</div>
</body>
</html>

LoginForm.jsp

 

 

 

회원가입 화면인

JoinForm.jsp 

를 생성합니다

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

<%@include file="../layout/header.jsp"%>

<div class="container">
	<form action="/blog/user?cmd=join" method="post" onsubmit="return valid();">
		<div class="d-flex justify-content-end">
			<button type="button" class="btn btn-info" onClick="usernameCheck();">중복검사</button>
		</div>
		<div class="form-group">
			<input type="text" id="username" name="username" class="form-control"
				placeholder="Enter Username" required"/>
		</div>

		<div class="form-group">
			<input type="password" name="password" class="form-control"
				placeholder="Enter Password" required />
		</div>

		<div class="form-group">
			<input type="email" name="email" class="form-control"
				placeholder="Enter Email" required />
		</div>

		<div class="d-flex justify-content-end">
			<button type="button" class="btn btn-info" onClick="goPopup();">주소검색</button>
		</div>
		<div class="form-group">
			<input type="text"  name="address"  id="address" class="form-control" placeholder="Enter Address"  required/>
		</div>
		<button type="submit" class="btn btn-primary">회원가입완료</button>
	</form>
</div>


<script>
	var isChecking = false;

	function valid(){
		if(isChecking == false){
			alert("아이디 중복체크를 해주세요");
		}
		return isChecking;
	}
	function usernameCheck(){
		// DB에서 확인해서 username이 없다면 isChecking = true
		var username = $("#username").val();
		$.ajax({
			type: "POST",
			url: "/blog/user?cmd=usernameCheck",
			data: username,
			contentType: "text/plain; charset=utf-8",
			dataType: "text"  // 응답 받을 데이터의 타입을 적으면 자바스크립트 오브젝트로 파싱해줌.
		}).done(function(data){
			if(data === 'ok'){ // 유저네임 있다는 것
				isChecking = false;
				alert('유저네임이 중복되었습니다.')
			}else{
				isChecking = true;
				$("#username").attr("readonly", "readonly");
				alert("해당 유저네임을 사용할 수 있습니다.")
			}
		});
	}

	function goPopup() {
		// 주소검색을 수행할 팝업 페이지를 호출합니다.
		// 호출된 페이지(jusopopup.jsp)에서 실제 주소검색URL(https://www.juso.go.kr/addrlink/addrLinkUrl.do)를 호출하게 됩니다.
		
		var pop = window.open("/blog/user/jusoPopup.jsp", "pop",
				"width=570,height=420, scrollbars=yes, resizable=yes");
	}
	function jusoCallBack(roadFullAddr) {
		var addressEl = document.querySelector("#address");
		addressEl.value = roadFullAddr;
	}
</script>
</body>
</html>

 

ㅡㅡㅡㅡAjax 통신 정의ㅡㅡㅡㅡㅡ

Ajax (Async Javascript And XML)는 웹 페이지에서 새로운 데이터를 보여주려고 할 때 웹페이지 전체를 새로고침 하지 않고, 보여주고자 하는 데이터가 포함된 페이지의 일부 만을 로드 하기 위한 기법입니다.

Ajax는 비동기 처리 모델 (또는 non-blocking 이라고도 함)을 사용하여 데이터를 처리합니다.

 

동기 처리 모델에서 브라우저는 자바스크립트 코드를 만나면 스크립트를 처리하기 전까지 다른 작업을 일시 중지하고, 자바스크립트 코드의 처리가 끝난 후 기존 작업을 진행합니다.

반면에 Ajax를 사용하면 브라우저는 서버에 데이터를 요청한 뒤 페이지의 나머지를 로드하고 페이지와 사용자의 상호작용을 처리합니다.

 

Ajax 동작방식

function usernameCheck(){
		// DB에서 확인해서 username이 없다면 isChecking = true
		var username = $("#username").val();
		$.ajax({
			type: "POST",
			url: "/blog/user?cmd=usernameCheck",
			data: username,
			contentType: "text/plain; charset=utf-8",
			dataType: "text"  // 응답 받을 데이터의 타입을 적으면 자바스크립트 오브젝트로 파싱해줌.
		}).done(function(data){
			if(data === 'ok'){ // 유저네임 있다는 것
				isChecking = false;
				alert('유저네임이 중복되었습니다.')
			}else{
				isChecking = true;
				$("#username").attr("readonly", "readonly");
				alert("해당 유저네임을 사용할 수 있습니다.")
			}
		});
	}
//UserController.java  (Servlet)

else if(cmd.equals("usernameCheck")) { //유저네임 중복체크
			BufferedReader br = request.getReader(); //input 에 입력된 username을 읽음
			String username = br.readLine();
			System.out.println(username);
			//DB와 통신함 (service -> Dao)
			//Dao 에서 Select 해보고 int 결과값을 리턴해줌. 있으면 1 없으면 not 1
			int result = userService.유저네임중복체크(username); 
			
			PrintWriter out = response.getWriter();
			if(result == 1) {
				out.print("ok");
			}else {
				out.print("fail");
			}
			out.flush();
		}

 

  1. 요청(request) - 브라우저가 서버에 정보를 요청한다.
    바로 위에있는 JoinForm.jsp 에서 첫번째 Form의 중복검사 action이 usernameCheck() 함수를 가지고있는데,
    그 함수는 파일 하단에. 자바스크립트 함수로 정의되어 있습니다.

    이 함수가 JQuery를 이용한 Ajax 요청방법입니다. ( Web Front 에 입력된 데이터를 서버로 전송 )
    type : 요청방식 (get, post, delete, put)   //  url : 요청주소 ( Controller 에서 Servlet이 처리함 )

    data : 서버로 보낼 데이터. (위 함수에서는 var username = $("#username").val() 이 전송되는중)

    contentType : 응답받을 데이터의 형태, 보통은 데이터를 처리하기위해 JSON 이겠지만 
    Controller의 결과값을 보면 ok , fail 의 단순 문자열이기때문에 text

    응답에 성공을 한다면, done(function(data){  '대충 실행될 명령'   } ) 분기로 가서
    응답받은 데이터( data << Ajax 함수의 data 아님. Ajax의 결과값임 )로 해야할 일을 처리합니다.
    data를 result 로 바꾸는게 좀 보기 편할거같네요

  2. 서버의 동작 - 서버는 JSON, XML 등의 형식으로 데이터를 전달한다.
    최근에는 JSON을 가장 많이 사용하고 있습니다.

  3. 응답(response) - 브라우저에서 이벤트가 발생하여 콘텐츠를 처리한다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

 

JoinForm.jsp

 

 

명심할것!!!!!!! 컨트롤러 ↔ 서비스 ↔ DAO (DB)

User의 컨트롤을 담당 (로그인, 회원가입, 로그아웃)

UserController.java  (Servlet 파일)

package com.cos.blog.web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;

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 javax.servlet.http.HttpSession;

import com.cos.blog.domain.user.User;
import com.cos.blog.domain.user.dto.JoinReqDto;
import com.cos.blog.domain.user.dto.LoginReqDto;
import com.cos.blog.service.UserService;
import com.cos.blog.util.Script;

//http://localhost:8000/blog/user
@WebServlet("/user")
public class UserController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
    public UserController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		UserService userService = new UserService();
		String cmd = request.getParameter("cmd");

		// http://localhost:8000/blog/user?cmd=loginForm
		if(cmd.equals("loginForm")) { //로그인 페이지
			// 서비스 호출
			RequestDispatcher dis = request.getRequestDispatcher("user/loginForm.jsp");
			dis.forward(request, response);
			//response.sendRedirect("user/loginForm.jsp");
		}else if(cmd.equals("login")) { //로그인 완료
			// 서비스 호출
			String username = request.getParameter("username");
			String password = request.getParameter("password");
			LoginReqDto dto = new LoginReqDto();
			dto.setUsername(username);
			dto.setPassword(password);
			
			User userEntity = userService.로그인(dto);
			if(userEntity != null) {
				HttpSession session = request.getSession();
				session.setAttribute("principal", userEntity); //인증주체
				//response.sendRedirect("index.jsp"); //로그인 성공
				RequestDispatcher dis = request.getRequestDispatcher("index.jsp");
				dis.forward(request, response);
			}else {
				Script.back(response, "로그인 실패");
			}
			
			
		}else if(cmd.equals("joinForm")) { //회원가입 페이지
			RequestDispatcher dis = request.getRequestDispatcher("user/joinForm.jsp");
			dis.forward(request, response);
			//response.sendRedirect("user/joinForm.jsp");
			
		}else if(cmd.equals("join")) { //회원가입 완료
			// 서비스 호출
			String username = request.getParameter("username");
			String password = request.getParameter("password");
			String email = request.getParameter("email");
			String address = request.getParameter("address");
			JoinReqDto dto = new JoinReqDto();
			dto.setUsername(username);
			dto.setPassword(password);
			dto.setEmail(email);
			dto.setAddress(address);
			System.out.println("회원가입 : "+dto);
			int result = userService.회원가입(dto); //DB와 통신함 (service -> Dao)
			if(result == 1) {
				RequestDispatcher dis = request.getRequestDispatcher("index.jsp");
				dis.forward(request, response);
			}else {
				Script.back(response, "회원가입 실패 -1");
			}
		}else if(cmd.equals("usernameCheck")) { //유저네임 중복체크
			BufferedReader br = request.getReader();
			String username = br.readLine();
			System.out.println(username);
			int result = userService.유저네임중복체크(username); //DB와 통신함 (service -> Dao)
			
			PrintWriter out = response.getWriter();
			if(result == 1) {
				out.print("ok");
			}else {
				out.print("fail");
			}
			out.flush();
		}else if(cmd.equals("logout")) { //로그아웃
			HttpSession session = request.getSession();
			session.invalidate(); //즉시 세션 만료
			RequestDispatcher dis = request.getRequestDispatcher("index.jsp");
			dis.forward(request, response);
		}
	}
}

UserService.java 작성

package com.cos.blog.service;

import com.cos.blog.domain.user.User;
import com.cos.blog.domain.user.UserDao;
import com.cos.blog.domain.user.dto.JoinReqDto;
import com.cos.blog.domain.user.dto.LoginReqDto;
import com.cos.blog.domain.user.dto.UpdateReqDto;

public class UserService {
	private UserDao userDao;
	
	public UserService() {
		userDao = new UserDao();
	}
	
	
	public int 회원가입(JoinReqDto dto) {
		
		int result = userDao.save(dto);
		return result;
	}

	public User 로그인(LoginReqDto dto) {
		return userDao.findByUsernameAndPassword(dto);
	}

	public int 회원수정(UpdateReqDto dto) {

		return -1;
	}

	public int 유저네임중복체크(String username) {
		int result = userDao.findByUsername(username);
		return result;
	}
}

 

DB.java 에 아래의 함수 생성

public static void close(Connection conn, PreparedStatement pstmt) {
		try {
			conn.close();
			pstmt.close();
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}

 

UserDao.java 작성 (대부분 SQL문)

 

public int save(JoinReqDto dto) { // 회원가입
		String sql = "INSERT INTO user(username, password, email, address, userRole, createDate) VALUES(?,?,?,?, 'USER', now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getUsername());
			pstmt.setString(2, dto.getPassword());
			pstmt.setString(3, dto.getEmail());
			pstmt.setString(4, dto.getAddress());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
    
    public int findByUsername(String username) { //아이디 중복 체크
		String sql = "SELECT * FROM user WHERE username = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, username); //dto가 아닌 그냥 username
			rs =  pstmt.executeQuery(); //조회 결과가 1건이라도 있으면 1 , 없으면 -1

			if(rs.next()) { //1건이라도 있다 = true 일때 실행
				return 1; // 있어
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return -1; // 없어
	}
    
    public User findByUsernameAndPassword(LoginReqDto dto) { // 로그인
		String sql = "SELECT id, username, email, address FROM user WHERE username = ? and password =?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getUsername()); //dto가 아닌 그냥 username
			pstmt.setString(2, dto.getPassword());
			rs =  pstmt.executeQuery(); 

			// Persistence API
			if(rs.next()) { //1건이라도 있다 = true 일때 실행
				User user = User.builder()
						.id(rs.getInt("id"))
						.username(rs.getString("username"))
						.email(rs.getString("email"))
						.address(rs.getString("address"))
						.build();
				
				return user;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null; // 없어
	}

 

한글 데이터 요청 , 응답시 한글 깨짐을 방지하기위한 

CharConfig.java 생성 (doFilter)

package com.cos.blog.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CharConfig implements Filter{

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
    	HttpServletResponse response = (HttpServletResponse) resp;

     	request.setCharacterEncoding("utf-8");
		response.setContentType("text/html; charset=utf-8");

//		String username = request.getParameter("username");
//		System.out.println("username : "+username);
//		
//		PrintWriter out = response.getWriter();
//		out.println("안녕");
//		out.flush();
		chain.doFilter(request, response);
	}
}

 

 

뒤로가기를 함수로 따로 구현하기 위한 

Script.java 작성

package com.cos.blog.util;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

public class Script {
	public static void back(HttpServletResponse resp, String msg) throws IOException {
		PrintWriter out = resp.getWriter();
		resp.setContentType("text/html; charset=utf-8"); //utf-8로 응답하겠다
		out.println("<script>");
		out.println("alert('"+msg+"');");
		out.println("history.back();");
		out.println("</script>");
		out.flush();
	}

 

패키지정리는 알아보기쉽게.. (언젠간 한눈에 보일거같습니다)

 

Dto란?   Data Transfer Object 

데이터 교환만을 위한 객체로, DB에서 데이터를 얻어 Service나 Controller 등으터 보낼 때 사용하는 객체를 말한다.

User , Board 처럼 이미 모델이 만들어져있지만 각각 상황에 따라 일부 필드(변수)는 요구되지않는 요청이 생길 수 있다.

 

예를 들자면 로그인을 위한 데이터를 받고싶다고 하자. (아이디, 패스워드 만 있으면 됨)

그러나 User 모델은 로그인만을 하기위해 사용하기에는 쓸데없는 변수가 너무많다.

 

 

그럴때, 오로지 로그인만을 위한 DTO(Data Transfer Object : 데이터 전송 객체)를 만들어서 사용하는것이다.

 

미완성 게시물입니다. 이후 내용 추가

 

 

추가할 내용

유저네임 중복검사 , 로그인 구현

Ajax 통신 이해하기, (Reader, Writer)  이해하기,

(get,setAttribute) 이해하기,

RequestDispatcher 이해하기

 

집에서 새로 프로젝트 만들어서 내가 작성한내용 따라해서 리뷰하기.

개발환경 : STS4

서버 : Tomcat9.0

DB : MySQL

필요 준비물 : lombok.jar , mysql-connector-java-8.0.16.jar

 

 

앞으로의 시스템 흐름을 이해하기위해 가장 먼저 숙지 해야할 그림입니다. 

이번 시간에는 그림 아래부분에있는 DB를 생성하고 , 그것을 담을 Model객체를 만들어보겠습니다.

 

 

1. Dynamic Web Project - blog 생성 , Runtime Target = Tomcat9.0 으로 설정

더보기

저는 만드는 척만 하겠습니다.

1.a 21년 5월 23일 추가

옛날에는 새 프로젝트를 만들면 Dynamic Web Project 를 선택할 수 있었는데, STS의 버전이 업그레이드 되면서 해당 프로젝트의 생성을 권장하지 않아서 그런지 기본 선택목록에서 아예 제외된것 같습니다.

 

Web Project Plugin 설치방법

https://goodteacher.tistory.com/329

 

STS에서 Dynamic Web Project 개발

STS에서 Dynamic Web Project 개발 STS는 왜 이렇게 원래의 legacy 방밥을 통한 개발을 싫어할까? Boot를 밀고 싶은 맘이야 충분히 공감하지만 legacy도 좀 끼워주면 안되나 싶다. 사라져버린 Dynamic Web Program..

goodteacher.tistory.com

Help - install new software - ... 

 

 

2. MySQL Workbench 실행, 테이블생성 을 하기전에~~~~

유저 생성

더보기
create user 'bloguser'@'%' identified by '비밀번호';
GRANT ALL PRIVILEGES ON *.* TO 'bloguser'@'%';
create database blog;
use blog;

 

새로 만든 bloguser로 접속합시다

 

 

3. 테이블 생성

더보기
drop table user;
drop table board;
drop table reply;

CREATE TABLE user(
	id int primary key auto_increment,
    username varchar(100) not null unique,
    password varchar(100) not null,
    email varchar(100) not null,
    address varchar(100),
    userRole varchar(20),
    createDate timestamp
) ;

CREATE TABLE board(
	id int primary key auto_increment,
    userId int,
    title varchar(100) not null,
    content longtext,
    readCount int default 0,
    createDate timestamp,
    foreign key (userId) references user (id)
);

CREATE TABLE reply(
	id int primary key auto_increment,
    userId int,
    boardId int,
    content varchar(300) not null,
    createDate timestamp,
    foreign key (userId) references user (id) on delete set null,
    foreign key (boardId) references board (id) on delete cascade
);

 

 

 

4. Model 생성

더보기

com.cos.blog.domain 패키지 안에 board , reply , user 패키지생성.

각각의 패키지에 각각의 class 생성.

 

package com.cos.blog.domain.board;

import java.sql.Timestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Board {
	private int id;
	private int userId;
	private String title;
	private String content;
	private int readCount;
	private Timestamp createDate;
}
package com.cos.blog.domain.reply;

import java.sql.Timestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Reply {
	private int id;
	private int userId;
	private int boardId;
	private String content;
	private Timestamp createDate;
}
package com.cos.blog.domain.user;

import java.sql.Timestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
	private int id;
	private String username;
	private String password;
	private String email;
	private String address;
	private String userRole; // admin, user
	private Timestamp createDate;
}

 

lombok.jar 설치 하셔야합니다

 

 

 5. Dao(DataAccessObject) 생성

더보기

Dao란 무엇인가??

DB에 접근하여 조회, 삽입, 수정, 삭제의 기능만을 담당하는 객체입니다.

그림에서 Model과 DB 사이에 위치하고있습니다.

 

기능은 나중에 구현하고 빈 클래스만 만들어주세요

 

 

 6. DB연결 

tomcat.apache.org/tomcat-9.0-doc/index.html

 

Apache Tomcat 9 (9.0.41) - Documentation Index

This is the top-level entry point of the documentation bundle for the Apache Tomcat Servlet/JSP container. Apache Tomcat version 9.0 implements the Servlet 4.0 and JavaServer Pages 2.3 specifications from the Java Community Process, and includes many addit

tomcat.apache.org

Tomcat Document에 가보시면 DB연결을 하기위한 세팅방법이 나와있습니다.

설명해드리겠습니다.

 

 

더보기

META-INF 와 WEB-INF 에 context.xml , web.xml 을 만들어주세요.

xml 파일의 파일명은 반드시 소문자로하셔야합니다. 대문자를 인식하지못합니다.

 

두 개의  파일은 원래 Tomcat의 설정 파일인데 add 해서 사용한다는 느낌? 같습니다

 

 

 

1. context.xml

Document 에서 가져왔습니다

 주석은 필요없고 driverClassName이랑 url에 서버시간만 붙여주시면 됩니다

 

 

수정안하고 해도 가능하긴할텐데 cj.jdbc가 더 최신버전인거같습니다 .

<context>
	<Resource name="jdbc/TestDB" auth="Container"
		type="javax.sql.DataSource" maxTotal="100" maxIdle="30"
		maxWaitMillis="10000" username="bloguser" password="비밀번호"
		driverClassName="com.mysql.cj.jdbc.Driver"
		url="jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Seoul" />
</context> ​

 

 

 

2. web.xml

저도 방금 봤는데 버전 차이가 조금 있네요

밑에걸로 복붙 해주세요

 

 

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="4.0"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
	http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">

	<description>MySQL Test App</description>
	<resource-ref>
		<description>DB Connection</description>
		<res-ref-name>jdbc/TestDB</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>

</web-app>

 

 3. DB.java   +   DBTest.java 생성

 

 

3-1 DB.java

 

package com.cos.blog.config;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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



public class DB {
	public static Connection getConnection() {
		try {
			Context initContext = new InitialContext();
			Context envContext  = (Context)initContext.lookup("java:/comp/env");
			DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB");
			Connection conn = ds.getConnection();
			return conn;
		} catch (Exception e) {
			System.out.println("DB연결실패");
			e.printStackTrace();
		}
		return null;
	}

 

 

jdbc/oracle 과 jdbc/TestDB 가 다릅니다

DB연결하는 기능을 함수화 했습니다.

 

 

3-2 DBTest.java

프로젝트에서 우클릭 - Build Path

 

 

 

Add Library - JUnit4 선택

 

 

 

package com.cos.blog.test;

import static org.junit.Assert.assertNotNull;

import java.sql.Connection;

import org.junit.Test;

import com.cos.blog.config.DB;


public class DBTest {
	
		@Test
	public void DBConnectionTest() {
		Connection conn2 = DB.getConnection();
		assertNotNull(conn2);
	}
}

방금 DB에서 만든 getConnection() 함수를 사용합니다

 

 

 

 @Test 어노테이션을 이용하여 테스트를 할 수 있습니다. 

근데 JUnit 은 제가 아직 사용을 잘 안해봐서 모르겠습니다 

 

 

 

 

테스트 결과

옛날에 만든거라 사용법을 아직 잘 몰라서 어떻게 성공시키는지 모르겠네요

DB연결은 잘 되긴합니다

 

 

 

ㅎㅇ!!

 

 

juso.go.kr 에 접속해서 우측  상단 '개발자 센터'로 이동합니다~

 

 

 

 

그리고 API신청하기 클릭

 

 

 

업체 아무거나 쓰셔도 됩니다

URL 저는 localhost:8080 썼습니다

어짜피 인증키는 즉시나오는거라 상관없는거같기도

 

 

그다음 본인인증 해주시고 인증키를 발급받습니다. (빨간색 글자로 되있음)

인증키만 복사하고 어디 메모장에 적어두세요. 다시 보려면 또 본인인증 해야합니다

guidePopupApiJSP.zip
1.72MB

juso.go.kr 홈페이지에서 제공하는 API 사용 가이드 샘플 jsp파일 입니다. 다운받아주세요.

 

새로운 Dynamic Web Project를 만드시고 다운받은 파일 안에있는 Sample.jsp , jusoPopup.jsp를 붙여넣기 해주세요

 

1. Sample.jsp 에서 jusoPopup을 불러오는 경로부분을 자신의 프로젝트 경로에 맞게 바꿔주셔야합니다

 

2. jusoPopup.jsp 에 발급받았던 인증키를 넣어줍니다.

 

 

 

 

Sample.jsp를 Run on Server 하여 체험해봅니다!!!!!!!! 

 

 

이것을 개인프로젝트에 적용하는 방법을 설명 해드리겠습니다

 

제 임시 프로젝트입니다. jusoPopup.jsp는 그대로 가져와서 사용해야합니다.

 

주소검색 버튼을 누르면, jusoPopup.jsp 이(가) 나오게 해야합니다.

 

주소검색 버튼의 속성값에 onClick="goPopup()" 을 추가해줍니다. 

 

주소입력란(?)(input)의 속성값에 임의의 id="address" 를 추가해줍니다.

어렵지 않습니다

 

 

 

Sample.jsp 의 script 내부에있는 함수 goPopup() , jusoCallBack을 복사해옵니다.

저는 jusoCallBack함수에서 하나의 필드만 사용했기 때문에 하나만 인자로 가져왔습니다

 

체험판에서 했던거랑 똑같이 jusoPopup.jsp 를 찾아주는 경로를 설정해주시면?

 

 

주소검색 버튼클릭 -> 주소 검색( jusoPopup.jsp ) -> input창에 주소검색결과 삽입됨

 

이렇게 실행 될것입니다~~

 

 

 

 

 

 

 

- 웹 서버 (Web Server)

클라이언트가 서버에 페이지 요청을 하면 요청을 받아 정적 컨텐츠(.html, .png, .css등)를 제공하는 서버

클라이언트에서 요청이 올 때 가장 앞에서 요청에 대한 처리를 한다.

클라이언트의 요청을 기다리고 요청에 대한 데이터를 만들어서 응답하는 역할 (정적 데이터)

CASE

정적 컨텐츠를 요청(request)했나?

1. 정적 컨텐츠구나! 내가 제공해줄게 => .html, .png 등 응답(response)

2. 정적 컨텐츠가 아니구나.. 웹서버에서 간단히 처리 못하겠군. WAS에게 처리를 부탁해야겠다! => 결국 WAS가 처리해준 컨텐츠를 받은 웹서버는 응답(response)을 해줌

대표 : Apache, nginx







- WAS (Web Application Server)

동적 컨텐츠를 제공하기 위해 만들어진 애플리케이션 서버 (DB조회, 로직처리가 요구되는 컨텐츠)

JSP,Servlet 구동 환경 제공

컨테이너, 웹컨테이너, 서블릿 컨테이너라고도 부름

* JSP, servlet을 실행시킬 수 있는 소프트웨어 = 컨테이너

동작 프로세스

1. 웹서버로부터 요청이 오면 컨테이너가 받아서 처리

2. 컨테이너는 web.xml을 참조하여 해당 서블릿에 대한 쓰레드 생성하고 httpServletRequest와 httpServletResponse 객체를 생성하여 전달한다.

3. 컨테이너는 서블릿을 호출한다.

4. 호출된 서블릿의 작업을 담당하게 된 쓰레드(2번에서 만든 쓰레드)는 doPost()또는 doGet()을 호출한다.

5. 호출된 doPost(), doGet() 메소드는 생성된 동적 페이지를 Response객체에 담아 컨테이너에 전달한다.

6. 컨테이너는 전달받은 Response객체를 HTTPResponse형태로 바꿔 웹서버에 전달하고 생성되었던 쓰레드를 종료하고 httpServletRequest, httpServletResponse 객체를 소멸시킨다.

대표 : Tomcat, Jeus, JBoss



WAS와 웹 서버 차이

- 동적 컨텐츠 처리를 수행 가능한가 아닌가.

WAS는 정적,동적 처리 둘다 가능하지만 정적처리를 WAS가 하게되면 부하가 많이 걸려서 좋지 않음


* 톰캣(WAS)에는 아파치(웹서버)의 기능(웹서비스데몬, Httpd)를 포함하고 있다.

- 일반적인 WAS, Web Server 구조가 아닌 걸로 알고 있음.


* WAS, Web Server를 따로 두고 쓰는 이유가 성능때문이라고 하는 건 잘못되었다.

톰캣5.5 이상부터는 httpd의 native모듈을 사용해서 정적파일을 처리하는 기능을 제공하는데 이것이 순수 아파치 Httpd만 사용하는 것과 비교해서 성능이 전혀 떨어지지 않기 때문이다.

그럼에도 톰캣앞에 아파치를 두는 이유는 하나의 서버에서 php애플리케이션과 java애플리케이션을 함께 사용하거나, httpd 서버를 간단한 로드밸런싱을 위해서 사용해야 할 때 필요하기 때문.



출처: https://jeong-pro.tistory.com/84 [기본기를 쌓는 정아마추어 코딩블로그]

이전 버전의 hello 프로젝트에는

일단 흐름을 계속 보면서 익힙시다 (가능하다면)


기능소개



로그인 

1. 서비스에서 req.getParameter로 input값을 저장할 String 변수 두 개를 만듬. (username , password)

2. UserDao의 login(Users uesrs) 함수의 매개변수를 만들어주기위해 Users 객체의 틀을 만든다.

3. UserDao 에서 DB연결 후 Users 객체 userEntity 에 DB에서 get해온값으로 build 한다. userEntity를 반환한다.

4. 다시 서비스로 돌아와, if(UserEntity != null) 이라면 



UsersService.java 일부

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
public Users 로그인(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("=========loginPorc Start=========");
        System.out.println(username);
        System.out.println(password);
        System.out.println("=========loginPorc End=========");
        // 2번 DB값이 있는지 select 해서 확인
        // login함수 의 매개변수를 만들어주기위한 빌드업 (Users 틀 만들기)
        Users user = Users.builder()
                .username(username)
                .password(password)
                .build();
 
        // login 함수 실행 (sql로 데이터베이스에 select)
        UsersDao usersDao = new UsersDao();
        //login 의 반환형이 Users
        Users userEntity = usersDao.login(user);       // UserDao
        
        if(userEntity != null) {
            // session에는 사용자 패스워드 절대넣지않기
            // 3번 세션 키 발급
 
            // session 두 줄 이 무슨 역할 하는건가요??
            HttpSession session = req.getSession();
            //setAttribute : name으로 지정한 이름에 value값을 할당합니다.
            session.setAttribute("sessionUser", userEntity); // name , Object(value)
            
            Script.href(resp, "index.jsp""로그인 성공123");
            //한글처리를 위해 resp객체를 건드린다.
            // mime타입
            // http header 에 context-type
            
//            resp.sendRedirect("index.jsp"); 데이터를 들고 이동하는게 아니고 그냥 페이지이동만하는 기능.(필요없음)
            return user;
        }else {
            Script.back(resp, "로그인실패 -1");
            resp.sendRedirect("auth/login.jsp");
        }
        return null;
    }
cs





UserDao.java 일부

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
public Users login(Users users) throws SQLException {
        StringBuffer sb = new StringBuffer();
        sb.append("SELECT id, username, password, email FROM users WHERE username = ? AND password = ?");
        String sql = sb.toString();
        Connection conn = DBConnMySQL.getInstance();
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, users.getUsername());
            pstmt.setString(2, users.getPassword());
//            int result2 = pstmt.executeQuery(sql); // 변경된 row count를 리턴, 오류 시 -1를 리턴
//            System.out.println("result2 : " + result2);
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) { //출력할 행이 여러개면 while
                Users userEntity = Users.builder()
                        .id(rs.getInt("id"))  //Resultset rs 에서 가져오는중 = DB에서 가져오는중
                        .username(rs.getString("username"))
                        .password(rs.getString("password"))
                        .build();
                System.out.println("로그인 성공");
                return userEntity;
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("로그인 실패");
        return null;
    }
cs





ㅎㅇ 이번 실습은 정말 이해를 잘 해야하는, 하고싶은 부분이다


MVC가 웹 에서 가장 활용이 많이된다던  그 분의 말씀, 이제야 이해가 되는것 같다



먼저 MVC의 흐름? 을 그림으로 한눈에 보자



용어 설명

Model무엇을 할지 정의합니다. 비지니스 로직에서의 알고리즘, 데이터 등의 기능을 처합니다.
Controller어떻게 할지를 정의합니다. 요청을 받아서 화면과 Model과 View를 연결시켜주는 역할을 하지요.
View무엇을 화면으로 보여주는 역할을 하지요. 웹이라면 웹페이지, 모바일이라면 어플의 화면의 보여지는 부분입니다.



용어 설명2

Model: 어플리케이션의 데이터, 자료를 의미합니다.
View: 사용자에게 보여지는 부분, 즉 유저 인터페이스(User interface)를 의미합니다.

Controller: Model과 View사이를 이어주는 브릿지(Bridge)역할을 의미합니다.



용어 설명3

모델(Model)

프로그램에 사용되는 데이터를 의미하며 데이터베이스(DB), 상수, 문자열과 같은 변수들, 비전 프로그램이라면 카메라 정보와 같은 것들이 해당됩니다. 모델에는 뷰나 컨트롤러의 정보가 전혀 없습니다. 단지, 정보만 반환하거나 설정할 수 있습니다.

뷰(View)

다이얼로그에 존재하는 텍스트박스, 라벨, 버튼 등 사용자 인터페이스(User interface) 요소들을 의미합니다. 사용자가 제어하고 데이터를 확인할 수 있는 영역입니다. 뷰에서는 별도의 데이터를 보관하지 않습니다. 뷰에서 입력받고 출력해주는 모든 데이터는 모델을 사용해야합니다.

컨트롤러(Controller)

모델과 뷰를 관장하는 브릿지(Bridge)역할을 수행합니다. 사용자가 버튼을 클릭하면 이벤트는 뷰에서 발생하지만 내부 처리는 컨트롤러에서 관리하는 것입니다. 또한, 입력이 발생하면 이에 대한 통지를 담당합니다.


용어 설명4

M (model, domain)

M은 Model을 가리킨다.
Model이란 프로그램이 작업하는 세계관의 요소들을 개념적으로 정의한 것이라고 볼 수 있다.
예를 들어 음식점 무인 포스기를 개발한다고 가정해보자. 무인포스기가 정상적으로 목표하는 작업을 수행하기 위해서는 우선 메뉴가 있어야하고, 메뉴를 담을 수 있는 장바구니, 해당 메뉴의 수량, 결제수단, 할인정책 등등이 필요할 것이다.

이처럼 프로그램이 목표하는 작업을 원활하게 수행하기 위해 필요한 물리적 개체, 규칙, 작업등의 요소들을 구분되는 역할로써 정의해놓은게 Model이 된다. Model은 DTO와 DAO로 분류할 수 있다.
두 개념에 대해서는 나중에 다른 세션에서 정리할 예정이므로 간단히 언급만하고 넘어가겠다.

결과적으로 Model을 잘 설계하는 것은, 해당 도메인 세계를 얼마만큼 이해하고 있는지와도 밀접한 연관이 있다. 꼭 물리적인 요소뿐만아니라 추상적인 요소 또한 해당 작업을 수행하는데 특정 책임과 역할로서 구분될 수 있다면 최대한 구체적이고 작은 entitiy를 유지하면서 Model을 설계하는 것이 중요하다.

V (view)

V는 View를 가리키고 사용자가 보는 화면에 입출력 과정 및 결과를 보여주기 위한 역할을 한다. 입출력의 순서나 데이터 양식은 컨트롤러에 종속되어 결정되고, 도메인 모델의 상태를 변환하거나, 받아서 렌더링하는 역할을 한다.

view를 구현할 때 주의할 점은 도메인 로직의 어떤 것도 알고 있으면 안된다는 것이다. 절대적으로 객체를 전달받아 상태를 바로 출력하는 역할만을 담당해야 한다. 그렇기 때문에 view에서는 도메인 객체의 상태를 따로 저장하고 관리하는 클래스 변수 혹은 인스턴스 변수가 있을 필요가 없다.

C (controller)

C는 Controller를 가리킨다. controller는 model과 view를 연결 시켜주는 다리 역할을 함과 동시에 도메인 객체들의 조합을 통해 프로그램의 작동 순서나 방식을 제어한다. controller는 view와 model이 각각 어떤 역할과 책임이 있는 지 알고 있어야 한다.

웹 프로그래밍에서는 Controller에서 service layer를 분리하여 domain 로직이 수행되는 곳과 view의 요청을 매핑하는 곳을 독립적으로 관리할 수 있다.


웹사이트 개발시 MVC 패턴을 적용하는 일반적인 목적

- 애플리케이션에 데이터 구조와 Model(비지니스 로직)은 전형적으로 자주 변경되지 않지만, 데이터의 View(프레젠테이션 로직)은 사실 자주 변경되기 때문에 Model(비즈니스 로직)과 View(프레젠테이션 로직)을 분리함으로써 이러한 요구의 변경에 따라 애플리케이션에 유지보수가 쉬워진다. 

예를 들어 웹사이트가 보여주는 데이터 구조에는 변함이 없지만, 국내외의 사용자에게 다양한 언어로 데이터를 보여주어야 할 경우 또는 PC/스마트폰/스마트TV 등과 같은 해상도가 다른 장치에서 동일한 데이터를 보여주어야 할 경우 Model(비즈니스 로직)은 유지하고, View(프레젠테이션 로직)만 수정하면 된다. 즉 MVC 패턴을 사용하면 웹사이트의 유지보수가 쉽다.

Model(비지니스 로직)을 담당하는 웹 개발자와 View(프레젠테이션 로직)을 담당하는 웹 디자이너 분리된 작업이 가능하다.





추가 설명

JSP 에서 JAVA로 데이터를 전송하려면 그냥 form action="" 을 이용하면 되지만


JAVA의 데이터를 JSP로 넣으려면 Dispatcher 에 데이터를 담아서 보내야한다.

보내주는 사람(?)은 Request 객체 이다.




실습한 코드 


UsersController.java

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
package com.cos.hello.controller;
 
import java.io.IOException;
import java.sql.SQLException;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
//javax 로 시작하는 패키지는 톰캣이 갖고있는 라이브러리이다.
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import com.cos.hello.model.Users;
import com.cos.hello.service.UsersService;
 
public class UserController extends HttpServlet {
    // 12월 21알 월요일
    // req , res는 톰캣이 만들어줌. (클라이언트의 요청이 있을 때 마다)
    // req는 Reader 할 수 있는 ByteStream 요청
    // res는 Writer 할 수 있는 ByteStream 응답
    // http://localhost:8000/hello/user?gubun=login
 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        String gubun = req.getParameter("gubun");
//        route(gubun, req, resp);
        System.out.println("doGet실행됨");
        doProcess(req, resp);
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost실행됨");
        doProcess(req, resp);
    }
 
    protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//      super.doGet(req, resp);
        System.out.println("UserController 실행됨");
 
        String gubun = req.getParameter("gubun");
        System.out.println(gubun);
        try {
            route(gubun, req, resp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
 
    private void route(String gubun, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException, SQLException {
        UsersService usersService = new UsersService();
        
        if (gubun.equals("login")) {
            resp.sendRedirect("auth/login.jsp"); //loginProc 버튼
        } else if (gubun.equals("join")) {
            resp.sendRedirect("auth/join.jsp"); // joinProc 버튼
        } else if (gubun.equals("selectOne")) {
            usersService.유저정보보기(req, resp);
        } else if (gubun.equals("updateOne")) {
            usersService.유저정보수정페이지(req, resp);
        } else if (gubun.equals("joinProc")) { 
            usersService.회원가입(req, resp);
        } else if (gubun.equals("loginProc")) {
            usersService.로그인(req, resp);
            //SELECT id, username, email From users where username = ? and password = ?
            //DAO 함수명 : login() , return -> Users Object
            //정상 -> 세션을 담고 index.jsp , 비정상 -> login.jsp
        }
    }
}
 
cs

UsersService 객체를 만들어서 route에서 열심히 일하고있다.




UsersService.java

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
package com.cos.hello.service;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import com.cos.hello.dao.UsersDao;
import com.cos.hello.model.Users;
 
public class UsersService {
    public void 회원가입(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException {
        //input 에서 값을 받아옴
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
 
        System.out.println("=========joinProc Start=========");
        System.out.println(username);
        System.out.println(password);
        System.out.println(email);
        System.out.println("=========joinProc End=========");
        // 2번 DB에 연결해서 3가지 값을 INSERT하기
        // input 값을 토대로 Users 빌드
        Users user = Users.builder()
                .username(username)
                .password(password)
                .email(email)
                .build();
        
        // insert 함수 실행 (sql로 데이터베이스에 insert)  
        UsersDao usersDao = new UsersDao();
        int result = usersDao.insert(user); //pstmt
        
        if(result == 1) {
               resp.sendRedirect("auth/login.jsp");
            } else {
               resp.sendRedirect("auth/join.jsp");
            }
    }
    
    public Users 로그인(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("=========loginPorc Start=========");
        System.out.println(username);
        System.out.println(password);
        System.out.println("=========loginPorc End=========");
        // 2번 DB값이 있는지 select 해서 확인 (생략)
        Users user = Users.builder()
                .username(username)
                .password(password)
                .build();
 
        // login 함수 실행 (sql로 데이터베이스에 select)
        UsersDao usersDao = new UsersDao();
        Users userEntity = usersDao.login(user);
        
        if(userEntity != null) {
            // session에는 사용자 패스워드 절대넣지않기
            // 3번 세션 키 발급
            HttpSession session = req.getSession();
            
            //setAttribute : name으로 지정한 이름에 value값을 할당합니다.
            session.setAttribute("sessionUser", userEntity); // name , value
            
            resp.sendRedirect("index.jsp");
            return user;
        }else {
            resp.sendRedirect("auth/login.jsp");
        }
        return null;
    }
    
    public void 유저정보보기(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException, ServletException {
        HttpSession session = req.getSession();
        
        Users users = (Users)session.getAttribute("sessionUser");
        UsersDao usersDao = new UsersDao();
        if(users != null) {
            Users userEntity = usersDao.selectById(users.getId());
            req.setAttribute("users", userEntity); // name , Object
            
            // xxx.jsp로 이동할겁니다 라는 객체
            RequestDispatcher dis = req.getRequestDispatcher("user/selectOne.jsp");
            
            // 덮어쓰기
            dis.forward(req, resp);
        }else {
            System.out.println("유저정보보기() 실패");
        }
    }
    
    public void 유저정보수정페이지(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException, ServletException {
        HttpSession session = req.getSession();
        Users users = (Users)session.getAttribute("sessionUser");
        UsersDao usersDao = new UsersDao();
        if(users != null) {
            Users userEntity = usersDao.selectById(users.getId());
            req.setAttribute("user", userEntity); // name , Object
            
            // xxx.jsp로 이동할겁니다 라는 객체
            RequestDispatcher dis = req.getRequestDispatcher("user/updateOne.jsp");
            
            // 덮어쓰기
            dis.forward(req, resp);
        }
    }
}
 
cs

Model (User 객체) , UserDao , MySQL 셋을 모두 활용하고 있다.

정확하게는 Model, UserDao 두가지. (UserDao 안에 MySQL Query가 있다.)



UserDao.java

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
package com.cos.hello.dao;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
import com.cos.hello.config.DBConnMySQL;
import com.cos.hello.model.Users;
 
public class UsersDao {
    //(String username,String password,String email)
    public int insert(Users users) throws SQLException {// 회원가입수행해줘
        // 1번 form의 input태그에 있는 3가지 값 username, passeword, email받기
            
        StringBuffer sb = new StringBuffer();
        sb.append("INSERT INTO users(username, password, email)");
        sb.append("VALUES(?,?,?)");
        String sql = sb.toString();
        Connection conn = DBConnMySQL.getInstance();
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, users.getUsername());
            pstmt.setString(2, users.getPassword());
            pstmt.setString(3, users.getEmail());
            int result2 = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
            
//            pstmt.executeUpdate();
             
//            return pstmt.executeUpdate();
            if(result2 == 1) {
                System.out.println("result2 : " + result2);
                System.out.println("회원가입 성공");
                return 1;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 3번 INSERT가 정상적으로 되었다면 index.jsp
        
        return -1;
    }
    
    public Users login(Users users) throws SQLException {
        StringBuffer sb = new StringBuffer();
        sb.append("SELECT id, username, password, email FROM users WHERE username = ? AND password = ?");
        String sql = sb.toString();
        Connection conn = DBConnMySQL.getInstance();
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, users.getUsername());
            pstmt.setString(2, users.getPassword());
//            int result2 = pstmt.executeQuery(sql); // 변경된 row count를 리턴, 오류 시 -1를 리턴
//            System.out.println("result2 : " + result2);
            
            
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) {
                Users userEntity = Users.builder()
                        .id(rs.getInt("id"))
                        .username(rs.getString("username"))
                        .password(rs.getString("password"))
                        .build();
                
                return userEntity;
            }
            System.out.println("로그인 성공");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("로그인 실패");
        return null;
    }
    
    public Users selectById(int id) throws SQLException {
        StringBuffer sb = new StringBuffer(); //간단한 문장은 String 으로 , 복잡한 문장은 Buffer로
        sb.append("SELECT id, username, password, email FROM users WHERE id = ?");
        String sql = sb.toString();
        
        Connection conn = DBConnMySQL.getInstance();
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id);            
//            int result2 = pstmt.executeQuery(sql); // 변경된 row count를 리턴, 오류 시 -1를 리턴
//            System.out.println("result2 : " + result2);        
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) {
                Users userEntity = Users.builder()
                        .id(rs.getInt("id"))
                        .username(rs.getString("username"))
                        .password(rs.getString("password"))
                        .email(rs.getString("email"))
                        .build();
                System.out.println("SelectById 성공");
                return userEntity;
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("SelectById 실패");
        return null;
    }
}
 
cs



UsersService 에서 사용할 수 있도록 반환형이 Users 객체나 , success 인 정보를 함수로 반환한다.







hello 프로젝트 참고


https://github.com/tony6303/jspwork



ㅎㅇ 다이나믹 웹 프로젝트   프로젝트 이름 hello 를 만들어 놓으세요 ~


hello2 는 제가 삽질하느라 잘못 만든 프로젝트입니다 ~~~~





Servers 가 Tomcat 관련 파일이 들어있는곳인데 , web.xml에서 내용 일부를 복붙해서 hello 프로젝트에 새로 만들어 줄겁니다 ~


web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                       http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
    <servlet>
        <servlet-name>userController</servlet-name>
        <servlet-class>com.cos.hello.controller.UserController</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>boardController</servlet-name>
        <servlet-class>com.cos.hello.controller.BoardController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>userController</servlet-name>
        <url-pattern>/user</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>boardController</servlet-name>
        <url-pattern>/board</url-pattern>
    </servlet-mapping>
</web-app>
cs




WEB-INF 폴더에 저장   ( new - xml file 로 직접 만들어서 코드만 복붙하세요 )






(미완성)



서블릿을 동작하게 만들려면 web.xml 에 필터를 만들어야함 , 서블릿 맵핑을 해야함


URL 패턴을 쓰지않고 URI 패턴을 사용하기위해서임


모든 요청을 한 곳 으로 모으고싶어서 쓰는것 ( 입구를 하나로)





ㅎㅇ 일단  톰캣을 설치하셈


https://tomcat.apache.org/download-90.cgi




9.0버전 , installer 로 다운받으시고






저는 셧다운포트 8005  연결포트 8000 으로 했습니다


연결포트 8080은 oracle의 포트와 충돌이 있을수도 있다고...


톰캣 설치 끝



STS 실행하세요


FIle > New > other > web > 다이나믹 웹 프로젝트 만드세요




타겟 런타임을 톰캣 9.0으로 설정해주세요

설치경로는 기본값으로 하셨다면 

C:\Program Files\Apache Software Foundation\Tomcat 9.0


여기 있을겁니다







이제 jsp 파일을 만들 수 있습니다



jsp 파일은 항상 WebContent 폴더에 작성해주세요


META-INF , WEB-INF 는  외부접근 불가 구역입니다. 주로 프로젝트의 설정파일을 저장하는곳입니다.


뜬금없지만 JSP관련 기초 설명을 하겠습니다


JSP는 쉽게 말하면 html 파일에서 자바코드를 작성할 수 있게 해주는 파일입니다.

톰캣은 하나의 자바 컴파일러 라고 할 수 있어요.



웹서버(아파치)

내 하드디스크의 특정 폴더를 http주소 방식으로 접근할 수 있게 열어둔 것


접근 방법

프로토콜://ip주소:포트번호/컨텍스트루트/자원명

ex) http://localhost:8000/test1/a.html


jsp

html파일안에 java코드를 넣을 수 있는 파일


톰켓

아파치가 처리할 수 없는 .jsp 파일 요청시 동작함. html파일은 그대로

두고 java코드만 컴파일하여 html코드와 그 결과를 .html파일로 만들

어서 응답해줌.


WebContent

프로젝트 폴더내에 유일하게 외부에서 접근할 수 있게 열어둔 영역


컨텍스트루트

웹서버내에 많은 프로젝트들을 구분하기 위해서 필요


URL

자원에 접근하는 법

URI

식별자로 접근하는 법





MySQL에 있는 데이터를 웹에 뿌려봅시다

아까 만든 다이나믹 웹 프로젝트에서 WEB-INF 폴더에, lib폴더에 >  jar파일 하나 추가하고 add 빌드패스 합니다


https://mvnrepository.com/artifact/mysql/mysql-connector-java



저는 8.0.16 버전 썼습니다







그리고 MySQL에 연결을 시켜줄 자바 파일을 하나 만듭니다

패키지명 com.cos.test1.config


얇은 지식으로 말씀드리자면 패키지명(유일해야함) = 도메인(유일함)

그러므로 도메인을 거꾸로 쓴거같은 양식이 유일하기때문에 그렇게 작성한다고 하네요



new > class > DBConn.java 생성




DBConn.java

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
package com.cos.test1.config;
 
import java.sql.Connection;
import java.sql.DriverManager;
 
public class DBConn {
    public static Connection getConnection() {
        Connection conn = null;
        //localhost 뒤 = DB이름?
        String url = "jdbc:mysql://localhost:3306/ssar?serverTimezone=Asia/Seoul";
        String username = "ㅇㅎㅇㅀㅇㅀㅇㅀ";
        String password = "ㅇㅎㅇㅎㅎㄱㅎ";
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(url, username, password);
            
            System.out.println("DB 연결성공");
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
 
cs


String url = "jdbc:mysql://localhost:3306/ssar?serverTimezone=Asia/Seoul";
String username = "ㅇㅀㄷㄱㅎㄷㅎr";
String password = "둃ㅇㅀㅇㅀ";


url = 포트번호뒤에 있는 ssar 은 제 계정에있는 데이터베이스 이름입니다. 각자 환경에 맞게 바꿔주세요

username = 사용자 계정 아이디 입니다.

password = 사용자 계정 비밀번호 입니다.




webcontent > new jsp file


select.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
<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="com.cos.test1.config.DBConn"%>
<%@page import="java.sql.Connection"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>찾아보기</title>
</head>
<body>
<%
    String sql = "SELECT * FROM users WHERE id=1";
    Connection conn = DBConn.getConnection();
    PreparedStatement pstmt = 
            conn.prepareStatement(sql);
    ResultSet rs = pstmt.executeQuery();
    
    rs.next();
    int id = rs.getInt("id");
    String username = rs.getString("username");
    String password = rs.getString("password");
    String email = rs.getString("email");
%>
<h3>id : <%=id %></h3>
<h3>username : <%=username %></h3>
<h3>password : <%=password %></h3>
<h3>email : <%=email %></h3>
</body>
</html>
cs

<% %> 로 감싸진 부분에서는 자바코드를 작성해도 되는 곳입니다.


아까 java resource에 만들었던 DBConn을 import 해서 사용하고있습니다.


executeQuery()의 반환값이 Resultset 이기 때문에 새로 변수를 만들어서 저장해줍니다.


rs,next() : 커서를 한 번 이동시켜줍니다 (안하면 안됨)


rs.get@@@ : 새로만든 변수에 get해서 데이터를 넣습니다 .    변수명은 웬만하면 필드명이랑 일치하게 작성합니다 


<%=id %> : 표현식 태그 라고하네요 결과값을 출력합니다.



실행하기전에~~~


크롬으로 바꿔주시고 (알아서 하셈)




프로젝트 폴더를 Run As > Run On Server 를 하신다면 welcome page (기본값 index.jsp , index.html) 을 실행할 것입니다.


그래서 저희는 그냥  select.jsp 를 Run On Server 할겁니다


TomCat 9.0  확인하고 Finish ㄱㄱ







잘 작동하고있네요 다행입니다




1편에서 한 내용


Oracle 사용자 계정만들기 , DB연결하기 , INSERT랑 DELETE 하기




INSERT , DELETE , UPDATE , SELECT (SELECT는 scott 계정에 맞게 작성하였음)

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
 
import config.DBConnection;
import model.Dept;
 
public class MainApp {
 
    public static void main(String[] args) {
//        추가(9);
//        삭제(1);
        찾기(10);
    }
 
    // 함수로 모듈화
    public static void 추가(int id) {
//        String sql = "INSERT INTO test1(id) VALUES("+id+")"; //이렇게하면 인젝션 뚫림
        String sql = "INSERT INTO test1(id) VALUES(?)";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id); // 첫번째 '?' 부분에 id를 넣겠다.
            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
            System.out.println("result : " + result);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
 
    // 함수로 모듈화
    public static void 삭제(int id) {
        String sql = "DELETE FROM test1 WHERE id = ?";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id); // 첫번째 '?' 부분에 id를 넣겠다.
            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
            System.out.println("result : " + result);
//            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
 
    // 함수로 모듈화
    public static void 수정(int id) {
        String sql = "UPDATE test1 SET 4 WHERE id = ?";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id); // 첫번째 '?' 부분에 id를 넣겠다.
            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
            System.out.println("result : " + result);
//            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
 
    // 함수로 모듈화 , 사용자 scott으로 변경해야함
    // return값 Dept
    public static Dept 찾기(int deptno) {
        String sql = "SELECT deptno, dname, loc FROM dept WHERE deptno=?";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, deptno); // 첫번째 '?' 부분에 id를 넣겠다.
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) {
                Dept dept = Dept.builder()
                        .deptno(rs.getInt("deptno"))
                        .dname(rs.getString("dname"))
                        .loc(rs.getString("loc"))
                        .build();
                System.out.println(dept);
                return dept;
//                int deptno2 = rs.getInt("deptno");
//                String dname = rs.getString("dname");
//                String loc = rs.getString("loc");
//                System.out.println(deptno2);
//                System.out.println(dname);
//                System.out.println(loc);
            }
 
//            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
//            System.out.println("result : " + result);
//            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    
    
    // 함수로 모듈화 , 사용자 scott으로 변경해야함
    // return값 Dept
    public static List<Dept> 전체찾기() {
        String sql = "SELECT deptno, dname, loc FROM dept WHERE deptno=?";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
//            pstmt.setInt(1, deptno); // 첫번째 '?' 부분에 id를 넣겠다.
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()) {
                Dept dept = Dept.builder()
                        .deptno(rs.getInt("deptno"))
                        .dname(rs.getString("dname"))
                        .loc(rs.getString("loc"))
                        .build();
                System.out.println(dept);
                
//                return dept;
//                int deptno2 = rs.getInt("deptno");
//                String dname = rs.getString("dname");
//                String loc = rs.getString("loc");
//                System.out.println(deptno2);
//                System.out.println(dname);
//                System.out.println(loc);
            }
 
//            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
//            System.out.println("result : " + result);
//            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
 
cs


모델 먼저 만드세요. [ 만드는이유 : 관리하기 편하기위해서 ? ]


참고로 올리는 scott 스키마 사진


▼Dept 클래스를 모델 패키지에 만든 모습

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package model;
 
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Dept {
    private int deptno;
    private String dname;
    private String loc;
}
 
cs


lombok.jar 다운로드


https://mvnrepository.com/artifact/org.projectlombok/lombok

알아서 빌드패스




이번에 해볼 일 : 전체찾기 (SELECT)


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
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
import config.DBConnection;
import model.Dept;
 
public class MainApp {
 
    // 함수로 모듈화 , 사용자 scott으로 변경해야함
    // return값 List<Dept>
    public static List<Dept> 전체찾기() {
        String sql = "SELECT deptno, dname, loc FROM dept";
        Connection conn = DBConnection.getinstance();
        // Byte Stream (?)
 
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            ResultSet rs = pstmt.executeQuery();
            
            //List 하위에 vector , ArrayList가 있음, 선언은 반복문위에
            List<Dept> listDept = new ArrayList<Dept>();
            while(rs.next()) {
                Dept dept = Dept.builder()
                        .deptno(rs.getInt("deptno"))
                        .dname(rs.getString("dname"))
                        .loc(rs.getString("loc"))
                        .build();
                System.out.println(dept); //toString() 작동 ?
                
                //컬렉션에 담기
                listDept.add(dept);
            }
            return listDept;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    
    public static void main(String[] args) {
//        추가(9);
//        삭제(1);
//        찾기(10);
//        Dept dept = 찾기(10);
        List<Dept> listDept = 전체찾기();
    }
}
 
cs

메서드 전체찾기()만 보면 됩니다.

예제 준비물 : Oracle sqldeveloper , sts tool , ojdbc6.jar


1. sqldeveloper 에 존재하는 데이터베이스에 연동을 할것이기 때문에 사용자계정이 있어야한다.

만들어보자.


먼저 모든 권한을 갖고있는 system 계정에 로그인을 한 뒤 아래 명령어를 실행한다.


-- 앞으로 사용할 사용자이름 , 비밀번호

CREATE USER superuser IDENTIFIED BY 1234; -- 사용자를 만들면 데이터베이스가 같이 만들어짐

GRANT CREATE SESSION TO superuser;

GRANT CREATE TABLE TO superuser;

GRANT CREATE TABLESPACE TO superuser;

GRANT UNLIMITED TABLESPACE TO superuser;



2. Ojdbc6.jar를 다운받아서 buildpath를 한다.


https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6/11.2.0.4 


여기서 다운받으면 아마 될 지도 ?


oracle developer 가있다면 이 경로에 가면 jar파일이 있을것이다. 나는 여기 있는걸 복붙해서 썼다.

C:\oraclexe\app\oracle\product\11.2.0\server\jdbc\lib



Build Path 성공한 사진







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
package config;
 
import java.sql.Connection;
import java.sql.DriverManager;
 
public class DBConnection {
    //커넥션을 리턴하는게 목적입니다
    //PC의 서버에 연결할것입니다
    public static Connection getinstance() {
        Connection conn = null;
        String url = "jdbc:oracle:thin:@localhost:1521:xe"; //고정
        String user = "superuser"; //사용자계정
        String password = "1234";
        
        //OracleDriver 클래스를 메모리에 로드, 알집파일 안에 있음
        //패키지 경로
        try {
            //문자열에 오류가 있을수도있으니 자체적으로 try문을 권장함
            //reflection 메모리에떠있는 타입을 찾아낸다 (getconnection 컨트롤 클릭 해보시오)
            Class.forName("oracle.jdbc.driver.OracleDriver");
            conn = DriverManager.getConnection(url, user, password);
            System.out.println("DB연결 성공이요");
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("연결 실패");
        return null;
    } // end of getinstance 
}
 
cs


문자열에서 오타가 발생하거나 인터넷이 끊겨있지 않은 이상 연결 성공 할것이다.




3. 자바로 데이터 INSERT 해보기


새 클래스를 만들고 메인함수를 만듭니다.


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
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
 
import config.DBConnection;
 
public class MainApp {
 
    public static void main(String[] args) {
        String sql = "INSERT INTO test1(id) VALUES(1)";
        Connection conn = DBConnection.getinstance();
        // Byte Stream  (?)
        
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        
    }
 
}
 
cs


아 지금 보니 id필드를 갖고있는 test1 이라는 테이블이 있어야한다 알아서 만드십쇼



4. 함수로 모듈화

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
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
 
import config.DBConnection;
 
public class MainApp {
 
    public static void main(String[] args) {
        추가(9);
        삭제(1);
    }
    //함수로 모듈화
    public static void 추가(int id) {
//        String sql = "INSERT INTO test1(id) VALUES("+id+")"; //이렇게하면 인젝션 뚫림
        String sql = "INSERT INTO test1(id) VALUES(?)";
        Connection conn = DBConnection.getinstance();
        // Byte Stream  (?)
        
        try {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, id); // 첫번째 '?' 부분에 id를 넣겠다.
            int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
            System.out.println("result : " + result);
//            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    //함수로 모듈화
        public static void 삭제(int id) {
//            String sql = "INSERT INTO test1(id) VALUES("+id+")"; //이렇게하면 인젝션 뚫림
            String sql = "DELETE FROM test1 WHERE id = ?";
            Connection conn = DBConnection.getinstance();
            // Byte Stream  (?)
            
            try {
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1, id); // 첫번째 '?' 부분에 id를 넣겠다.
                int result = pstmt.executeUpdate(); // 변경된 row count를 리턴, 오류 시 -1를 리턴
                System.out.println("result : " + result);
//                pstmt.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
}
 
cs



ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

pstmt를 알고있다면 코드이해에 조금 도움이 될것입니다...

하지만 저는 아직 모르겠네요 알게되면 추가하겠습니다.


coolSMS 이용하기위한 준비물


javaSDK-2,2jar

https://github.com/coolsms/java-sdk/releases



JSON은 COOLSMS 실행에는 필요하지는 않습니당

json-simple-1.1.jar

http://www.java2s.com/Code/Jar/j/Downloadjsonsimple11jar.htm



coolSMS 회원가입후 대시보드(문자메시지 관리페이지) 에서 API KEY 발급받기





public class ExampleSend { public static void main(String[] args) { String api_key = "키입력"; String api_secret = "시크릿키입력"; Message coolsms = new Message(api_key, api_secret); // 4 params(to, from, type, text) are mandatory. must be filled HashMap<String, String> params = new HashMap<String, String>(); params.put("to", "발신번호"); params.put("from", "수신번호"); //무조건 자기번호 (인증) params.put("type", "SMS"); params.put("text", "보낼 메시지를 입력하시오"); params.put("app_version", "test app 1.2"); // application name and version try { //send() 는 메시지를 보내는 함수 JSONObject obj = (JSONObject) coolsms.send(params); System.out.println(obj.toString()); } catch (CoolsmsException e) { System.out.println(e.getMessage()); System.out.println(e.getCode()); } } 

}


class명을 ExampleSend 로 해서 메인함수 내의 코드만 복붙해 간다.

(이클립스 사용시) jar파일이 성공적으로 추가되었다면 Ctrl + Shift + O 를하면 자동으로 import가 될것이다.


JAR파일 추가하는법 (링크)


api_key , api_secert 에 발급받은 key를 대입한다.

발신번호와 수신번호에 각자 원하는 번호를 입력한다.


그러나 우리는 수신번호를 일부러 틀리게 적어서 error를 발생시킬것이다.



try문 안에있는 obj.toString()의 결과가 출력된다

{"group_id":"R2G9fqnb315X6VYf","error_list":{"1":"1062"},"success_count":0,"error_count":1}

key와 value값으로 이루어진 Json 형식의 데이터이다.



이 데이터를 String 클래스의 메서드로 뭔가뭔가 왤케왤케 하게 파싱을 해볼것이다

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
package sms;
 
import lombok.Data;
 
@Data
public class Response {
    String group_id;
    String error_list;
    String success_count;
    String error_count;
 
    static String startEndTrim(String str) {
        return str.substring(str.indexOf("{")+1,str.lastIndexOf("}"));
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String data = "{\"group_id\":\"R2GtX62GMhUpnaem\",\"error_list\":{\"1\":\"1062\"},\"success_count\":0,\"error_count\":1}\r\n"
                + "";
        System.out.println(data);
        System.out.println("----------------------------------");
        data = data.replaceAll("\"""");
        data = startEndTrim(data);
 
        Response res = new Response();
        System.out.println(data);
 
        String s1[] = data.split(",");
        for (int i = 0; i < 4; i++) {
            if(s1[i].contains("{")||s1[i].contains("}")) {
                s1[i] = s1[i].replace("{""");
                s1[i] = s1[i].replace("}""");
                System.out.println("s1[" + i + "] " + s1[i]);
            }else
                System.out.println("s1[" + i + "] " + s1[i]);            
        }
 
        for (int i = 0; i < s1.length; i++) {
            String s2[] = s1[i].split(":");
            for (int j = 0; j < s2.length; j++) {
                System.out.println("s2[" + i + "]" + "[" + j + "] " + s2[j]);
            }
        }
 
        System.out.println(res);
    }
 
}
 
cs




출력 결과

{"group_id":"R2GtX62GMhUpnaem","error_list":{"1":"1062"},"success_count":0,"error_count":1}


----------------------------------

group_id:R2GtX62GMhUpnaem,error_list:{1:1062},success_count:0,error_count:1

s1[0] group_id:R2GtX62GMhUpnaem

s1[1] error_list:1:1062

s1[2] success_count:0

s1[3] error_count:1


s2[0][0] group_id

s2[0][1] R2GtX62GMhUpnaem

s2[1][0] error_list

s2[1][1] 1

s2[1][2] 1062

s2[2][0] success_count

s2[2][1] 0

s2[3][0] error_count

s2[3][1] 1


대부분의 RAM의 절반은 OS가 사용한다.


나머지 반을 JVM이 관리를 해주게 되는데, 프로그램 내에 사용되지 않는 메모리(쓰레기)는 가비지 컬렉션 기능에의해 자동으로 회수된다.


static 은 사용하기전에도 메모리에 이미 존재하는 자료 ?

프로그램이 실행될 때 부터 종료될 때 까지 메모리에 남아있게 된다. 전역변수로 지정하고싶을 때 static 을 사용한다.


heap 은 잠깐 메모리에서 사용하고 사라지는 자료 ?

참조형 데이터 타입을 갖는 객체,배열 들은 Heap영역에 데이터가 저장된다. 이때 변수(객체변수, 참조변수)는 Stack영역의 공간에서 실제 데이터가 저장된 Heap영역의 참조값을 new 연산자를 통해 리턴받는다.

다시 말하면 실제 데이터를 갖고있는 Heap영역의 참조 값을 Stack영역의 객체가 갖고있다. (매핑 테이블 ? 이야기인듯)

이렇게 리턴 받은 참조 값을 갖고 있는 객체를 통해서만 해당 인스턴스를 핸들 할 수 있다.


출처 : http://blog.naver.com/heartflow89/220954420688



함수가 호출되면, 함수 내부를 stack이라고 하는데 함수 호출시점부터 함수가 종료될 때 까지 메모리가 유지된다.


.class파일을 실행하면 static부분을 메모리에 로드한다.

static공간에 있는 main()함수를 호출하고

이때 stack이 생성되는데 이 stack의 이름이 main이다.

main은 stack이 종료되면 프로그램도 종료된다.


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

String str = "안녕하세요";


자바에서 String은 기본자료형이 아니라 클래스이다. (파스칼표기법)

크기를 정할수없는 타입(String 에 어떤크기의 변수가 들어갈지 모른다) = 레퍼런스 타입 (참조한다)

안녕하세요 가 저장이된것이 아니라 안녕하세요의 '주소'가 저장되어있다.


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

int[] n = {1,2,3}

메모리에서 연속된 세 공간이 있는 자리를 찾아서 시작주소만 저장한다. (배열의 크기를 알고있기 때문에 끝주소가 자동으로 계산이 된다)


나는 백틱을 알기 전에는 자바스크립트 에서 문자열과 변수를 함께 다룰때 


print("Hello" + 어떤변수 + "world");


이렇게 큰따옴표와 + 연산을 이용하여 작성도 힘들고 보기도 불편한 작업을 하고 있었다.


하지만 백틱이라는 엄청난,,, 도구를 알게되었다. 자바스크립트에서만 사용할 수 (?) 있다는게 좀 아쉬운거같다


참고로 백틱은 키보드 1 왼쪽에있는 물결표시 버튼에 있습니다.   ( ` ) << 작은따옴표 아님




예제를 보면서 코드설명 하겠습니다


여기서 삭제 버튼을 누르면






문자열이 치환 되어버립니다




소스 코드

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
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <title>Document</title>
    <style>
        #comment-list{
            border: 5px solid black;
            padding: 10px;
        }
        #comment-list span{
            display: inline-block;
            border: 2px solid red;
            padding: 10px;
        }
    </style>
</head>
<body>  
    <input type="text" id="input-comment"/>
    <button onclick="addComment()">댓글 작성</button>
    <hr/>
    <div id="comment-list">
        <div id="item-1">
            <span id="comment-1">댓글1</span>
            <span id="comment-delete-1" onclick="deleteComment(1)">삭제</span>
        </div>
        <div id="item-2">
            <span id="comment-2">댓글2</span>
            <span id="comment-delete-2" onclick="deleteComment(2)">삭제</span>
        </div>
    </div>
</body>
<script>
    //append, prepend, after, before
    no = 3;
    function addComment(){
        //get
        var comment = $("#input-comment").val();
 
        //set  
        //문자열과 변수를 같이 사용해야할 때 편리한 도구 >> ` <<(backtick, 키보드 1 왼쪽 버튼)
         /*
            1. 백틱이 필요한 라인을 그대로 복붙한다.
            <span id="comment-1">댓글1</span>
            <span id="comment-delete-1" onclick="deleteComment(1)">삭제</span>
            2. 변수로 바꾸어야 할 부분을 ${변수}로 직접 수정한다.
            <span id="comment-${no}">댓글1</span>
            <span id="comment-delete-${no}" onclick="deleteComment(${no})">삭제</span>
        */
 
        //한 줄에 쓰면 이렇게 됨
        // $("#comment-list").prepend(`<div id=item-${id}> <span id=comment-${id}>${comment}</span> 
        // <span id=comment-delete-${id} onclick="deleteComment(${id})">삭제</span> </div>`);
 
        //한 줄에 쓰는건 보기 안좋으니 변수에 문자열 추가 하는방식으로
        var commentitem = `<div id="item-${no}">`;
        commentitem = commentitem + `<span id="comment-${no}">${comment}</span>`;
        commentitem = commentitem + `<span id="comment-delete-${no}" onclick="deleteComment(${no})">삭제</span>`;
        
 
        $("#comment-list").prepend(commentitem);
        no = no + 1;
        
        // $("#input-comment").val();
    }
 
    function deleteComment(id){
        // $("#item-"+id).remove();
        $(`#comment-${id}`).text("삭제된 댓글 입니다");
    }
</script>
</html>
cs



1. 백틱이 필요한 라인을 그대로 복붙한다.
<span id="comment-1">댓글1</span>
<span id="comment-delete-1" onclick="deleteComment(1)">삭제</span>

2. 변수로 바꾸어야 할 부분을 ${변수}로 직접 수정한다.
<span id="comment-${no}">댓글1</span>
<span id="comment-delete-${no}" onclick="deleteComment(${no})">삭제</span>


addComment() 간략설명

no 라는 숫자변수를 함수 실행(댓글이 작성됨)마다 1씩 증가되기때문에 댓글을 번호로 모두 구별할수 있어서

삭제버튼을 누르면 해당 댓글만 삭제가 가능해집니다.


근데 핵심은 백틱을 사용해서 보기도좋고 작성하기도 편하다는것

'국비지원 Spring프레임워크 > JQuery' 카테고리의 다른 글

제이쿼리(Jquery) 개요  (0) 2020.11.09

jQuery를 사용하면 아주 간편하게 HTML 요소를 선택하고, 그렇게 선택된 요소에 손쉽게 특정 동작을 설정할 수 있다.

$(선택자).동작함수();

달러($) 기호는 jQuery를 의미하고, jQuery에 접근할 수 있게 해주는 식별자이다.
선택자를 이용하여 원하는 HTML 요소를 선택하고, 동작 함수를 정의하여 선택된 요소에 원하는 동작을 설정한다.

선택자에는 클래스, ID 도 들어갈 수 있다.



동작함수 정리 블로그

https://soft91.tistory.com/9



자주쓰는 함수

text() , html(), arrt(), val()



영화포스터 예제



소스코드

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <title>영화 포스터</title>
    <style>
        .container{
            margin: 0 auto;
            width: 80%;
            display: flex;
            text-align: center;
            justify-content: center;
        }
        .movie-item{
            padding: 10px;
            width: 300px;
            height: 500px;
            background-color: gray;
            border: 1px solid black;
            box-shadow: 0 0 3px 3px black;
        }
    </style>
</head>
<body>
    <h1>영화포스터</h1>
    <button onclick="getMovie()">영화 불러오기</button>
    <div class="container">
        <div class="movie-item">
            <div id="poster">
                <img id="movie-poster" src="#" width="290px" height="300px" alt="">
            </div>
            <div id="title">
                제목임시
            </div>
            <div id="rating">
                평점임시
            </div>
        </div>
    </div>
 
<script>
    function getMovie(){
        var movie = {
            poster: "https://t1.daumcdn.net/movie/99a4d42206a028764e86c5bff8cf0021db985223",
            title: "도굴",
            rating: 9.1
        }
 
        $("#movie-poster").attr("src",movie.poster);
        $("#title").text(movie.title);
        $("#rating").text(movie.rating);
        console.log("버튼테스트");
    }
</script>
</body>
</html>
cs

function 이 핵심 내용

'국비지원 Spring프레임워크 > JQuery' 카테고리의 다른 글

자바스크립트 백틱(backtick)  (0) 2020.11.10

+ Recent posts