20211209 스프링 회원정보 수정

DUUUPPAAN·2021년 12월 9일
0

Spring_Framework

목록 보기
4/19

·이번엔 정석대로!

-어제 진행한 회원가입은 사실 정석대로 진행한 것은 아니었다. 왜냐하면 정석대로라면 화면부터 완성이 되기 보다는 제공할 서비스를 고려해서 DB부터 진행하는 경우가 많기 때문이다.(특히, 개발자라면) 보통 디자인은 디자인을 하는 쪽에서 하기 때문에 더더욱 개발자쪽에서 이런 작업을 한다면, 프론트단이 아니라 서버단에서부터 구축을 시작하는 것이 정석이라고 하셨다. 그러나 지금까지는 화면이 보여야 이해력 부분에서 더 쉽기 때문에 화면부터 구축을 했다. 사실 어제보다 나는 오늘의 진행 순서가 더 이해가 쉽다. 물론 어제 화면부터 해봐서 그런건지는 몰라도, 서버쪽부터 와야 서버에 정의된 쿼리들을 어떤식으로 사용했는지가 보이기 때문에 서버쪽에서부터 오는 것이 내 입장에서는 더 정리되고 편한 느낌이었다. 교수님이 굉장히 센스가 있다고 하셨는데, 사실 센스라기 보다는 앞부터 오면 구현이 안된 메소드를 호출하는 방식이라 진짜 이해가 안되었던 것이다. 조금 잘난척하는 느낌이 들었을 것 같아서 조금 걱정이지만, 진짜 나는 오늘 수업이 훨씬 더 이해가 잘 되었다. 센스가 아니라 그냥 구현되지 않는 것을 쓰는 것이 너무 이상했기 때문이다.

·쿼리문부터!

-회원정보 수정은 당연하게도 UPDATE를 사용해서 진행했다.

<update id="userUpdate" parameterType="com.icia.web.model.User">

UPDATE TBL_USER
   SET USER_PWD = #{userPwd},
       USER_NAME = #{userName},
       USER_EMAIL = #{userEmail}
 WHERE USER_ID = #{userId}
 
</update>

-업데이트의 리턴타입은 int이기 때문에 resultMap은 사용하지 않아도 된다.

-.xml에 쿼리를 작성했으면 해당 쿼리문의 id와 똑같은 추상메소드도 정의해줘야 한다. 오버라이딩 하는 것이나 마찬가지이기 때문이다.

	public int userUpdate(User user);

-위와 같이 UserDao.java 파일에 추상메소드를 정의해주었다.
-이제 해당 메소드를 호출하는 UserService에서 쿼리문을 실행하는 실제 로직을 구현해준다.

	//회원 정보 수정
	public int userUpdate(User user) 
	{
		int count = 0;
		
		try 
		{
			count = userDao.userUpdate(user);
		}
		catch(Exception e) 
		{
			logger.error("[UserService] userUpdate Exception", e);
		}
		
		return count;
	}

-구현이 다 됐다면, 이제 컨트롤러에서 요청에 대한 처리를 서비스를 통해 처리하고 응답해주는 코드를 작성한다.

