라즈베리파이반

라즈베리파이 등 컴퓨터계열 게시판입니다.

제목스프링 레거시로 게시판 만들기 (10) : 회원가입2022-11-07 08:18
작성자user icon Level 4

88x31.png


이번 글에서는 bean validation을 통해 회원가입을 구현하겠습니다. 


1. 회원가입폼 작성


우선 JSP를 통해 회원가입 화면 템플릿을 작성하겠습니다. 

tailwindcss를 통해 클래스로 스타일을 작성하고, HTML5의 폼검증 API를 통해 클라이언트에서 1차적으로 검증을 하도록 작성했습니다. 


/src/main/webapp/WEB-INF/views/member/include/signUpFormCSS.jsp 

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

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


<style type="text/tailwindcss">

    main {@apply text-gray-600 container px-5 py-16 mx-auto}

    article {@apply lg:w-2/3 w-full mx-auto overflow-auto}

    form {@apply flex flex-col max-w-2xl mx-auto pb-2 gap-3 xl:gap-6 before:text-lg before:xl:text-2xl before:font-bold before:text-gray-600 before:text-center}

    form > label {@apply flex flex-col w-full text-sm sm:text-base}

    form > label > font {@apply before:font-medium before:text-gray-600 before:mr-4}

    form > button[type=submit] {@apply relative flex justify-center rounded-md border border-transparent bg-slate-600 py-2 px-4 mt-4 text-sm font-medium text-white 

        hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2}

    form input {@apply mt-1 w-full px-3 py-2 bg-white border border-violet-300 rounded-md text-sm shadow-sm placeholder-slate-400

        focus:outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 disabled:bg-slate-50 disabled:text-slate-500 

        disabled:border-slate-200 disabled:shadow-none}

</style>


/src/main/webapp/WEB-INF/views/member/signUpForm.jsp 

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

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

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


<!DOCTYPE html>

<html lang="ko">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>

    <%@ include file="./include/signUpFormCSS.jsp" %>

    <title>게시글 목록</title>

</head>

<body>

<main>

    <article>

        <form class="before:content-['회원가입']" action="/member" method="post" onsubmit="return false">

            <input type="hidden" name="act" value="signUp" />

            <label>

                <font class="before:content-['이메일']" color="red">

                    <c:if test="${errors.hasFieldErrors('email')}">* ${errors.getMessage('email')}</c:if>

                </font>

                <c:choose>

                    <c:when test="${!errors.hasFieldErrors('email')}">

                        <c:set var="value" value="${errors.getFieldValue('email')}" />

                    </c:when>

                    <c:otherwise><c:remove var="value" /></c:otherwise>

                </c:choose>

                <input type="email" name="email" required placeholder="Email" value="${value}" />

            </label>

            <label>

                <font class="before:content-['비밀번호']" color="red">

                    <c:if test="${errors.hasFieldErrors('password')}">* ${errors.getMessage('password')}</c:if>

                </font>

                <input type="password" name="password" minlength="8" maxlength="20" required placeholder="Password" />

            </label>

            <label>

                <font class="before:content-['비밀번호_확인']" color="red">

                    <c:if test="${errors.hasFieldErrors('rePassword')}">* ${errors.getMessage('rePassword')}</c:if>

                </font>

                <input type="password" name="rePassword" minlength="8" maxlength="20" required placeholder="Password" />

            </label>

            <label>

                <font class="before:content-['이름']" color="red">

                    <c:if test="${errors.hasFieldErrors('name')}">* ${errors.getMessage('name')}</c:if>

                </font>

                <c:choose>

                    <c:when test="${!errors.hasFieldErrors('name')}">

                        <c:set var="value" value="${errors.getFieldValue('name')}" />

                    </c:when>

                    <c:otherwise><c:remove var="value" /></c:otherwise>

                </c:choose>

                <input type="text" name="name" required placeholder="Name" value="${value}" />

            </label>

            <label>

                <font class="before:content-['닉네임']" color="red">

                    <c:if test="${errors.hasFieldErrors('nickName')}">* ${errors.getMessage('nickName')}</c:if>

                </font>

                <c:choose>

                    <c:when test="${!errors.hasFieldErrors('nickName')}">

                        <c:set var="value" value="${errors.getFieldValue('nickName')}" />

                    </c:when>

                    <c:otherwise><c:remove var="value" /></c:otherwise>

                </c:choose>

                <input type="text" name="nickName" required placeholder="Nickname" value="${value}" />

            </label>

            <button type="submit" class="before:content-['회원가입']"></button>

        </form>

    </article>

