20211214 코로나 음성, 오랜만에 비대면

DUUUPPAAN·2021년 12월 14일
0

Spring_Framework

목록 보기
7/19

·코로나 검사 결과

-다행히도 어제 추운 야외에서 2시간 넘게 기다려서 받은 코로나 검사 결과 음성이 나왔다. 반의 다른 분들도 전부 음성이 나와서 다시 학원에 갈 수 있게 되었다. 물론 오늘은 비대면 수업이었지만... 사실 내일도 비대면 수업을 하는 날인데, 오늘 집에서 해보니 집중이 조금 어려운 부분이 확실히 있었다. 그래서 대면 수업을 하러 내일 나갈 생각이다. 조금 걱정되는 부분이 없다고는 할 수 없지만, 그래도 당장 집중력을 높여서 코딩을 하는 것이 더 중요하다고 생각한다.

·팀 모임

-원래라면 오늘은 우리팀이 대면 수업을 나가서 강의실에서 서로 회의를 하고 있어야 했다. 부트스트랩으로 만든 사이트 메인화면을 참고해서 어떤 템플릿으로 프로젝트를 해나갈지를 결정해야 하는 시점이었는데, 코로나때문에 대면으로 할 수 없는 상황이었다. 그래서 줌으로 회의방을 만들어서 원격으로라도 각자의 템플릿을 보고 프로젝트에 사용할 템플릿을 투표로 정했다. 앞으로 정할 내용들이 산더미지만, 일단은 이 정도 수준에서 팀 회의를 종료했다.

·게시판 글쓰기

-오늘은 전에 했던 것처럼 게시판 글쓰기에 대해 진행했다. 물론 이번에는 첨부파일과 댓글, 대댓글이 있으니 더 복잡해졌지만, 그래도 해당 부분을 진행했다.

-기존의 list.jsp의 jQuery 영역에 각각의 버튼을 눌렀을 때 어떻게 되는지에 대해 처리했다.

	$(document).ready(function(){
		$("#btnSearch").on("click", function(){
			//새로 조회버튼을 누를 때에는 신규로 넣은 값을 가져가야 함.
			document.bbsForm.hiBbsSeq.value = "";
			document.bbsForm.searchType.value = $("#_searchType").val();
			document.bbsForm.searchValue.value = $("#_searchValue").val();
			//조회를 했을 때 무조건 1페이지로 가야 함. 결색 결과가 몇페이지까지 나올지 모르니깐
			document.bbsForm.curPage.value = 1;
			document.bbsForm.action = "/board/list";
			document.bbsForm.submit();
		});
		
		$("#btnWrite").on("click",function(){
			document.bbsForm.hiBbsSeq.value = "";
			document.bbsForm.action = "/board/writeForm";
			document.bbsForm.submit();
		});
		
		
	});
	
	function fn_view(bbsSeq)
	{
		document.bbsForm.hiBbsSeq.value = bbsSeq;
		//searchType, searchValue는 안가져가나요?
		//조회 버튼을 안눌렀다면 굳이 가져갈 필요가 없음
		//조회 버튼을 눌렀다면 히든 타입 bbsForm에는 이미 값이 들어가 있음
		document.bbsForm.action = "/board/view";
		document.bbsForm.submit();
	}

-여기서 중요한 것은 조회항목과 검색내용 그리고 현재 페이지를 적절한 때에 가져가고 가져와야 한다는 것이다.

