10/01/2018

스프링 쇼핑몰 만들기 #3. 기본화면, 회원가입과 로그인

스프링 쇼핑몰 만들기

회원가입과 로그인 기능입니다. 회원가입간에 간단한 암호화를 이용하겠습니다.

공통으로 적용되는 header.jsp, nav.jsp, footer.jsp파일을 별도의 폴더에 생성합니다.

그리고 회원가입때 사용할 쿼리를 테스트해봅니다.

쿼리가 정상적으로 작동한다면 src/main/resourses → mappers → memberMapper.xml에 추가합니다.

<!-- 회원 가입 -->
<insert id="signup">
 insert into tbl_member(userId, userPass, userName, userPhon)
  values(#{userId}, #{userPass}, #{userName}, #{userPhon}) 
</insert>

다음은 DAO와 Service를 생성합니다.

MemberController를 추가하고, 회원가입용 메서드를 작성합니다.

@Inject
MemberService service;

@Autowired
BCryptPasswordEncoder passEncoder;
  
// 회원 가입 get
@RequestMapping(value = "/signup", method = RequestMethod.GET)
public void getSignup() throws Exception {
 logger.info("get signup");
}

// 회원 가입 post
@RequestMapping(value = "/signup", method = RequestMethod.POST)
public String postSignup(MemberVO vo) throws Exception {
 logger.info("post signup");
  
 String inputPass = vo.getUserPass();
 String pass = passEncoder.encode(inputPass);
 vo.setUserPass(pass);

 service.signup(vo);

 return "redirect:/";
}

입력받은 패스워드를 BCrypt로 암호화시키고 다시 MemberVO에 넘겨주는 방식입니다.

매퍼 → DAO → Service → Controller 순서로 작업되었고, 다음은 JSP입니다.

회원기능은 모두 member폴더에 통합하여 보관하며, 회원가입용 페이지인 signup.jsp를 작성합니다.

모든 jsp파일을 하나하나 입력하지말고 home.jsp를 복사한 뒤 <div id="container_box"> ~ </div> 내부만 수정하는 방식으로 합니다.

이런식으로 모든 페이지가 똑같이 구성되어있다면 추가/편집이 용이하기 때문에 매우 편리합니다. 더 나아가서 타일즈(tiles)를 이용하는 방법도 있는데, 타일즈는 나중에 사용하겠습니다.

<section id="content">
 <form role="form" method="post" autocomplete="off">
  <div class="input_area">
   <label for="userId">아이디</label>
   <input type="email" id="userId" name="userId" placeholder="example@email.com" required="required" />      
  </div>
  
  <div class="input_area">
   <label for="userPass">패스워드</label>
   <input type="password" id="userPass" name="userPass" required="required" />      
  </div>
  
  <div class="input_area">
   <label for="userName">닉네임</label>
   <input type="text" id="userName" name="userName" placeholder="닉네임을 입력해주세요" required="required" />      
  </div>
  
  <div class="input_area">
   <label for="userPhon">연락처</label>
   <input type="text" id="userPhon" name="userPhon" placeholder="연락처를 입력해주세요" required="required" />      
  </div>
  
  <button type="submit" id="signup_btn" name="signup_btn">회원가입</button>
  
 </form>   
</section>

아이디 인풋박스(input)의 타입을 이메일로하여서, 메일 형식으로만 받을 수 있도록 설정합니다. 마찬가지로 패스워드도 타입을 설정하여 *모양으로 표시하도록 하고, 모든 인풋박스에 필수입력(required) 설정을 해둡니다.

이렇게 jsp에서 미리 설정을 해두었지만, 나중에 한번 더 작업하겠습니다.

이제 프로젝트를 실행하고 회원가입 페이지로 이동해봅니다.

실제로 회원가입을 해보겠습니다.

인풋박스에 설정을 해두었기 때문에, 아이디는 이메일 형식으로만 입력할 수 있으며, 인풋박스가 공란이면 진행되지 않습니다. 아이디의 이메일이 실제로 존재하는지 확인할 방법은 없으며, 스페이스는 공란이 아니라 진행이 가능한 문제가 있습니다.

데이터베이스를 확인해보니, 회원가입이 잘 된것을 알 수 있습니다.

이번엔 로그인 쿼리를 매퍼에 추가합니다.

<!-- 로그인 -->
<select id="signin" resultType="com.kubg.domain.MemberVO">
 select
     userId, userName, userPass, userPhon, userAddr1, userAddr2, userAddr3, regiDate
 from
     tbl_member
 where userId = #{userId}
</select>

BCrypt 암호화는 매번 그 값이 바뀌기 때문에, 입력한 아이디를 이용해 패스워드를 가져온 뒤, 로그인할 때 입력한 패스워드와 데이터 베이스에 저장된 패스워드를 비교하는 방식으로 합니다.

DAO와 Service에 코드를 추가합니다.

Service에 로그아웃을 위한 메서드가 하나 더 있습니다.

다음은 Controller에 코드를 추가합니다.

// 로그인  get
@RequestMapping(value = "/signin", method = RequestMethod.GET)
public void getSignin() throws Exception {
 logger.info("get signin");
}

// 로그인 post
@RequestMapping(value = "/signin", method = RequestMethod.POST)
public String postSignin(MemberVO vo, HttpServletRequest req, RedirectAttributes rttr) throws Exception {
 logger.info("post signin");
   
 MemberVO login = service.signin(vo);  
 HttpSession session = req.getSession();
 
 boolean passMatch = passEncoder.matches(vo.getUserPass(), login.getUserPass());
 
 if(login != null && passMatch) {
  session.setAttribute("member", login);
 } else {
  session.setAttribute("member", null);
  rttr.addFlashAttribute("msg", false);
  return "redirect:/member/signin";
 }  
 
 return "redirect:/";
}
  
// 로그아웃
@RequestMapping(value = "/signout", method = RequestMethod.GET)
public String signout(HttpSession session) throws Exception {
 logger.info("get logout");
 
 service.signout(session);
   
 return "redirect:/";
}

passMatch 는 사용자가 입력한 패스워드와 데이터 베이스에 저장된 패스워드를 비교해서 동일하면 true, 그렇지않다면 false를 반환합니다.

login 에 값이 없는 경우는 아이디를 잘못입력한 경우이므로, 아이디나 패스워드가 모두 맞아야 session.setAttribute("member", login);코드가 실행됩니다.

아이디나 패스워드 중 하나라도 틀리다면 else { } 부의 코드가 실행되는데 session.setAttribute("member", null); 로 세션값을 제거하고 rttr.addFlashAttribute("msg", false); 는 특정 페이지로 이동될 때 msg의 값(false)를 부여합니다. 이때 특정 페이지는 return "redirect:/member/signin"; 가 됩니다.

nav.jsp의 코드를 추가합니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<ul>
<c:if test="${member == null}">
 <li>
  <a href="/member/signin">로그인</a>
 </li>
 <li>
  <a href="/member/signup">회원가입</a>
 </li>
</c:if>
<c:if test="${member != null}">
 <li>
  ${member.userName}님 환영합니다.
 </li>
 <li>
  <a href="/member/signout">로그아웃</a>
 </li>
</c:if>
</ul>

조건문을 이용하여 로그인 안했을 때와 로그인할 때 나오는 목록이 달라지게 됩니다.

이제 signin.jsp를 작성합니다. signup.jsp를 복사해서 이름을 바꾸고, 내부 코드를 조금 수정합니다.

<section id="content">
 <form role="form" method="post" autocomplete="off">
  <div class="input_area">
   <label for="userId">아이디</label>
   <input type="email" id="userId" name="userId" required="required" />      
  </div>
  
  <div class="input_area">
   <label for="userPass">패스워드</label>
   <input type="password" id="userPass" name="userPass" required="required" />      
  </div>
       
  <button type="submit" id="signin_btn" name="signin_btn">로그인</button>
  
  <c:if test="${msg == false}">
   <p style="color:#f00;">로그인에 실패했습니다.</p>
  </c:if>
  
 </form>   
</section>

조건문을 이용하여, msg의 값이 false라면 실패 메시지가 출력되도록합니다.

제대로 작동되는지 확인하기 위해, 프로젝트를 실행하고 로그인해봅니다.

로그인하게되면 회원가입 때 입력했던 닉네임이 표시되고, 로그아웃 링크도 보입니다. 로그아웃은 미리 코드를 입력해놨기 때문에 지금 클릭해도 잘 작동됩니다.

로그인간에 아이디 또는 패스워드가 틀렸을 경우 메시지가 출력됩니다.

로그인간에 아이디가 잘못되었을 경우(=데이터 베이스에 없는 아이디를 입력한 경우), Null값을 반환하게되어 최종적으로 500 에러가 발생합니다.

로그인 부분에 대해서는 차후 다른 게시물로 보완하겠습니다.

게시물 수정
  1. boolean passMatch = passEncoder.matches(vo.getUser_pass(), login.getUser_pass());

    if(login != null && passMatch){
    에서 오류가 발생합니다 ㅠㅠ

    && passMatch ->부분을 없애고 진행하면 오류 없이 되는데요..
    (boolean문도 삭제)
    넣으면 아래와 같은 에러가 계속 발생합니다 ㅠㅠ
    2월 18, 2019 4:36:38 오후 org.apache.catalina.core.StandardWrapperValve invoke
    심각: Servlet.service() for servlet [appServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
    java.lang.NullPointerException
    at com.shop.controller.MemberController.loginPost(MemberController.java:59)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)

    답글삭제
  2. 그리고 삭제 해서 넘겼음에도 불구하고 nav.jsp에서 값을 제대로 못받아와서 내용이 바뀌질 않습니다.. 따로 작성한 부분이 있는것도 아니고...내용 그대로 넣었는데도 부분이 안되어서..
    ${member.user_name } 님 환영합니다. 이 부분이 되질 않습니다 ㅠㅠㅠ

    답글삭제
    답글
    1. 안녕하세요?

      먼저 에러가 Null데이터 때문에 생기는 에러입니다. MemberController의 59번째 줄에 문제가 있는것 같은데 확인해보시고, 매개변수(view에서 controller로 받아오는 데이터들)중 null값을 가진 데이터가 있나 확인해보시기 바랍니다.

      -

      에러 있는 부분을 지우고 실행하여 로그인을 시도해봤는데 내용이 바뀌지 않았다는건, 말그대로 로그인이 되지 않았을 확률이 높은것 같습니다. 이것도 위처럼 매개변수 중 null이 있기 때문인것 같습니다.

      만약 jsp파일의 최상단에 <%@ page session="false" %>가 있다면 지워주세요.

      삭제
    2. 만약 해결되지 않는다면, 코드를 메일로 보내주시면 확인해보겠습니다.

      삭제
    3. 보내주신 답신 잘 받았습니다.. page session="false"가 홈에 있을줄 몰랐네요 ㅠㅠㅠ 알려주셔서 감사합니다...

      추가적인 질문으로...로그인 실패 했을때 500에러가 나는것은 어떻게 해결하면 될까요..?
      boolean passMatch = passEncoder.matches(vo.getUser_pass(), login.getUser_pass());\
      이곳에서 에러가 시작 된다고 하는데 passMatch도 0,1 로 제대로 들어가고 있는데 잘 모르겠습니다..ㅠㅠ

      삭제
    4. 안녕하세요? 먼저 메일 부분은 잘 해결되어서 다행입니다.

      먼저 진행중인 프로젝트에서 정상적인 아이디를 입력하고 비밀번호를 다르게 입력할 경우 본문 처럼 에러가 없이 메시지가 출력될것입니다. 하지만 아이디가 틀릴 경우 말씀하신 에러가 발생 될 겁니다.

      MemberVO login = service.signin(vo);이 코드가 요구하는 데이터는 아이디인데,
      데이터 베이스가 있는 아이디를 입력하게 되면 해당 값을 반납하므로 문제가 없으나
      데이터 베이스에 없는 아이디를 입력하게 되면 해당 값이 없으므로 Null을 반납하게되며 최종적으로 에러가 발생합니다.

      데이터 베이스에 없는 아이디를 입력하더라도 에러가 발생하지 않게하려면 컨트롤러와 서비스에 약간의 작업이 필요로 합니다.

      대충 생각나는대로 적어보자면..

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

      // Controller
      @RequestMapping(value = "/login", method = RequestMethod.POST)
      public String loginPOST(HttpServletRequest req, RedirectAttributes rttr) throws Exception {

      boolean loginSuccess = memberService.login(req);

      [..생략..]
      }


      // Service
      @Override
      public boolean login(HttpServletRequest request) throws Exception {

      // 아이디와 비밀번호를 저장할 map 생성
      HashMap<String, String> map = new HashMap<String, String>();

      // 로그인 성공 여부(DB 보유 여부)
      boolean loginSuccess = false;

      // 필요한 데이터를 추출
      String userId = request.getParameter("userId");
      String userPw = request.getParameter("userPw");

      // 추출한 데이터를 map에 입력
      map.put("userId", userId);
      map.put("userPw", userPw);

      MemberVO login = dao.login(map);

      // login의 값이 null이 아니라면 (데이터가 존재한다면)
      if (login != null) {
      HttpSession session = request.getSession();

      // 세션에 로그인 정보를 부여함
      session.setAttribute("login", login);

      // 로그인 성공 여부를 변경
      loginSuccess = true;
      }

      return loginSuccess;
      }

      // DAO
      @Override
      public MemberVO login(HashMap<String, String> map) throws Exception {
      return session.selectOne(namespace + ".login", map);
      }

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

      이런 방식으로 처리하면 됩니다. 물론 다른 방법도 있으나, 지금 바로 떠오르는게 이거네요-_-; 또한, Service에서 굳이 map으로 작업하지 않고 vo로 작업해도 상관없습니다.

      본문에 이 부분에 대해서 대략적으로 언급하고, 이후에 로그인 부분에 대해서 별도의 게시물을 작성하여 보완하겠습니다.

      감사합니다.

      삭제
    5. 아..!! 에러가 나는게 정상이군요 ...;;; 저는 제가 뭘 잘못한줄 알고 계속 이거만 들여다보고 있었네요..ㅎㅎ 그래도 다행입니다 ..ㅎㅎ 저도 기존에 했던것과 비교해서 다른방법으로 해보도록 하겠습니다 감사합니다!

      삭제
    6. 저는 기존 코드 컨트롤러에
      MemberVO login = service.signin(vo);

      if(login==null){
      return "redirect:/";
      rttr.addFlashAttribute("msg" , " 값이없습니다") ;
      이런식으로 해결했습니다

      삭제
  3. 익명6/20/2019

    안녕하세요 게시글 잘 봤습니다. 로그인시 날짜와 시간을 DB에 넣고싶은데 어떤식으로 해야할지 감이 안오네요...혹시 조언좀 해주실 수 있을까요?

    답글삭제
    답글
    1. 안녕하세요?

      말씀하시는게, '마지막 로그인 시점'을 DB에 저장하는게 맞지요?

      1. 회원 테이블에 날짜 데이터를 저장할 컬럼을 준비
      - 예를들어 tbl_member [ userId, password, name, lastDate ] 라고 가정합니다.
      2. 로그인할 때 아이디와 패스워드를 검증하고, 둘 모두 일치하여 세션을 생성하는 시점 다로 다음에
      3. tbl_member를 수정(update)하는 기능을 추가합니다.
      4. 매퍼에서는 update tbl_member set lastDate = sysdate where userId = #{아이디} 이런식으로 입력해두면 되겠죠

      이렇게해주면 회원이 로그인할 때마다 lastDate를 현재 날짜로 수정하게됩니다.
      sysdate는 현재 날짜와 시간을 모두 가지고 있으므로, 날짜 따로 시간 따로 필요하다면 서비스에서 코드로 Date 포맷으로 나누시면 됩니다.

      삭제
  4. 안녕하세요 자료 올리신거 보면서 공부하는 학생입니다.다른게 아니라 회원가입할때 500 에러가 떠서 질문드립니다.
    6월 27, 2019 4:59:46 오후 org.apache.catalina.core.StandardWrapperValve invoke
    심각: 경로 []의 컨텍스트 내의 서블릿 [appServlet]을(를) 위한 Servlet.service() 호출이, 근본 원인(root cause)과 함께, 예외 [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
    ### Error updating database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.kubg.mappers.testMapper.singup
    ### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.kubg.mappers.testMapper.singup]을(를) 발생시켰습니다.
    java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.kubg.mappers.testMapper.singup

    답글삭제
    답글
    1. 안녕하세요?

      에러 메시지를 보면 매퍼에서 에러가 발생했다는것을 알 수 있습니다. 매퍼의 아이디와 DAO를 확인해보세요

      삭제
  5. 안녕하세요. 정말 잘 보고 공부하고있습니다
    다름이 아니라 두가지 질문이 있습니다.
    1. mapper에서 작성할때 resultType의 의미와 용도를 알고싶습니다.
    2. 로그인post에서 파라미터 값이 vo는 알겠는데
    httpservletRequest req, redirectattribute rttr의 의미와 사용용도 또한 알고싶습니다.
    초보라서 자세한 답변 부탁드릴게요 ㅠㅠ

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      1. mapper의 resultType
      매퍼의 내부 쿼리를 실행하였을 때 나오는 결과값의 형태가 resultType가 됩니다. <select id="signin" resultType="com.kubg.domain.MemberVO"> 내부 쿼리를 실제로 실행해보면, memberVO의 형태와 똑같은 결과가 나오기 때문에, 이를 DAO에 반환(return)하면서 자동으로 VO의 각 변수에 대입하기 위해 resultType를 사용합니다.

      2. httpservletRequest와 redirectattribute
      httpservletRequest는 현재 세션에 있는 값을 불러올 때 사용합니다. 현재 세션에는 사용자가 접속한 각종 데이터가 들어가 있는데, httpservletRequest을 이용해 데이터를 제어할 수 있습니다.

      본문에서는 HttpSession session = req.getSession();으로 현재 세션을 가져왔으며, 로그인 여부에 따라서 session.setAttribute("member", login); 또는 session.setAttribute("member", null);가 실행되며, member라는 이름의 세션에 데이터를 추가하여 사용할 수 있습니다.

      redirectattribute는 페이지가 바뀌면서(새로고침이든, 이동이든) 데이터를 넘겨주는 역할을 합니다. 본문의 rttr.addFlashAttribute("msg", false);는 로그인에 실패하였을 때, 새로고침되며 msg라는 이름의 데이터에 false를 넣어서 전송하게 됩니다. 이때 view(JSP)에서 이를 감지하여 로그인에 실패했다는 메시지를 출력합니다.

      삭제
  6. 저 500에러 해결하셨나요???

    답글삭제
  7. 작성자가 댓글을 삭제했습니다.

    답글삭제
  8. 작성자가 댓글을 삭제했습니다.

    답글삭제
  9. 덕분에 쉽게 알아갑니다 감사합니다.

    답글삭제
  10. 질문이 있습니다. 전에 보고 따라했을때 가입 잘되고 로그인 잘 된다 생각했는데 오라클 sql developer에서 직접 회원을 insert해서 가입시켰더니 로그인이 안됩니다 ㅠㅠ 크롬에서 가입하고 로그인하면 그 정보가 디비에 잘 저장되는데 디비에서 가입한것들은 화면에 안나오고 그러네요..왜 그럴까요

    답글삭제
    답글
    1. 게시판 역시 더미로 300개정도 디비에서 삽입했더니 크롬 화면에서는 보이지않습니다 ㅠㅠ
      반대로 크롬에서 직접 글을 쓰면 디비에 저장은 되구요..

      삭제
    2. 아 commit을 안했네요 ㅠㅠ죄송합니다 ㅠㅠ잘되네요!!!!!
      선생님 조만간 드래그앤 드롭으로 첨부파일 이런거도 해주시면 좋을거같아요!!!
      아니면 회원간 쪽지 보내기 이런거두요!!!!

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다.

      첫번째 댓글만 보고 커밋보단 암호화 문제가 먼저 생각났는데, 그부분은 처리하셨나보군요.
      오라클의 경우 습관적으로 커밋해야하더군요. 종종 이 문제 때문에 며칠동안 바둥거릴때도 있고 말이죠..ㅠㅠ

      쪽지나 드래그 파일 첨부는 새로 작성하는 게시판쪽에 추가할 예정이긴한데
      아마 1월 초까지는 업무 마감 때문에 좀 천천히 진행할 예정입니다.

      다시 한번 방문해주셔서 감사합니다. 새해 복많이 받으세요.

      삭제
  11. 안녕하세요 블로그를 보며 공부하는 학생입니다.
    궁금한 점이 있어서
    그 로그인 컨트롤러 부분에서
    @RequestMapping(value="/signin", method = RequestMethod.GET)
    @RequestMapping(value="/signin", method = RequestMethod.POST)
    이렇게 두개로 나누셨는데

    그 이유가 궁금합니다..
    위에 void getsignin을 지우고
    @RequestMapping(value="/signin")
    public String postsignIn(memberVO vo, HttpServletRequest req) throws Exception {
    ~~~
    이렇게 바꿔서 했는데 리디렉션이 너무 많다고 오류가 나더라구요..

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      겟(get)과 포스트(post)를 나눈 이유는, 둘의 역할이 달라서입니다.

      겟에서는 단순히 화면을 띄우기 위한 용도가 있지만, 추가적으로 db 또는 추가 로직을 통하여 사용자에게 보여줄 데이터를 준비합니다.
      포스트에서는 사용자가 입력한 데이터를 처리하는 로직으로 이어집니다.

      겟과 포스트를 하나로 합칠 경우, 전송 방식이 어떤거냐에 따라 분기해주셔야 정상적으로 실행이 됩니다.

      삭제
  12. 안녕하세요. 오랜만에 다시 들러서 복습합니다. 갑자기 궁금한게 생겼는데
    mapper에서
    insert into tbl_member(userId, userPass, userName, userPhon)
    values(#{userId}, #{userPass}, #{userName}, #{userPhon})
    이문장에서 #을 붙인거와 안붙인것들 차이가 궁금해요.
    1. #안붙인 userId는 oracle(sql developer에서 만든 테이블)의 컬럼명과 같아야하는건가요? 대소문자구문해야하는건지
    2. #붙인 것들은 사용자가 jsp화면에서 입력하는 것을 받아서 VO에 저장하는 건가요? 즉, 스프링에서 VO에서 만든(getter/setter) 변수명들과 같아야하나요?

    여기 글들 보면서 어느새 취업해서 하는데 문득 습관적으로 같은 이름써서 근본적인 것이 헷갈려서 질문드립니다.
    좋은 하루되세요!

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      2번부터 먼저 답변드리자면

      뷰(jsp)에서 입력받은 사용자 아이디(userId)와 패스워드(password)를 컨트롤러단에서 MemberVO 형태로 합쳐줍니다. MemberVO 형태로 만든 데이터를 매퍼로 건내주게되면 매퍼는 이를 Object로 받게되는데, 이렇게되면 사용하기가 어렵지요.

      그래서 매퍼에 파라미터 타입(parametaType)에 MemberVO를 넣어줘서 "이 매퍼에 들어오는 데이터는 모두 MemberVO 형태의 데이터입니다" 라고 매퍼에 알려줍니다.

      이렇게되면 매퍼가 데이터를 받으면서, 이 데이터는 MemberVO 형태라는걸 알 수 있기 때문에, MemberVO 내부에 있는 데이터인 userId와 password를 바로 호출할 수 있게됩니다. 이때 호출하는 방식은 #{user}, #{password}입니다. (#대신 $를 사용할수도 있습니다만, 성격이 조금 다르니 데이터를 받는 경우 #을 사용합니다)

      그러므로 2번의 답변은... 스프링에 있는 VO 내부에 있던 변수명칭을 그대로 사용합니다.


      이제 1번 답변은.. insert 문의 사용법은
      insert into [테이블명] ([컬럼1], [컬럼2]...) values ([컬럼1에 넣을 데이터], [컬럼2에 넣을 데이터]...) 입니다.

      그러므로 첫번째 있는 userId는 테이블의 컬럼이고 두번째 줄에있는 #{userId}는 위의 답변에 의해 사용자가 입력한 데이터라는걸 알 수 있습니다.

      "왜 이름을 똑같이 지어서 햇갈리게 하는거지?"라고 생각할 수 있으나, 기본적으로 VO는 해당 테이블과 동일한 데이터를 담을 수 있어야합니다. 즉 테이블 == VO가 되는건데, 이때 둘의 이름을 상이하게 짓는다면 그게 더 구분하기 어렵겠지요?

      물론 테이블과 VO를 구분하기 위해 특정한 규칙에 의거하여 명칭을 바꾸기도합니다.(이건 개인이나 팀에 따라 다릅니다) 테이블에선 userId, VO에선 _userId 처럼 짓는다던가 하는 방식이지요

      삭제
  13. 안녕하세요 많은 도움을 받고 있는데 에러가 나서 찾다찾다 댓글로 여쭤볼려구요

    시큐리티 버전에 맞춰 셋팅하고 했는데 자꾸 컨트롤러쪽에서 아래와 같은 에러가 납니다만 도움주실수잇으신지요...ㅜㅜ

    javax.servlet.ServletException: 서블릿 [appServlet]을(를) 위한 Servlet.init() 호출이 예외를 발생시켰습니다.
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:678)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:609)
    org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:810)
    org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1623)
    org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    java.lang.Thread.run(Unknown Source)
    근본 원인 (root cause)

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberController': Unsatisfied dependency expressed through field 'passEncoder'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}


    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      오키 게시물에 같은 에러가 있는데 한번 보시기 바랍니다.

      삭제
    2. 저도 이 문제로 3시간 날렸네요.

      우선 src/main/webapp/WEB-INF/spring 폴더에 spring-security.xml을 만들어주시고,









      요대로 작성하신뒤
      web.xml에 contextConfigLocation에서 spring-security.xml의 경로를 지정해주시면 됩니다.


      contextConfigLocation

      /WEB-INF/spring/appServlet/servlet-context.xml
      /WEB-INF/spring/spring-security.xml

      삭제
    3. spring-security.xml

      ?xml version="1.0" encoding="UTF-8"?
      beans:beans
      xmlns="http://www.springframework.org/schema/security"
      xmlns:beans="http://www.springframework.org/schema/beans"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/security
      http://www.springframework.org/schema/security/spring-security.xsd">


      beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /

      /beans:beans

      삭제
  14. MemberContorller에서 import할때 ctrl+shift+o를 사용해서 import를 했는데, 이때 org.slf4j를 import가 아니라 import java.util.logging.Logger를 import를 해버려서 자꾸 오류가 났다.
    또한 올라온 코드의 logger.info 가 아니라 Logger.info 이고
    pom.xml에 springframework.security를 추가 안해주어서 passEncoder에서 오류났다.

    dependency
    org.springframework.security groupId
    spring-security-web artifactId
    4.2.1.RELEASE version
    dependency

    org.springframework.security
    spring-security-core
    4.2.1.RELEASE

    org.springframework.security
    spring-security-config
    4.2.1.RELEASE

    답글삭제
    답글
    1. 이거때문에 고민했는데.. 감사합니다.

      삭제
  15. @Autowired
    BCryptPasswordEncoder passEncoder;

    @Autowired 를 주석처리하면 실행이되고, 주석처리 안하면 500에러가 뜸

    controller에

    @Bean
    BCryptPasswordEncoder passwordEncoder() {

    return new BCryptPasswordEncoder();
    }

    추가

    근데 회원가입을 안해봐서 암호화가 되는지는 아직 모름

    참고 : https://stackoverflow.com/questions/58202592/no-qualifying-bean-of-type-org-springframework-security-crypto-bcrypt-bcryptpas

    답글삭제
  16. signup.jsp까지 따라왔었는데 오라클이 실수로 중간에 한번 꺼졌는데 그전에 데이터 작업해둔걸 저장을 안해놔서 아예 다시 코딩을 쳐야되는 상황이 왔는데 테이블잉랑 커밋은 다 해놨었거든요 근데 저기까지 따라가서 실행해보니 404 해당페이지를 찾을 수 없다며 에러가 뜨네요 오라클이 꺼지기전까진 페이지가 나왔었는데.... 이런 경우는 어떡하죠..? 오라클 연동이 끊겨서 이렇게 된건가요..?다시해야되나요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      오라클이 중간에 종료되어서 테이블은 커밋했으나 내부 데이터는 커밋 안한 상태라고 이해를 했는데
      스프링에서 작업하신 내용하고는 무관합니다.

      아래에 작성하신 에러 메시지라면 아마 메이븐쪽의 문제로 보이는데
      프로젝트 우클릭 -> 메이븐 -> 업데이트 메이븐을 해보시기 바랍니다.

      삭제
  17. 이렇게 오류 뜨는건 뭔가요..?ㅠㅠ 원래 됐는데 jsp까지 따라가고 첫화면부터 에러로 안뜨더니 이렇게 나오네요
    10월 28, 2020 12:16:32 오후 org.apache.catalina.core.StandardContext startInternal
    심각: 하나 이상의 리스너들이 시작하지 못했습니다. 상세 내역은 적절한 컨테이너 로그 파일에서 찾을 수 있습니다.
    10월 28, 2020 12:16:32 오후 org.apache.catalina.core.StandardContext startInternal
    심각: 이전 오류들로 인해 컨텍스트 []의 시작이 실패했습니다.
    10월 28, 2020 12:16:32 오후 org.apache.catalina.core.ApplicationContext log
    정보: Closing Spring root WebApplicationContext
    10월 28, 2020 12:16:32 오후 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc
    경고: 웹 애플리케이션 [ROOT]이(가) JDBC 드라이버 [net.sf.log4jdbc.sql.jdbcapi.DriverSpy]을(를) 등록했지만, 웹 애플리케이션이 중지될 때, 해당 JDBC 드라이버의 등록을 제거하지 못했습니다. 메모리 누수를 방지하기 위하여, 등록을 강제로 제거했습니다.
    10월 28, 2020 12:16:32 오후 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc
    경고: 웹 애플리케이션 [ROOT]이(가) JDBC 드라이버 [oracle.jdbc.OracleDriver]을(를) 등록했지만, 웹 애플리케이션이 중지될 때, 해당 JDBC 드라이버의 등록을 제거하지 못했습니다. 메모리 누수를 방지하기 위하여, 등록을 강제로 제거했습니다.
    10월 28, 2020 12:16:32 오후 org.apache.coyote.AbstractProtocol start
    정보: 프로토콜 핸들러 ["http-nio-8080"]을(를) 시작합니다.
    10월 28, 2020 12:16:32 오후 org.apache.catalina.startup.Catalina start
    정보: 서버가 [6,497] 밀리초 내에 시작되었습니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      해당 에러의 경우 오라클의 jdbc가 실제로 존재하지 않는 경우 발생하는것으로 예상됩니다.
      오라클 jdbc는 ide에서 추가한것 외에도, 톰캣 폴더 하위의 lib 폴더에 jdbc jar파일을 넣어주시면 될것 같습니다.

      삭제
  18. 로그인시 controller단에서 이름값을 못가져옵니다. db에는 userName에 저장이 되어 있는데
    로그인할때 디버깅해서 보니 userName에 null값이 들어옵니다.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      컨트롤러에서 이름을 못가져왔다면, 매퍼의 쿼리문부터 dao-service-controller 순서로 확인해볼필요가 있습니다.
      (예를들어 dao부터 리턴값이 없는 void라던가.. 물론 이러면 에러가 나겠지만요)

      삭제
  19. 안녕하세요? 질문이 잇습니다 나중에 추가/편집이 용이하게 모든 페이지 구성이 같게 header, nav, footer 3개의 jsp파일을 만든 후 다음부터 만들 로그인, 회원가입 등등 페이지에서 include경로로 하여 모든 페이지 구성이 같게 해준다는 것은 이해했습니다. 그런데 home.jsp에서는 오류가 안나는데 그 외 로그인,회원가입 페이지에서 header,nav,footer 의 include 경로를 home.jsp에서 복사해서 다른페이지에 넣으면 계속 오류가 뜹니다..
    Fragment "include/header.jsp" was not found at expected path /kubg/src/main/webapp/WEB-INF/views/
    admin/include/header.jsp 오류 내용입니다 구글링해도 잘 모르겟어서 댓글 남깁니다ㅠㅠ

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      home.jsp가 있는 파일의 경로와 login.jsp 및 regist.jsp가 있는 파일의 경로가 다르기 때문에 생긴 에러로 보입니다.

      삭제
    2. 아하 알려주셔서 감사합니다! 혹시 가장 최근 스프링 게시판 만들기 글은 이제 추가 작성없이 종료인가요?!

      삭제
    3. 초안 자체는 어느정도 작성되어있기는한데..
      지속적으로 프로젝트에 투입하고, 나태함이 곁들여져서 미뤄지고 있습니다;

      삭제
    4. 알겟습니다 덕분에 공부 잘돼고잇네요 프로젝트 마무리 꼭 잘되시길 바라겟습니당 !!

      삭제
  20. 안녕하세요 로그인을 해도 ${member.userName}님 환영합니다. 이부분과 로그아웃부분이 안나오고 본문 영역만 나옵니다. 회원가입은 잘 되는데 왜 저런지 알려주실수 있나요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      환영 메시지와 로그아웃이 안보인다면.. 로그인이 안된 상황, 즉 <c:if test="${member != null}"> 조건식에서 true 결과가 나오지 않았다는것 입니다. member가 null값인거지요.

      그렇다면, 로그인할때의 데이터가 db까지 잘 이동되었고, 로그인하려는 아이디와 패스워드에 매칭되는 값이 없었다는 의미이므로

      로그인 기능을 쭉 둘러보며 데이터가 잘 전달되는지, 정상적으로(=의도적으로) 동작하는지 확인해보셔야할 것 같습니다.

      삭제
    2. 혹시 코드좀 봐주실수 있나요? 계속 해결을 못하고 있어서요ㅠㅠ

      삭제
    3. 데이터가 잘 전달되는지만 확인하셔도 거의 대부분 해결됩니다.

      코드를 올리시게된다면 view로 시작해서 controller, service, dao, mapper 까지 다 올리셔야 확인이 되겠지만, 그것도 실제 동작하는것과 제가 보는게 다를 수 있으니... 일단 데이터가 제대로 가는지만 보셔도 될 거 같습니다.

      삭제
  21. 익명1/16/2023

    비번을 잘못입력하면 오류메세지가 잘 뜨지만, 아이디를 잘못 입력하면, MemberVO에 null이 들어가고 MemberVO login 이 null인데, login.getUserPass() 이거에서 오류가 나는 거 같습니다. null은 .getUserPass() 이런 메소드 쓰면 오류 뜨는거 맞겠죠? 짧은 코드만 써서 테스트 해보면 아는데 파이썬에서는 걍 파일 만들어서 하면 됏는데 자바는 그냥 프로젝트 통째로 실행시키는 거 밖에 없어서 그런 테스트를 못하겟네요

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      null인 경우 아마 말씀하신대로가 맞으리라 봅니다.

      그래서 보통은 프론트에서 한번 데이터를 검증하고 넘어가고, 백에서도 다시 재검증한 뒤 문제가 발생하면 임의의 코드를 발송하거나 하는 뉘앙스로 진행합니다.

      자바의 경우 유닛테스트 예시가 많으니 이를 이용하면 전체를 실행하지 않아도 메서드별로 테스트할 수 있겠습니다.

      삭제
  22. 익명9/25/2023

    signup.jsp 즉 회원가입을 누르면 화면에 404 에러가 떠서 무엇이 문제 알려주실수 있나요?

    HTTP 상태 404 – 찾을 수 없음
    타입 상태 보고

    메시지 요청된 리소스 [/member/signup]은(는) 가용하지 않습니다.

    설명 Origin 서버가 대상 리소스를 위한 현재의 representation을 찾지 못했거나, 그것이 존재하는지를 밝히려 하지 않습니다.

    Apache Tomcat/9.0.43

    답글삭제