</main>


<script>

document.querySelector('button[type=submit]').addEventListener('click', function() {

    if(document.querySelector('input[name=password]').value != document.querySelector('input[name=rePassword]').value) {

        return document.querySelector('input[name=rePassword]').setCustomValidity("패스워드가 일치하지 않습니다.");

    }

    document.querySelector('form').submit();

});

</script>

</body>

</html>



2. Mapper, DAO, Service 수정


이메일이 중복되었는지 확인하기 위해 MemberDAO에 getMemberByEmail 메소드를 추가합니다. 


/kro/rubisco/dao/MemberDAO.java 

package kro.rubisco.dao;


import java.util.List;


import org.apache.ibatis.annotations.Mapper;


import kro.rubisco.dto.MemberDTO;


@Mapper

public interface MemberDAO {


    public void create(MemberDTO member) throws Exception;

    

    public MemberDTO read(Long memberId) throws Exception;

    

    public MemberDTO getMemberByEmail(String email) throws Exception;


    public void update(MemberDTO member) throws Exception;


    public void delete(Long memberId) throws Exception;


    public List<MemberDTO> listAll() throws Exception;

}


memberMapper.xml 파일에 getMemberByEmail라는 id를 가지는 select 쿼리문을 작성하여 MemberDAO의 getMemberByEmail 메소드에 매핑하도록 하겠습니다. 


memberMapper.xml 

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

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="kro.rubisco.dao.MemberDAO">


<resultMap id="getMember" type="MemberDTO">

    <association property="group" column="group_id" select="getGroup" />

</resultMap>


<insert id="create">

insert into member (password, email, name, nick_name, group_id)

