공부/그룹 스터디

[5회차 03] 로그인 - 예외처리

junani0v0 2024. 5. 5. 22:21

예외처리

먼저 현재 회원가입되지 않은 값을 넣어 로그인해보면


이처럼 500에러가 발생하고


콘솔창에는 기대값은 1인데 아무것도 안나와 0이기에 EmptyResultDataAccessException이 발생하였습니다 (null값을 주지 않음)

하지만 사용자에게는 절대 이러한 에러 페이지를 보여주면 안되기에 예외처리를 해보겠습니다

Tip

예외 코드를 보면 아래서부터 실행되는 것입니다 thread가 생성 -> javax.servlet 이 받고 springframework가 servlet을 땡겨와 dispatcher servlet -> RequestMapping -> 실행시켜 찾아온 곳이 우리가 작성한 패키지의 클래스와 메서드가 나오며 그중 제일 위에 끝난부분이 에러가 발생한 곳으로 우리가 수정할 수 있는 부분이 나옵니다

at com.portfolio.www.dao.MemberDao.getMember(MemberDao.java:51)
at com.portfolio.www.service.JoinService.login(JoinService.java:39)
at com.portfolio.www.controller.LoginController.login(LoginController.java:31)

코드를 보면 com.portfolio.www.dao.MemberDao.getMember에서 최종적으로 에러가 발생한 것을 알 수 있습니다

(그 위에 부분인 JdbcTemplate, DataAccessUtils을 들어가보시면 예외를 전에 있는 곳으로 예외되전지기를 하도록 되어있는데 그 다음인 MemberDao에서는 예외처리를 안해서 거기서 에러가 발생)
그리고 MemberDao에서 예외가 발생했지만 절대 Dao에서는 예외처리를 하지 않습니다 그냥 그다음 Service로 예외를 던져줘야합니다

MemberDao 예외던지기

Dao에서는 절대 예외처리를 하지 않음으로 예외를 그 다음 장소인 Service로 예외를 던져줍니다
그러면 cosole에 찍힌 에러를 받은 com.portfolio.www.dao.MemberDao.getMember로 가서 예외 던지기를 해줍니다

기존 코드

public MemberDto getMember(String memberId) {
    String sql = "SELECT * FROM member WHERE member_id= ? ";
    Object[] args = {memberId};

    return queryForObject(sql, new MemberRowMapper(), args);
}

수정 코드

public MemberDto getMember (String memberId) throws EmptyResultDataAccessException{
    String sql = "SELECT * FROM member WHERE member_id= ? ";
    Object[] args = {memberId};

    return queryForObject(sql, new MemberRowMapper(), args);
}

메서드 뒤에 예외를 던지기위해 throw를 넣어주고 그다음 발생한 예외를 적어주고 import해줍니다
그러면 예외가 이제 다음 장소인
com.portfolio.www.service.JoinService.login로 예외가 던져입니다

JoinService 예외처리

기존 코드

    public MemberDto login(HashMap<String, String> params) {
    //사용자 찾기
        MemberDto memberDto = memberDao.getMember(params.get("memberId"));
        String passwd = params.get("passwd"); 
        //사용자가 입력한 비밀번호
        String dbpasswd = memberDto.getPasswd(); 
        //암호화해서 DB에 저장된 비밀번호
    //비밀번호 비교
        BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), dbpasswd);

        return result.verified ? memberDto : null ;
    }

이제 넘어온 예외를 try-catch를 사용해 처리해 줍니다

    public MemberDto login(HashMap<String, String> params) {
        MemberDto memberDto = null;
        try {
            //사용자 찾기
            memberDto = memberDao.getMember(params.get("memberId"));
            String passwd = params.get("passwd"); 
            //사용자가 입력한 비밀번호
            String dbpasswd = memberDto.getPasswd(); 
            //암호화해서 DB에 저장된 비밀번호
        //비밀번호 비교
            BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), dbpasswd);

            return result.verified ? memberDto : null ;

        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

이때 MemberDto 선언을 밖으로 빼주고 null값을 넣고 나머지 코드를 try안에 넣고 catch에 괄호 안에 예외이름과 변수 명을 적고 catch안에 return null을 해주면 예외가 발생할 경우 null 값을 반환하여 controller에서 따로 코드가 수행되지 않고 return mv 됩니다


이제 서버를 재시작해 회원가입 하지 않은 아이디로 로그인하면 에러페이지가 나오지않지만 login.do페이지로 가게됩니다
우리는 로그인을 해야지만 이 페이지를 보여주고 싶기에 LoginController에서 아이디가 없다는 메시지와 로그인페이지로 다시 보내게 변경해봅니다

LoginController

기존 코드

@RequestMapping("login.do")
public ModelAndView login(
        HttpServletRequest request,
        @RequestParam HashMap<String, String> params) {
    ModelAndView mv = new ModelAndView();
    MemberDto memberDto = joinService.login(params);

    if(!ObjectUtils.isEmpty(memberDto)) {    //로그인을 했으면 null값이 아닐테니
    //세션 처리
        //세션을 불러와서
        HttpSession session = request.getSession();
        //memberId넣어줌
        session.setAttribute("memberId", memberDto.getMemberId());
    }
    mv.setViewName("main");

    return mv;
}