-위에서 submit을 "/board/writeForm"이 경로로 해줬다. 그러면 당연하게도 먼저 컨트롤러부터 찾아가게 된다. 왜냐하면 .jsp인 writeForm을 보여줄 것이기 때문이다.(글쓰기 페이지라고 생각하면 된다.)


	//글쓰기 페이지, 게시판 글쓰기
	@RequestMapping(value="/board/writeForm")
	public String writeForm(ModelMap model, HttpServletRequest request, HttpServletResponse response) 
	{
		//쿠키 값
		String cookieUserId = CookieUtil.getHexValue(request, AUTH_COOKIE_NAME);
		
		//글 쓰고 돌아갈 때 서치타입 벨류 현재페이지 세팅이 필요함
		String searchType = HttpUtil.get(request, "searchType", "");
		String searchValue = HttpUtil.get(request, "searchValue", "");
		long curPage = HttpUtil.get(request, "curPage", (long)1);
		
		//기본적으로 사용자 이름과 이메일 주소는 보여줬음
		//사용자 정보 조회
		//유저 객체 정의를 해줘야 함 위쪽에
		User user = userService.userSelect(cookieUserId);
		
		model.addAttribute("searchType", searchType);
		model.addAttribute("searchValue", searchValue);
		model.addAttribute("curPage", curPage);
		
		model.addAttribute("user", user);
		
		return "/board/writeForm";
	}

-writeForm.jsp에서 user객체에 들어있는 값 중, 이름과 이메일을 사용할 것이기 때문에 ModelMap을 사용해서 해당 값을 사용할 수 있도록 해준다. 그러면 다음과 같이 인풋박스에서 user객체를 사용할 수 있게 된다.

<input type="text" name="userName" id="userName" maxlength="20" value="${user.userName}" style="ime-mode:active;" class="form-control mt-4 mb-2" placeholder="이름을 입력해주세요." readonly />
<input type="text" name="userEmail" id="userEmail" maxlength="30" value="${user.userEmail}" style="ime-mode:inactive;" class="form-control mb-2" placeholder="이메일을 입력해주세요." readonly />

-이제 글쓰기 버튼을 눌렀을 때에 대한 처리를 해줘야 한다. 우선, 중요한 점은, 글쓰기 버튼을 눌렀을 때, 두번 눌려서 db에 정보가 두번 입력되지 않도록 버튼이 한번 눌리면 비활성화 되도록 해줘야 한다는 점이다. 물론, 입력값이 올바르지 않았을 때는 경고창을 띄우고, 다시 활성화 시켜줘야 한다. 또한 이전과는 달리 JSON방식으로 속성과 값이 쌍으로 보내지는것이 아니라, form태그의 값을 전체 보낼 것이라서 전과 ajax통신하는 법이 조금 다르다.

$(document).ready(function(){
		
		//우선 페이지 로딩되었을 때 해당 란에 커서가 가게 해야 함.
		$("#hiBbsTitle").focus();
		
		$("#btnList").on("click",function(){
			document.bbsForm.action = "/board/list";
			document.bbsForm.submit();			
		});
		
		$("#btnWrite").on("click",function(){
			$("#btnWrite").prop("disabled", true);
			//클릭 후 비활성화하기
			//똑같은걸 두번 눌러서 서버에 두개 들어가는 것을 방지하기 위함.
			
			if($.trim($("#hiBbsTitle").val()).length <=0 )
			{
				//값이 없음
				alert("제목을 입력하세요.");
				$("#hiBbsTitle").val("");
				$("#hiBbsTitle").focus();
				
				$("#btnWrite").prop("disabled", false);
				return;
			}
			
			if($.trim($("#hiBbsContent").val()).length <=0)
			{
				alert("내용을 입력하세요");
				$("#hiBbsContent").val("");
				$("#hiBbsContent").focus();
				
				$("#btnWrite").prop("disabled", false);
				return;
			}
			
			//ajax 통신으로 갈 것!
			//기존에는 키와 속성을 쌍으로 보냈음. 근데 이제는 form객체를 통으로 보낼 것
			//id가 writeForm이라고 하는 애의 0번째 값 여러개의 writeForm이 있을 수 있으니깐
			var form = $("#writeForm")[0];
			//폼 자체의 타입으로 보내기 위한 객체 생성.
			var formData = new FormData(form);
			
			$.ajax({
				type:"POST",
				enctype:'multipart/form-data',
				url:"/board/writeProc",
				data:formData,
				processData:false,		//formData를 String으로 변환하지 않음
				contentType:false,		//content-type 헤더가 multipart/form-data로 전송한다는 것
				cache:false,
				timeout:600000,
				beforeSend:function(xhr)
				{
					xhr.setRequestHeader("AJAX", "true");
				},
				success:function(response)
				{
					if(response.code == 0)
					{
						alert("게시물이 등록되었습니다.");
						//리스트 페이지로 돌아갈 때는 가져온 값을 가져가야 하지만,
						//글쓰기를 눌렀을 때는, 가져온 값을 가져가면 내가 쓴 글이 안보임
						//그래서 넣어줘서 보내지 않음
						location.href = "/board/list";
					}
					else if(response.code == 400)
					{
						alert("파라미터값이 올바르지 않습니다.");
						//버튼 활성화 처리
						$("#btnWrite").prop("disabled", false);
					}
					else
					{
						alert("게시물 등록 중 오류가 발생했습니다.");
						$("#btnWrite").prop("disabled", false);
					}
				},
				error:function(error)
				{
					icia.common.error(error);
					alert("게시물 등록 중 오류가 발생했습니다. Ajax");
					$("#btnWrite").prop("disabled", false);
				}
			});
			//ajax 통신 끝
		});
		
	});