values (#{password}, #{email}, #{name}, #{nickName}, #{groupId})

</insert>


<select id="read" resultMap="getMember">

select * from member where member_id = #{memberId}

</select>


<select id="getMemberByEmail" resultMap="getMember">

select * from member where email = #{email}

</select>


<select id="getGroup" resultType="GroupDTO">

select * from member_group where group_id=#{groupId}

</select>


<update id="update">

update member 

set password=#{password},

    email=#{email}, 

    name=#{name}, 

    nick_name= #{nickName},

where member_id = #{memberId}

</update>


<delete id="delete"> delete from member where member_id = #{memberId} </delete>


<select id="listAll" resultMap="getMember">

<![CDATA[ select * from member where m.member_id > 0 order by m.member_id desc ]]>

</select>


</mapper>


MemberService 인터페이스에 email을 매개변수로 받는 read 메소드를 오버로드하여 작성합니다. 


/kro/rubisco/service/MemberService.java 

package kro.rubisco.service;


import java.util.List;


import kro.rubisco.dto.MemberDTO;


public interface MemberService {

    

      public void regist(MemberDTO member) throws Exception;


      public MemberDTO read(Long memberId) throws Exception;

      

      public MemberDTO read(String email) throws Exception;


      public void modify(MemberDTO member) throws Exception;


      public void remove(Long memberId) throws Exception;


      public List<MemberDTO> listAll() throws Exception;

}


해당 인터페이스의 구현체를 작성합니다.


/kro/rubisco/service/impl/MemberServiceImpl.java

package kro.rubisco.service.impl;


import java.util.List;


import org.apache.ibatis.session.SqlSession;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;


import kro.rubisco.dao.MemberDAO;

import kro.rubisco.dto.MemberDTO;

import kro.rubisco.service.MemberService;


@Service

@Transactional(readOnly = true)

public class MemberServiceImpl implements MemberService {


    private final MemberDAO memberDAO;

    

    @Autowired

    public MemberServiceImpl(SqlSession sqlSession) {

        this.memberDAO = sqlSession.getMapper(MemberDAO.class);

    }

    

    @Override

    public void regist(MemberDTO member) throws Exception {

        memberDAO.create(member);

    }


    @Override

    public MemberDTO read(Long memberId) throws Exception {

        return memberDAO.read(memberId);

    }

    

    @Override

    public MemberDTO read(String email) throws Exception {

        return memberDAO.getMemberByEmail(email);

    }


    @Override

    public void modify(MemberDTO member) throws Exception {

        memberDAO.update(member);

    }


    @Override

    public void remove(Long memberId) throws Exception {

        memberDAO.delete(memberId);

    }


    @Override

    public List<MemberDTO> listAll() throws Exception {

        return memberDAO.listAll();

    }


}



3. DTO, Controller 수정


bean validation을 위해 MemberDTO에 다음과 같이 어노테이션을 붙여줍니다.


/kro/rubisco/dto/MemberDTO.java

package kro.rubisco.dto;


import java.util.Date;


import javax.validation.constraints.Email;

import javax.validation.constraints.NotBlank;

import javax.validation.constraints.Size;


import lombok.Getter;

import lombok.Setter;


@Getter

@Setter

public class MemberDTO {

    

    private Long memberId;

    

    @NotBlank(message = "패스워드를 입력하세요.")

    @Size(min = 8, max = 20, message = "패스워드는 8글자 이상 20글자 이하로 입력하세요.")

    private String password;

    

    @NotBlank(message = "이메일을 입력하세요.")

    @Email

    private String email;

    

    @NotBlank(message = "이름을 입력하세요.")

    private String name;

    

    @NotBlank(message = "닉네임을 입력하세요.")

    private String nickName;

    

    private Long groupId;

    private GroupDTO group;

    private Date createDate;

    private Date lastLogin;

}


마지막으로 MemberController의 insertMember 메소드를 수정해주세요.


/kro/rubisco/controller/MemberController.java

package kro.rubisco.controller;


import java.util.Locale;


import org.springframework.context.MessageSource;

import org.springframework.stereotype.Controller;

import org.springframework.validation.BindingResult;

import org.springframework.validation.FieldError;

import org.springframework.validation.annotation.Validated;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.PatchMapping;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;


import kro.rubisco.config.BindExceptionWithViewName;

import kro.rubisco.dto.MemberDTO;

import kro.rubisco.service.MemberService;

import lombok.RequiredArgsConstructor;


@Controller

@RequiredArgsConstructor

@RequestMapping("/member")

public class MemberController {


    private final MessageSource messageSource;

    private final MemberService memberService;

    

    @GetMapping()

    public String getMemberInfo() {

        return "member/getMemberInfo";

    }

    

    @GetMapping(params="act=signUp")

    public String getSignUpView() throws Exception {

        return "member/signUpForm";

    }

    

    @PostMapping()

    public String insertMember(

        @Validated @ModelAttribute("member") MemberDTO member, 

        BindingResult bindingResult, 

        @RequestParam("rePassword") String rePassword,

        Locale locale

    ) throws Exception {

        

        if(!member.getPassword().equals(rePassword)) {

            bindingResult.addError(new FieldError("java.lang.String", "rePassword", "패스워드가 일치하지 않습니다."));

        }


        if(memberService.read(member.getEmail()) != null) {

            bindingResult.rejectValue("email", "overlap.email", "동일한 이메일이 존재합니다.");

        }

        

        if(bindingResult.hasErrors()) {

            throw new BindExceptionWithViewName(bindingResult, "member/signUpForm", messageSource, locale);

        }

        

        member.setGroupId(1L);

        memberService.regist(member);

        

        return "redirect:/";

    }

    

    @PatchMapping()

    public String updateMember(MemberDTO member) throws Exception {

        memberService.modify(member);

        return "redirect:/member";

    }

    

    @DeleteMapping()

    public String deleteMember() throws Exception {

        return "redirect:/";

    }

}


검증기를 따로 작성하지 않고 메소드 자체에 패스워드와 이메일 중복 검증 로직을 작성했습니다. 아직 그룹을 생성하는 폼이 없으므로, 이전에 DB에 입력한 Admin 그룹의 groupId인 1L을 주입하여 회원가입이 되도록 했습니다.


아래 화면은 이메일을 중복하여 가입했을 경우 나타나는 화면입니다.

#스프링 레거시# 회원가입# Bean Validation
댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)