//회원정보 수정
		  @RequestMapping(value="/user/updateProc", method=RequestMethod.POST)
		  @ResponseBody
		  public Response<Object> updateProc(HttpServletRequest request, HttpServletResponse response)
		  {
			     Response<Object> ajaxResponse = new Response<Object>();
			     
			     String CookieUserId = CookieUtil.getHexValue(request, AUTH_COOKIE_NAME);
			     
			     String userPwd = HttpUtil.get(request, "userPwd");
				 String userName = HttpUtil.get(request, "userName");
				 String userEmail = HttpUtil.get(request, "userEmail");
				 
				 
				 if(!StringUtil.isEmpty(CookieUserId)) 
				 {
				    //쿠키유저 아이디가 null이 아닐 때.
					 User user = userService.userSelect(CookieUserId);
				
					if(user != null) 
					{
					   //유저 정보가 있을 때
					   if(StringUtil.equals(user.getStatus(), "Y")) 
					   {
					      //회원이 정지된 회원이 아니여야 수정이 가능함.
					      if(!StringUtil.isEmpty(userPwd) && !StringUtil.isEmpty(userName) && !StringUtil.isEmpty(userEmail)) 
					      {
					         //입력값이 있냐를 체크.
					         user.setUserPwd(userPwd);
					         user.setUserName(userName);
					         user.setUserEmail(userEmail);
					         
					         if(userService.userUpdate(user) > 0) 
					         {
					            //정상 처리
					            ajaxResponse.setResponse(0, "Success");
					         }
					         else 
					         {
					            ajaxResponse.setResponse(500, "Internal Server Error");
					         }
					      }
					      else 
					      {
					         //넘어온 값이 없음.
					         CookieUtil.deleteCookie(request, response, AUTH_COOKIE_NAME);
					         ajaxResponse.setResponse(400, "Forbidden User");
					      }
					   }
					   else 
					   {
					      //쿠키는 있는데 정지된 사용자임,  쿠키부터 날림
					      CookieUtil.deleteCookie(request, response, AUTH_COOKIE_NAME);
					      ajaxResponse.setResponse(400, "Forbidden User");
					   }   
					}
					else 
					{
					   //쿠키는 있는데 회원정보에 없음, 쿠키부터 날림
					   CookieUtil.deleteCookie(request, response, AUTH_COOKIE_NAME);
					   ajaxResponse.setResponse(404, "Not Found");
					 }
					    
				 }
				 else 
				 {
				    ajaxResponse.setResponse(400, "Bad Request");
				 }
				     
				 return ajaxResponse;
		}

-전과 크게 다르지 않지만, 회원 상태가 Y가 아니거나 존재하지 않는 회원인 경우를 잘 걸려줘야 한다. 당연히 보여주는 view가 없고 바로 비동기통신으로 응답을 처리하는 부분이기 때문에 @ResponseBody 어노테이션을 사용해준다.

-이제 보여지는 화면을 구성해야 하는데 문제는 해당 화면에 대한 url을 요청받으면 처리할 view의 파일 위치를 리턴하는 컨트롤러 영역을 구현해야 한다. 그래서 해당 부분먼저 구현한다.

		//회원정보 수정 화면
		@RequestMapping(value="/user/updateForm", method=RequestMethod.GET)
		public String updateForm(ModelMap model, HttpServletRequest request, HttpServletResponse response) 
		{
			
			String cookieUserId = CookieUtil.getHexValue(request, AUTH_COOKIE_NAME);
			
			User user = userService.userSelect(cookieUserId);
			
			//객체를 읽어오면, 객체 모델을 통해서 화면에 뿌려주기 위한 용도.
			//처음은 나는 jsp에서 유저라는 이름으로 쓸거다. 두번째는 어떤 값을? 유저 객체를!
			//매개변수 1은 jsp에서 사용할 이름, 뒤에 매개변수는 현재 메소드에서 사용할 유저 객체
			model.addAttribute("user", user);
			
			return "/user/updateForm";
		}

-여기서 중요한 부분은 바로 ModelMap의 객체를 인자로 받은 것인데, 해당 부분의 구체적인 로직까지는 아직 이해하지 못했으나, 실제로 화면에 값을 뿌려줄 때 굳이 새로운 유저객체를 선언하지 않아도 되도록 하게 도와주는 객체라고 이해했다.
-쉬는시간에 정리한 부분을 올려본다.

ModelMap에 대한 이해

원래는 스프링을 하기 전에
.jsp에서 상단에 jsp 문법으로 유저 객체를 생성해서 쿠키 값에 해당하는 유저 객체를 
db에서 받아와야 함.
<%
	String CookieUserId = CookieUtil.get(request, "USER_ID");
	UserDao userDao = new UserDao();
	User user = userDao.userSelect(CookieUserId);
%>
위의 코드를 작성하고 나서야 비로소
<%= user.userId%>이런식으로 사용가능함. 이걸 하지 않으면 유저 객체 자체가 선언되지 않았기 때문에
오류가 남

근데 컨트롤러에서 모델맵을 만들어서 attribute로 넘기면
굳이 선언해줄 필요 없이
${user.userId} 이런식으로 사용이 가능함. 굳이 또 객체를 선언해서 db에서 각각의 값을 읽어와서 
해당 값을 넣어주는 복잡한 과정이 필요하지 않게 됨. 