-enctype을 위해 폼 태그의 정의 부분을 같이 써놓겠다.

<form name="writeForm" id="writeForm" method="post" enctype="multipart/form-data">

-insert문을 진행하기 전에, 이번엔 첨부파일과 댓글 그리고 대댓글까지 있다. 그래서 대댓글의 부모 댓글을 알기 위한 컬럼이 하나 더 필요하다. 그래서 컬럼 하나를 추가해줬다.

ALTER TABLE TBL_HIBOARD ADD HIBBS_PARENT NUMBER(12) DEFAULT 0 NOT NULL;

COMMENT ON COLUMN TBL_HIBOARD.HIBBS_PARENT IS'부모 게시물 번호';

또한 전에 HiBoard.java는 작성했지만, HiBoardFile.java는 작성하지 않았기에 해당 부분을 작성한다.

package com.icia.web.model;

import java.io.Serializable;

public class HiBoardFile implements Serializable
{
	private static final long serialVersionUID = 1L;
	
	private long hiBbsSeq;		//게시물 번호 
	private short fileSeq;		//게시물 번호별 파일 번호
	private String fileOrgName;	//원본 파일명
	private String fileName;	//저장될 파일명
	private String fileExt;		//파일 확장자 명
	private long fileSize;		//파일 크기(byte단위)
	private String regDate;		//등록일

	public HiBoardFile() 
	{
		hiBbsSeq = 0;
		fileSeq = 0;
		fileOrgName = "";
		fileName = "";
		fileExt = "";
		fileSize = 0;
		regDate = "";
	}

	public long getHiBbsSeq() {
		return hiBbsSeq;
	}

	public void setHiBbsSeq(long hiBbsSeq) {
		this.hiBbsSeq = hiBbsSeq;
	}

	public short getFileSeq() {
		return fileSeq;
	}

	public void setFileSeq(short fileSeq) {
		this.fileSeq = fileSeq;
	}

	public String getFileOrgName() {
		return fileOrgName;
	}

	public void setFileOrgName(String fileOrgName) {
		this.fileOrgName = fileOrgName;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getFileExt() {
		return fileExt;
	}

	public void setFileExt(String fileExt) {
		this.fileExt = fileExt;
	}

	public long getFileSize() {
		return fileSize;
	}

	public void setFileSize(long fileSize) {
		this.fileSize = fileSize;
	}

	public String getRegDate() {
		return regDate;
	}

	public void setRegDate(String regDate) {
		this.regDate = regDate;
	}
}

-크게 어려울 것 없이 테이블의 컬럼을 보면서 각각의 변수 및 초기값을 세팅해준다.

-또한 HiBoard.java에도 추가된 컬럼 부분을 추가해주고, 자신에게 종속되는 hiBoardFile 객체도 선언해준다.

//하이보드 파일은 하이보드에 종속적인 존재이기 때문에
	private HiBoardFile hiBoardFile; //첨부파일에 대한 정보
	