이제 null일경우 수행할 else문을 추가해줍니다

수정 코드

@RequestMapping("login.do")
public ModelAndView login(
        HttpServletRequest request,
        @RequestParam HashMap<String, String> params) {
    ModelAndView mv = new ModelAndView();
    MemberDto memberDto = joinService.login(params);

    if(!ObjectUtils.isEmpty(memberDto)) {    //로그인을 했으면 null값이 아닐테니
    //세션 처리
        //세션을 불러와서
        HttpSession session = request.getSession();
        //memberId넣어줌
        session.setAttribute("memberId", memberDto.getMemberId());
        mv.setViewName("main");
    }else {//null일 경우
        mv.setViewName("login");
        mv.addObject("code", "9000");
        mv.addObject("msg", "사용자가 없습니다");
    }

    return mv;
}

이렇게 null값일 경우 실행될 코드를 else문에 넣고 로그인 페이지로 보내기 위해 setViewName을 login으로 해주고 사용자에게 보여줄 메시지를 위해 msg와 에러 코드를 mv에 넣어줍니다(전에 사용한 result를 code로 변경하겠습니다)

login.jsp

<script type="text/javascript">
    window.onload = function(){
        var code = '${code}';
        var msg = '${msg}';
        if(code != '' && code != '0000'){
            alert(msg);
            window.location.href = '/05/loginPage.do';
        }
    }
</script>

login.jsp를 이렇게 code로 수정하고 다음에 추가할꺼지만 정상일 경우
code = 0000으로 할 것이기에 조건을 0000과 ' ' 아닐경우 실행하도록 변경합니다


이제 서버를 재시작해 회원가입하지 않은 아이디로 로그인하면 메시지가 잘 나옵니다

예외처리 2

하지만 여기서 비밀번호가 틀렸을때와 아이디값이 없을때 똑같이 null이 나오는데 두개를 어떻게 구분하고 알림 메시지를 띄울것인가요??

JoinService에서 login 메서드의 반환값이 MemberDto인데 MemberDto 하나로는 두가지를 구분하는 것은 불가능합니다

이때 해결 방법은 JoinService에서 예외처리를 하지 않고 LoginController에게 예외를 던져줍니다(예외전파)

JoinService 예외 던지기(예외전파)

    public MemberDto login(HashMap<String, String> params) throws EmptyResultDataAccessException {
        //사용자 찾기
            MemberDto memberDto = memberDao.getMember(params.get("memberId"));
            String passwd = params.get("passwd"); 
             //사용자가 입력한 비밀번호
            String dbpasswd = memberDto.getPasswd(); 
             //암호화해서 DB에 저장된 비밀번호
        //비밀번호 비교
            BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), dbpasswd);
            return result.verified ? memberDto : null ;
    }

앞에서 말했듯 회원가입여부와 비밀번호 불일치로 인한 null값을 구분하기 위해 예외처리를 안하고 예외를 던져줍니다

LoginController 예외처리

@RequestMapping("login.do")
public ModelAndView login(
        HttpServletRequest request,
        @RequestParam HashMap<String, String> params) {
    ModelAndView mv = new ModelAndView();
    MemberDto memberDto = null;

    try {
        memberDto = joinService.login(params);    //사용자 확인
        if(!ObjectUtils.isEmpty(memberDto)) {    //로그인을 했으면 null값이 아닐테니
            //세션 처리
            //세션을 불러와서
            HttpSession session = request.getSession();
            //memberId넣어줌
            session.setAttribute("memberId", memberDto.getMemberId());
            mv.setViewName("main");
        }else {    //비밀번호가 다른경우
            mv.setViewName("login");
            mv.addObject("code", "9001");
            mv.addObject("msg", "비밀번호가 틀렸습니다");
        }
    } catch (EmptyResultDataAccessException e) { //사용자가 없는경우
        mv.setViewName("login");
        mv.addObject("code", "9000");
        mv.addObject("msg", "사용자가 없습니다");
    }
    return mv;
}

이렇게 LoginController에서 try-catch를 사용하여 예외처리를 하면 먼저 정상적인 경우 try안에 있는

memberDto = joinService.login(params);

로 사용자를 확인하고 있으면 if문이 실행도고 null일경우 else문이 실행
회원가입하지않은 아이디로 로그인 할경우 예외가 넘어와 catch문이 실행되게 함으로서 회원이 아니어서 발생한 null값과 비밀번호가 틀려나온 null값을 구분해줍니다

 


테스트를 해보면 구분해서 잘 작동하는 것을 확인할 수 있습니다

예외전파 정리

  • 예외를 던지면 항상 호출한곳으로 돌아간다
  • 호출한 곳에서는 예외를 받아서 try-catch로 처리할 수 있다
  • 처리를 안하고 호출한곳으로 예외던질 수 있다
  • 그렇기에 어디서 예외가 발생한건지 잘 알아야 해결가능
  • try-catch에서 절대 catch에 exception으로 적지 않고 직접 무슨 예외인지 적어준다(실제 발생하는 예외 하나만)