이제 보여지는 화면에서 ajax통신을 통해서 회원정보 수정을 요청하는 jQuery 영역을 구현한다. 즉, .jsp파일을 작성한다. 다른 부분은 전에 했던 jsp와 비슷하기 때문에 통신하는 부분인 ajax부분만 올리도록 하겠다.

      if($("#userPwd").val() != $("#oldUserPwd").val())
      {
    	  
	      $.ajax({
	    	  type: "POST",
	    	  url: "/user/updateProc",
	    	  data:{
	    		userId: $("#userId").val(),
	      		userPwd: $("#userPwd").val(),
	      		userName: $("#userName").val(),
	      		userEmail: $("#userEmail").val()
	    	  },
	    	  datatype: "JSON",
	    	  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("파라미터 값이 올바르지 않습니다.");
	    			  $("#userPwd1").focus();
	    		  }
	    		  else if(response.code == 404)
	    		  {
	    			  alert("회원정보가 없습니다.");
	    			  location.href = "/";
	    		  }
	    		  else if(response.code == 500)
	    		  {
	    			  //count가 0보다 작을 때
	    			  alert("회원정보 수정 중 오류가 발생했습니다.");
	    			  $("#userPwd1").focus();
	    		  }
	    		  else
	    		  {
	    			  alert("회원 정보 중 오류가 발생했습니다.");
	    			  $("#userPwd1").focus();
	    		  }
	    	  },
	    	  complete:function(data)
	    	  {
	    		  icia.common.log(data);
	    	  },
	    	  error:function(xhr, status, error)
	    	  {
	    		  icia.common.error(error);
	    	  }
	      });
      }
      else
      {
    	  alert("비밀번호가 전과 동일합니다. 전과 다른 비밀번호를 입력해주세요.");
    	  $("#userPwd1").focus();
      }

-버튼이 눌리고, 정규식 관련 모든 로직을 통과한 후 마지막에 ajax통신을 통해 요청과 응답을 받는 로직의 일부분이다. 이 때, 전과는 다른 과정이 하나 있는데, 바로 비밀번호 수정 시 전의 비밀번호와 동일하면 수정이 진행이 되지 않게 하는 로직이다. 이 로직을 구현하려면 바로 히든 타입의 인풋태그를 사용하면 된다. 물론 그렇게 하지 않고도 처리하는 여러 방법이 있지만, 히든타입으로 과거의 값들도 서버로 같이 보내는 경우가 가장 범용성이 있기 때문에 히든타입에는 예전 값을 그리고 보낼 인풋박스에는 수정된 값을 갖고있다가 해당 값이 같을 경우 비밀변호가 변경되지 않았기 때문에 수정이 안되는 방향으로 프로그램을 진행해야 한다.

·게시판을 위한 새로운 테이블

-기존의 jsp에서는 게시판을 만들 때, 댓글과 첨부파일 부분이 제외가 되었었다. 설명하는데 시간도 많이 걸리고, 이해를 저해하는 요소이기 때문에 스프링단에서 제대로 구현할 계획이었다. 그래서 우선 테이블과 인덱스 시퀀스 등을 정의했다.

--계층형 게시판.
--댓글 작성과 파일첨부를 위해서 보드 테이블을 새로 만들기!
--기존에 보드테이블의 네임 이메일을 사실 불필요한 요소였음. 이번에는 없이 만들겠음
--게층형 게시판의 핵심은 그룹 오더 인덴트
--SEQ는 시퀀스를 쓸 것임
--GROUP은 각 게시글마다의 댓글이 누구꺼의 댓글인지 알아야 함. 댓글의 내용은 전부 하이보드 안에 들어가 있을 것임.
--1-1은 1번 게시물에 대한 댓글, 2-2는 2번째 게시글에 대한 댓글이다. 이걸 알아야 보여줄 수 있음.
--왜냐하면 1-1 다음에 1-2가 입력되는 것이 아니라 1-1다음에 2-1이 달릴 수도 있음. 
--GROUP은 메인글은 0으로 달릴 것임. 댓글의 경우에는 달린 메인글의 시퀀스를 가져옴.
--ORDER는 최신순으로 댓글 보여줄 때 사용할 것
--대댓글이 띄어쓰기 되어서 들어가는 데(CSS로 적용하는데에) 사용될 것이 INDENT 변수, 들여쓰기.
CREATE TABLE TBL_HIBOARD(
    HIBBS_SEQ NUMBER(12) NOT NULL,
    USER_ID VARCHAR2(20) NOT NULL,
    HIBBS_GROUP NUMBER(12) NULL,
    HIBBS_ORDER NUMBER(10) NULL,
    HIBBS_INDENT NUMBER(10) NULL,
    HIBBS_TITLE VARCHAR(150) NULL,
    HIBBS_CONTENT CLOB NULL,
    HIBBS_READ_CNT NUMBER(10) NULL,
    REG_DATE DATE NULL
);