	private long hiBbsParent;	//부모 게시물 번호 나중에 추가한 컬럼이라 밑에 있는 것 뿐

-물론 생성자도 정의해준다.

...
{
...
hiBoardFile = null;
		//얘는 객체라서 null로 초기값을 줌
		hiBbsParent = 0;
}

-getter와 setter도 정의해줘야 하지만 어차피 이클립스의 기능을 이용하면 되기 때문에 굳이 상세하게 적진 않겠다.

-게시글을 추가하는 쿼리문을 작성했다. 아직 첨부파일이나 댓글에 대한 것은 배제한 상태로 구현했다.

--게시물 인서트
--여기서 HIBOARD와 HIBOARDFILE은 각각이지만 같이 처리되거나 안되면 둘 다 철회하도록 해야함
INSERT INTO TBL_HIBOARD (
    HIBBS_SEQ,
    USER_ID,
    HIBBS_GROUP,
    HIBBS_ORDER,
    HIBBS_INDENT,
    HIBBS_TITLE,
    HIBBS_CONTENT,
    HIBBS_READ_CNT,
    REG_DATE,
    HIBBS_PARENT
) VALUES (
    SEQ_HIBOARD_SEQ.NEXTVAL,
    'test',
    0,
    0,
    0,
    'testTitle',
    'testContent',
    0,
    SYSDATE,
    0
);

-우선 값들을 임의로 넣어보고 해당 쿼리가 정상 작동하는지부터 확인한 후에 HiBoardDao.xml에 해당부분을 정의해줬다.

<!-- 게시물 등록 시작 -->
<!-- 메인 글에 대한 인서트이기 때문에 parent는 0으로 맞춤 -->
<insert id="boardInsert" parameterType="com.icia.web.model.HiBoard">

<!-- 선행 처리기(시퀀스 번호 생성 시작) -->
<!-- keyProperty에 있는 것은 위에 파라미터 타입의 객체에 있는 변수명과 같아야 함. 저기다가 담을 것이라는 뜻. -->
<selectKey resultType="long" keyProperty="hiBbsSeq" order="BEFORE">
	SELECT SEQ_HIBOARD_SEQ.NEXTVAL FROM DUAL	
</selectKey>
<!-- 선행 처리기(시퀀스 번호 생성 종료)-->

INSERT INTO TBL_HIBOARD (
    HIBBS_SEQ,
    USER_ID,
    HIBBS_GROUP,
    HIBBS_ORDER,
    HIBBS_INDENT,
    HIBBS_TITLE,
    HIBBS_CONTENT,
    HIBBS_READ_CNT,
    REG_DATE,
    HIBBS_PARENT
) VALUES (
    #{hiBbsSeq},
    #{userId},
    #{hiBbsSeq},
    #{hiBbsOrder},
    #{hiBbsIndent},
    #{hiBbsTitle},
    #{hiBbsContent},
    0,
    SYSDATE,
    0
)
</insert>
<!-- 게시물 등록 끝 -->

-여기서 좀 의아한 부분이 있을 수 있다. 바로 이 부분이다.

<selectKey resultType="long" keyProperty="hiBbsSeq" order="BEFORE">
	SELECT SEQ_HIBOARD_SEQ.NEXTVAL FROM DUAL	
</selectKey>

이 부분은 밑의 쿼리가 실행되기 전에 값을 받아와서 넣어주는 역할을 한다. 시퀀스 번호를 받아와서 hiBbsSeq라는 변수에 넣어준다. 그냥 바로 시퀀스명.NEXTVAL을 쓰면 안되는 것이냐고 할 수 있지만, 안된다. 왜냐하면 hiBbsGroup은 게시물인 경우 본인의 게시물 번호, 즉, 입력할 때 부여받은 시퀀스 번호이기 때문에 NEXTVAL을 쓰면 게시물 번호와 그룹번호가 달라지는 현상이 발생할 수 있다. 그래서 시퀀스 값이 바뀌지 않도록 변수에 해당 값을 담아서 사용해준다.

-Service에서 메소드를 호출하기 위해서 추상메소드를 정의해준다. HiBoardDao.java에 정의한다.