CREATE SEQUENCE SEQ_HIBOARD_SEQ INCREMENT BY 1
MAXVALUE 999999999999
MINVALUE 1
NOCACHE;

COMMENT ON COLUMN TBL_HIBOARD.HIBBS_SEQ IS '게시물번호(시퀀스:SEQ_HIBOARD_SEQ)';
COMMENT ON COLUMN TBL_HIBOARD.USER_ID IS '사용자 아이디';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_GROUP IS '그룹번호';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_ORDER IS '그룹 내 순서';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_INDENT IS '들여쓰기';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_TITLE IS '게시물 제목';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_CONTENT IS '게시물 내용';
COMMENT ON COLUMN TBL_HIBOARD.HIBBS_READ_CNT IS '게시물 조회수';
COMMENT ON COLUMN TBL_HIBOARD.REG_DATE IS '게시물 등록일';


CREATE UNIQUE INDEX XPK_HIBOARD ON TBL_HIBOARD(HIBBS_SEQ ASC);

--첨부파일에 대한 것은 별로 테이블로 가져갈 것임 그렇기 때문에 새로운 테이블.
--게층형 게시판 첨부파일
--FILE_SEQ얘는 첨부파일이 몇개의 첨부파일인지 알기 위해서 필요한 것. 시퀀스를 쓰면 안됨.
--HIBBS_SEQ 얘는 새로운 시퀀스를 쓰지 않음.
--게시글은 모두 같은 경로로 들어감. 파일명이 중복되면 안되기 때문에 파일명은 이름 그대로 있지만, 실제로 서버에 저장되는 이미지 이름은 다름.
--올린 첨부파일 이름, 저장한 이름이 두개 필요함
CREATE TABLE TBL_HIBOARD_FILE
(
    HIBBS_SEQ NUMBER(12) NOT NULL,
    FILE_SEQ NUMBER(3) NOT NULL,
    FILE_ORG_NAME VARCHAR2(100) NULL,
    FILE_NAME VARCHAR2(50) NULL,
    FILE_EXT VARCHAR2(50) NULL,
    FILE_SIZE NUMBER(12) NULL,
    REG_DATE DATE NULL
);

--대체적으로 첨부파일 숫자가 정해진 게시판에서는 테이블 하나로도 됨. -> 그냥 컬럼값을 5개 갖고 있으면 됨. 근데 이 경우 불필요한 영역의 컬럼이 계속 있는 것임
--그러나 확장성을 위해서는 테이블을 두 개로 하는 것이 좋음

--이건 예시임.
--HIBBS_SEQ가 5번이라고 하자, 파일 첨부 하나
--트랜젝션이라는 개념, 게시판에 글을 남길 때, 한번의 액션을 취하지만, 두 테이블에 전부 INSERT해줘야 함.
--만약 한 테이블만 입력되고 하나가 오류나면 전부 롤백 처리해야 함. 이걸 하나의 트랜젝션이라고 부름.
SELECT NVL(MAX(FILE_SEQ), 0)
  FROM TBL_HIBOARD_FILE
 WHERE HIBBS_SEQ = 5;

--UPDATE TBL_HIBOARD_FILE
--   SET FILE_SEQ = NVL(MAX(FILE_SEQ), 0)+1 AS FILE_SEQ
-- WHERE HIBBS_SEQ = 5;
--만약 여기 인서트할 때는 NVL(MAX(FILE_SEQ), 0)이 부분에 +1을 해줘야 함. MAX는 해당 게시글의 첨부 파일 순서가 가장 큰 애를 가져오는 것.
--게시글 하나에 첨부 파일이 여러개일 수 있어서 HIBBS_SEQ NUMBER(12) NOT NULL, FILE_SEQ NUMBER(3) NOT NULL 얘네 두 개를 전부 걸어줘야 유니크해짐. 그래서 PK가 두 개가 될 것임.
--FILE_SEQ는 시퀀스를 쓰면 안됨. 왜냐면 파일첨부의 갯수를 구해야되는데 시퀀스를 쓰면 그냥 지금까지 올린 첨부파일 모두의 숫자 중 가장 마지막 숫자를 가져오게 되기 때문.
--HIBOARD_FILE에는 유니크 인덱스를 HIBBS_SEQ로 할 수가 없음.



COMMENT ON COLUMN TBL_HIBOARD_FILE.HIBBS_SEQ IS '게시물 번호(TBL_HIBOARD.HIBBS_SEQ)';
COMMENT ON COLUMN TBL_HIBOARD_FILE.FILE_SEQ IS '첨부파일 번호(HIBBS_SEQ MAX+1)';
COMMENT ON COLUMN TBL_HIBOARD_FILE.FILE_ORG_NAME IS '원본 파일명';
COMMENT ON COLUMN TBL_HIBOARD_FILE.FILE_NAME IS '파일명(서버)';
COMMENT ON COLUMN TBL_HIBOARD_FILE.FILE_EXT IS '파일 확장자';
COMMENT ON COLUMN TBL_HIBOARD_FILE.FILE_SIZE IS '파일 크기';
COMMENT ON COLUMN TBL_HIBOARD_FILE.REG_DATE IS '등록일';


CREATE UNIQUE INDEX XPK_HIBOARD_FILE ON TBL_HIBOARD_FILE(HIBBS_SEQ ASC, FILE_SEQ ASC);
--위에서 언급했듯이 복합키로 유니크 인덱스 생성!

-게시판은 굉장히 복잡한데, 게시글 하나에 댓글이 몇개씩 달릴 수 있고, 또 게시글의 댓글이 항상 순서대로만 달리는 것이 아니기에 컬럼세팅을 잘 해줘야 했다. 또한 HIBBS_INDENET는 나중에 css에서 적용할 댓글과 대댓글의 들여쓰기 기준이 되는 컬럼이 될 것인데, 현재로서는 어떤식으로 쓰일지 확실하게 감이 오진 않는다.

-게시판은 첨부파일이 필요하다. 문제는 첨부파일도 같은 테이블에 넣어서 하나의 컬럼을 차지하는 경우, 첨부파일의 숫자만큼의 컬럼이 필요하고, 만약 아무 첨부파일도 없다면 선언된 컬럼들이 낭비가 되기 때문에 다른 테이블로 나눠놨다. 물론, 하나의 트랜젝션이기 때문에 하나의 쿼리에서 오류가나면 바로 롤백해버리도록 로직처리를 할 것이다.

·수업 막바지 교수님의 한숨

-수업 막바지, 이제 게시판의 테이블 값을 가져오는 쿼리문을 하려고 하다가 교수님이 한숨을 쉬셨다. 너무 어려운 쿼리문이여서 시작부터 겁낼까봐 진행을 겁내셨다. 그래서 남은 시간에는 복습을 더 하고 이해하고 내일 쿼리문을 진행하자고 하셨다. 그래서 뭔가 더 겁이 난다...
-자습시간으로 받은 시간에는 팀프로젝트 관련 회의를 카톡으로 했다. 대면으로 나온 사람이 3명이고 비대면이 3명이었기 때문에 어쩔 수 없었다. 오늘은 우리팀이 나오는 날이 아니기 때문에 비대면 인원이 잘못한 것은 아니다! 내일 4교시에 진행될 회의에서 무엇을 결정하고 그전까지 고민해야 하는 부분들을 공유하고 여러 아이디어들을 교환했다. 내일은 친목을 위한 회식도 코로나 때문에 조촐하게(물론 현재 시국의 인원제한에 맞춰서) 진행하려고 한다. 물론 그전 회의에서 유의미한 결과를 얻는다면 더 좋을 것 같다. 내일 진행될 쿼리가 벌써부터 겁나지만 전에 했던 게시판의 쿼리문을 다시 열심히 보고 복습해서 가면 되겠지... 파이팅!!

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

0개의 댓글