	//게시물 등록(메인 게시물)
	public int boardInsert(HiBoard hiBoard);

-이제 서비스를 정의해줘야 한다. 그런데 주의할 점은, 게시물과 게시물 첨부파일에 대한 입력은 두개의 쿼리문으로 진행되지만, 그 과정을 하나로 묶어줘야 한다. 왜냐하면 첨부파일을 올리다가 에러가 나거나 하는경우가 있다면, 해당 첨부파일을 올린 게시글에 대한 insert도 철회되어야 한다. 이걸 하나의 트랜잭션으로 묶어서 처리한다고 한다. 트랜잭션은 하나의 과정이며, 커밋부터 다음 커밋까지의 과정이라고 생각해도 된다. 그래서 전과는 서비스가 조금 다르다. 예외처리를 서비스에서 하지 않고 해당 서비스를 호출한 곳에서 해준다.

//게시물 등록(메인 게시물)
   //원래는 하나의 트랜젝션으로 HiBoard와 HiBoardFile이 각각 입력이 아니라
   //HiBoardFile이 입력되다가 중단되면 HiBoard도 다 돌려놔야 됨.
   //그래서 트랜젝션 처리를 해줄 것.
   @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
   public int boardInsert(HiBoard hiBoard) throws Exception 
   {
	   //얘를 사용하는 곳에 예외처리를 넘김.
	   int count = 0;
	   
	   //얘는 예외처리를 여기서 안함. 왜? 트랜젝션으로 묶어야 하기 때문에.
	   count = hiBoardDao.boardInsert(hiBoard);
	   
	   //게시물이 정상적으로 등록되면, 첨부파일이 있다면 첨부파일 등록
	   
	   return count;

기존에는 try~catch문을 사용했지만, 컨트롤러쪽에서 예외처리를 해주는 것이 더 합리적이기 때문에 예외를 던진다. 또한, 해당 부분이 트랜잭션이라는 것을 알려주는 어노테이션도 기입해준다.

//글쓰기 처리, 게시물 등록
	@RequestMapping(value="/board/writeProc", method=RequestMethod.POST)
	@ResponseBody
	public Response<Object> writeProc(MultipartHttpServletRequest request, HttpServletResponse response)
	{
		Response<Object> ajaxResponse = new Response<Object>();
		
		String cookieUserId = CookieUtil.getHexValue(request, AUTH_COOKIE_NAME);
		String hiBbsTitle = HttpUtil.get(request, "hiBbsTitle", "");
		String hiBbsContent = HttpUtil.get(request, "hiBbsContent", "");
		
		//첨부파일에 대한 것을 받을 것.
		//write.jsp에서 보낸 파일이 아래의 아이디임
		FileData fileData = HttpUtil.getFile(request, "hiBbsFile", UPLOAD_SAVE_DIR);
		//뒤에는 파일 경로에 대한 부분
		
		//브라우저에서 값이 있냐없냐를 넘겨주지만
		//경로명을 알면 바로 들어오는 경우가 있음
		//그래서 해당 부분에 대한 예외 처리
		if(!StringUtil.isEmpty(hiBbsTitle) && !StringUtil.isEmpty(hiBbsContent)) 
		{
			//둘 다 값이 있으면
			//HiBoard 객체에 각각의 값들을 넣어서 적용.
			HiBoard hiBoard = new HiBoard();
			hiBoard.setUserId(cookieUserId);
			hiBoard.setHiBbsTitle(hiBbsTitle);
			hiBoard.setHiBbsContent(hiBbsContent);
			
			//첨부파일에 대한 조건
			//이미 클라이언트에서 fileData에 값을 넣어놨음
			//넘어온 값이 null이 아니고, 파일 사이즈가 존재하는 경우에만 실행
			if(fileData != null && fileData.getFileSize()>0) 
			{
				HiBoardFile hiBoardFile = new HiBoardFile();
				
				hiBoardFile.setFileName(fileData.getFileName());
				hiBoardFile.setFileOrgName(fileData.getFileOrgName());
				hiBoardFile.setFileName(fileData.getFileName());
				hiBoardFile.setFileSize(fileData.getFileSize());

				//서비스는 하나를 콜할 것임. 해당 서비스에서 hiboard테이블에 인서트 시키고
				//그제서야 하이보드파일을 인서트 시키도록 할 것임.
				
				//현재 값을 넣은 객체의 주소값을 
				//하이보드 내에 있는 하이보드파일 객체와 같은 곳을 바라보게 해줌.
				//왜냐면 hiBoardFile은 지역변수이기 때문에 해당 if 부분을 벗어나면 사라짐
				//그래서 주소값을 일치시켜주는 것임, 그래야 밖에서 사용할 수 있음
				hiBoard.setHiBoardFile(hiBoardFile);
			}
			
			//게시물 또는 게시물 파일 첨부 적용에 대한 코드 작성할 부분 표시
			
			//여기에 예외처리를 넘겼음.
			//이 부분은 트랜젝션으로 처리할 것이라서, 이건 이클립스가 오라클에게 트랜젝션이라고 알려주는 것.
			try 
			{
				if(hiBoardService.boardInsert(hiBoard)> 0) 
				{
					//정상 처리, 처리건수가 0이 아니여서
					ajaxResponse.setResponse(0, "Success");
				}
				else 
				{
					ajaxResponse.setResponse(500, "Internal Server Error");
				}
			}
			catch(Exception e) 
			{
				logger.error("[HiBoardController] /board/writeProc Exception", e);
				ajaxResponse.setResponse(500, "Internal Server Error");
			}
		}
		else 
		{
			//값이 없음
			ajaxResponse.setResponse(400, "Bad Request: No parameter");
		}
		
		return ajaxResponse;
	}

-여기서 주의할 점은 바로 매개변수이다. 폼태그 전체를 보내기 때문에 받는 객체도 HttpServletRequest가 아니라 MultipartHttpServletRequest객체로 받는다.

-파일 첨부에 대한 부분도 추가해야 하기 때문에 이 부분을 주의해서 적어줘야 한다.

//첨부파일에 대한 것을 받을 것.
		//write.jsp에서 보낸 파일이 아래의 아이디임
		FileData fileData = HttpUtil.getFile(request, "hiBbsFile", UPLOAD_SAVE_DIR);
		//뒤에는 파일 경로에 대한 부분

-이 부분은 예외처리와 함께 각각의 값들을 세팅해주는 부분이어서 여러번 봐야 한다. 특히 hiBoard.setHiBoardFile(hiBoardFile);이 부분이 핵심인데, 이 부분때문에 굳이 hiBoard객체에 hiBoardFile을 선언해준 것이다. 왜냐하면 해당 if문에서 정의된 부분이기 때문에 if문을 벗어나면 해당 hiBoardFile을 사용할 수 없다. 그래서 hiBoard의 hiBoardFile의 주소값을 위의 값과 일치시켜준다.

·성공적인 게시물 등록 그러나

-지금한 부분은 기존보다 조금 추가된 내용이 있긴 하지만 그래도 어느정도 이해가 되는 선에서 행해졌다. 그러나 내일 진행할 내용은 첨부파일과 댓글 대댓글에 대한 부분이기 때문에 전과는 다른 난이도가 될 것으로 예상된다. 그래서 정말 집중 또 집중해야 되는 부분이 될 것이다. 오늘 진행한 코드를 더 숙달해서 좀 더 익숙해진 후에 내일 코드를 보면 더 쉽게 받아들일 수 있을 것 같다. 그래서 지금부터 계속 보고 또 보고 보고 또 보고 계속 보다가 운동하고 잠을 청하려 한다. 오늘도 고생했고 파이팅! 대면수업의 이점을 내일 다시 누려보자!

profile
비전공자란 이름으로 새로운 길을 가려 하는 신입

0개의 댓글