멋쟁이v의 개발일지

[Spring] Mybatis 회원관리(2) - 회원수정, 탈퇴 본문

0년차/Spring

[Spring] Mybatis 회원관리(2) - 회원수정, 탈퇴

멋쟁이v 2023. 7. 8. 11:47
728x90
320x100


01. mybatis를 활용한 회원관리 - 회원수정

회원수정화면
1️⃣
회원 목록에서 수정 버튼에 타임리프 구문으로 쿼리스트링 표현

th:href=”@{/member/modifyMember(memberId=${l.memberId})}”

➡️ Controller에서 해당 쿼리스트링을 받을 수 있다. (memberId=수정하려는id)

  • [코드]memberList.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout/default}">
    
    <head>
        <link rel="stylesheet" type="text/css" th:href="@{/css/table.css}">
    </head>
    
    <th:block layout:fragment="customContents">
        <table>
            <thead>
            <tr>
                <th>회원아이디</th>
                <th>회원비밀번호</th>
                <th>회원이름</th>
                <th>회원등급</th>
                <th>회원이메일</th>
                <th>회원주소</th>
                <th>회원등록날짜</th>
                <th>수정</th>
                <th>삭제</th>
            </tr>
            </thead>
            <tbody>
            <tr th:if="${#lists.isEmpty(memberList)}">
                <td colspan="7">등록된 회원의 정보가 없습니다.</td>
            </tr>
            <tr th:unless="${#lists.isEmpty(memberList)}"
                th:each="l : ${memberList}">
                <td th:text="${l.memberId}"></td>
                <td th:text="${l.memberPw}"></td>
                <td th:text="${l.memberName}"></td>
                <td th:text="${l.memberLevel}"></td>
                <td th:text="${l.memberEmail}"></td>
                <td th:text="${l.memberAddr}"></td>
                <td th:text="${l.memberRegDate}"></td>
                <td>
                    <a th:href="@{/member/modifyMember(memberId=${l.memberId})}">수정</a>
                </td>
                <td>
                    <a th:href="@{#}">삭제</a>
                </td>
            </tr>
            </tbody>
        </table>
    </th:block>
    </html>

2️⃣
Controller

➡️ @RequestParam 로 쿼리스트링을 받는다.

➡️ 수정화면으로 주소를 이동한다.

  • [코드]Controller.java
    @GetMapping("/modifyMember")
    public String modifyMember(@RequestParam(value="memberId") String memberId,
                               Model model) {
    
        model.addAttribute("title", "회원수정");
        
        return "member/modifyMember";
    }

3️⃣
수정 화면에 해당 회원의 정보를 불러와야 한다. (Mapper)

➡️ 해당 회원의 정보를 조회하는 쿼리

➡️ 회원 등급을 관리자, 판매자, 구매자로 나타내도록 회원 등급 목록을 조회하는 쿼리

➡️ Mapper.xml에서 resultMap

  • 해당 클래스(dto)의 프로퍼티가 테이블의 어떤 컬럼과 매칭될지를 정의해준다.
  • id 태그는 테이블의 기본키, result 태그는 테이블의 일반컬럼, 외래키
  • [코드]Mapper.xml
    <!-- Map : 키와 값 / type에는 dto에 들어가야기 때문에 Member -->
    <!-- mybatis DTO 와 ResultSet의 결과값을 미리 매핑 정의 -->
    <resultMap type="Member" id="memberResultMap">
        <!-- id태그는 조회시 테이블의 PK(기본키) -->
        <!-- result태그는 조회시 테이블의 일반컬럼 혹은 외래키 -->
        <id     column="m_id"       property="memberId"/>
        <result column="m_pw"       property="memberPw"></result>
        <result column="m_name"     property="memberName"></result>
        <result column="m_level"    property="memberLevel"></result>
        <result column="m_email"    property="memberEmail"></result>
        <result column="m_addr"     property="memberAddr"></result>
        <result column="m_reg_date" property="memberRegDate"></result>
    </resultMap>
    
    <!-- 생략 -->
    
    <!-- 정의한 resultMap을 가져온다. -->
    <select id="getMemberInfoById" parameterType="String" resultMap="memberResultMap">
        /* 회원별 상세조회 */
        SELECT
            m.m_id,
            m.m_pw,
            m.m_name,
            m.m_level,
            m.m_email,
            m.m_addr,
            m.m_reg_date
        FROM
            tb_member AS m
        WHERE
            m.m_id = #{memberId};
    </select>
    
    <select id="getMemberLevelList" resultType="MemberLevel">
        /* 회원등급 조회 */
        SELECT
            l.level_num AS levelNum,
            l.level_name AS levelName
        FROM
            tb_member_level AS l
    </select>

  • [코드]Mapper.java
    // 회원별 상세조회
    public Member getMemberInfoById(String memberId);
    
    // 회원등급 조회
    public List<MemberLevel> getMemberLevelList();

4️⃣
Service

➡️ Mapper에서 선언한 메서드를 호출해서 결과값을 반환한다.

  • [코드]Service.java
    /**
    * 회원별 상세조회
    * @param memberId(회원아이디)
    * @return Member (회원정보)
    */
    public Member getMemberById(String memberId) {
    
      Member memberInfo = memberMapper.getMemberInfoById(memberId);
    
      return memberInfo;
    }
    
    /**
    * 회원등급 조회
    * @return
    */
    public List<MemberLevel> getMemberLevelList() {
    
      List<MemberLevel> memberLevelList = memberMapper.getMemberLevelList();
    
      return memberLevelList;
    }

5️⃣
Controller

➡️ Service에서 선언한 메서드를 호출 후 결과값을 model에 넘겨준다.

  • [코드]Controller.java
    @GetMapping("/modifyMember")
    public String modifyMember(@RequestParam(value="memberId") String memberId,
                               Model model) {
    
    		// 회원 상세조회
        Member memberInfo = memberService.getMemberById(memberId);
    
        // 회원등급 목록조회
        List<MemberLevel> memberLevelList = memberService.getMemberLevelList();
    
        model.addAttribute("title", "회원수정");
    		model.addAttribute("memberInfo", memberInfo);
        model.addAttribute("memberLevelList", memberLevelList);
        
        return "member/modifyMember";
    }

6️⃣
수정화면(html)

➡️ form으로 화면을 구성하고, 타임리프를 활용해서 전달받은 데이터를 화면에 출력한다.

  • [코드]modifyMember.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout/default}">
    
    <head>
      <link rel="stylesheet" type="text/css" th:href="@{/css/table.css}">
      <style>
        input {
          width: 98%;
          height: 23px;
        }
    
        select {
          width: 98%;
          text-align: center;
          height: 23px;
        }
    
        #modifyMemberBtn, #resetBtn {
          width: 49%;
          height: 25px;
        }
      </style>
    </head>
    
    <th:block layout:fragment="customContents">
      <form id="modifyMemberForm" th:action="@{/member/modifyMember}" method="post">
        <table th:object="${memberInfo}">
          <tbody>
          <tr>
            <td>
              <label for="memberId">회원아이디</label>
            </td>
            <td>
              <!-- th:value="${memberInfo.memberId}" -->
              <input type="text" id="memberId" name="memberId" th:value="*{memberId}"
                     placeholder="회원의 아이디를 입력해주세요." readonly="readonly"/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberPw">회원비밀번호</label>
            </td>
            <td>
              <input type="text" id="memberPw" name="memberPw" th:value="*{memberPw}"
                     placeholder="회원의 비밀번호를 입력해주세요."/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberName">회원이름</label>
            </td>
            <td>
              <input type="text" id="memberName" name="memberName" th:value="*{memberName}"
                     placeholder="회원의 이름을 입력해주세요."/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberLevel">회원등급</label>
            </td>
            <td>
              <select id="memberLevel" name="memberLevel">
                <th:block th:unless="${#lists.isEmpty(memberLevelList)}"
                          th:each="l : ${memberLevelList}">
                  <option th:value="${l.levelNum}" th:selected="${l.levelNum} == *{memberLevel}">[[${l.levelName}]]</option>
                </th:block>
              </select>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberEmail">회원이메일</label>
            </td>
            <td>
              <input type="text" id="memberEmail" name="memberEmail" th:value="*{memberEmail}"
                     placeholder="회원의 이메일을 입력해주세요."/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberAddr">회원주소</label>
            </td>
            <td>
              <input type="text" id="memberAddr" name="memberAddr" th:value="*{memberAddr}"
                     placeholder="회원의 주소를 입력해주세요."/>
            </td>
          </tr>
          <tr>
            <td colspan="2">
              <button type="submit" id="modifyMemberBtn">수정완료</button>
              <button type="reset" id="resetBtn">입력취소</button>
            </td>
          </tr>
          </tbody>
        </table>
      </form>
    </th:block>
    </html>

회원수정처리
1️⃣
수정화면에서 수정완료를 눌렀을 때 폼에 post방식으로 주소를 요청한다.

<form id="modifyMemberForm" th:action="@{/member/modifyMember}" method="post">

2️⃣
Controller

➡️ @PostMapping(”/modifyMember”) 로 post방식으로 요청 받은 주소를 처리한다.

➡️ return 화면으로는 수정처리 완료 후 다시 회원목록 화면으로 리다이렉트를 해준다.

  • [코드]Controller.java
    @PostMapping("/modifyMember")
        public String modifyMember(Member member) {
    
            return "redirect:/member/memberList";
    
        }

3️⃣
Mapper

수정처리를 위한 쿼리를 작성 후 인터페이스와 연결 시켜준다.

➡️ trim : 동적 쿼리를 작성하기 위한 태그입니다.

  • prefix : 맨 앞에 해당 값을 붙여준다.
  • prefixOverrides : 맨 앞에 해당 값이 있으면 삭제한다.
  • suffix : 맨 뒤에 해당 값을 붙여준다.
  • suffixOverrides : 맨 뒤에 해당 값이 있으면 삭제한다.

🗣 set, where는 태그만 써줘도 동적으로 쿼리가 실행된다. (trim 태그를 대체)

  • [코드]Mapper.xml
    <update id="modifyMember" parameterType="Member">
        /* 회원 수정 처리 */
        UPDATE tb_member
        <trim prefix="SET" suffixOverrides=",">
        <!--<set>-->
            <if test="memberPw != null and memberPw != ''">
                m_pw = #{memberPw},
            </if>
            <if test="memberName != null and memberName != ''">
                m_name = #{memberName},
            </if>
            <if test="memberLevel != null and memberLevel != ''">
                m_level = #{memberLevel},
            </if>
            <if test="memberEmail != null and memberEmail != ''">
                m_email = #{memberEmail},
            </if>
            <if test="memberAddr != null and memberAddr != ''">
                m_addr = #{memberAddr}
            </if>
        <!--</set>-->
        </trim>
        WHERE
            m_id = #{memberId};
    </update>
  • [코드]Mapper.java
    // 회원 수정 처리
    public int modifyMember(Member member);

4️⃣
Service

mapper에 생성한 메서드를 호출하고, 반환 타입을 확인 후 리턴을 해준다.

➡️ @Transactional 을 통해서 트랜잭션 처리를 해준다.

  • [코드]Service.java
    public int modifyMember(Member member) {
    
        int result = memberMapper.modifyMember(member);
    
        return result;
    }

5️⃣
Controller

Service에 생성한 메서드를 호출한다.

➡️ 수정처리가 실행되고, 화면이 회원목록으로 이동 된다.

  • [코드]Controller.java
    @PostMapping("/modifyMember")
    public String modifyMember(Member member) {
    
        memberService.modifyMember(member);
    
        return "redirect:/member/memberList";
    }

02. mybatis를 활용한 회원관리 - 회원탈퇴

💡
회원 탈퇴 시 데이터베이스의 각 테이블 간 관계를 확인해야 한다.
회원탈퇴화면
1️⃣
회원 탈퇴를 위한 화면을 회원 목록에서 삭제 버튼에 타임리프 구문으로 쿼리스트링 표현

th:href=”@{/member/removeMember(memberId=${l.memberId})}”

➡️ Controller에서 해당 쿼리스트링을 받을 수 있다. (memberId=수정하려는id)

2️⃣
Controller

➡️ GET방식으로 전달받은 데이터를 @RequestParam을 통해서 매개변수에 담아준다.

➡️ 데이터(memberId)를 model에 담아서 view로 전달한다.

  • [코드]Controller.java
    @GetMapping("/removeMember")
    public String removeMember(@RequestParam(value = "memberId") String memberId,
                               Model model) {
    
        model.addAttribute("title", "회원탈퇴");
        model.addAttribute("memberId", memberId);
    
        return "member/removeMember";
    }

3️⃣
탈퇴화면

➡️ 비밀번호를 입력하고, 해당 회원의 정보와 일치하면 탈퇴처리한다.

➡️ 폼으로 구성하고, 전달받은 데이터를 타임리프로 화면에 출력한다.

  • 비밀번호만 입력하는 폼으로 구성하여, 아이디 값은 hidden 속성으로 감추고, value에 값을 설정한다.
  • [코드]removeMember.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout/default}">
    
    <head>
        <link rel="stylesheet" type="text/css" th:href="@{/css/table.css}">
        <style>
            input {
                width: 98%;
                height: 23px;
            }
    
            select {
                width: 98%;
                text-align: center;
                height: 23px;
            }
    
            #removeMemberBtn, #resetBtn {
                width: 49%;
                height: 25px;
            }
        </style>
    </head>
    
    <th:block layout:fragment="customContents">
        <form id="removeMemberForm" th:action="@{/member/removeMember}" method="post">
            <table>
                <tbody>
                <tr>
                    <td>
                        <label for="memberPw">회원비밀번호</label>
                    </td>
                    <td>
                        <input type="hidden" name="memberId" th:value="${memberId}">
                        <input type="text" id="memberPw" name="memberPw"
                               placeholder="회원의 비밀번호를 입력해주세요."/>
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <button type="submit" id="removeMemberBtn">탈퇴완료</button>
                        <button type="reset" id="resetBtn">입력취소</button>
                    </td>
                </tr>
                </tbody>
            </table>
        </form>
    </th:block>
    </html>

회원탈퇴처리
💡
1) 회원의 탈퇴처리를 위해 테이블 관계를 확인한다.

2) 회원 테이블과 관련 있는 테이블의 정보를 먼저 삭제한다.

3) 마지막으로 회원 테이블에 있는 해당 회원의 아이디를 삭제한다.

➡️ 테이블 관계

  • 권한 별 회원 테이블과 연결된 테이블

    관리자 → 로그인 내역

    판매자 → 로그인 내역, 주문이력, 상품

    구매자 → 로그인 내역, 주문이력

➡️ 권한별 테이블 삭제 순서

  • 관리자 : 로그인내역 → 회원
  • 판매자 : 주문이력 → 상품 → 로그인내역 → 회원
  • 구매자 : 주문이력 → 로그인내역 → 회원

    (공통 : 로그인내역, 회원)

1️⃣
탈퇴화면에서 탈퇴완료 버튼을 눌렀을 때, 폼에 post 방식으로 주소를 요청한다.

<form id="removeMemberForm" th:action="@{/member/removeMember}" method="post">

2️⃣
Controller

➡️ @PostMapping(”/removeMember”) 로 post방식으로 요청 받은 주소를 처리한다.

➡️ @RequestParam에 아이디와, 비밀번호를 받는다.

➡️ return 화면으로는 탈퇴처리 완료 후 다시 회원목록 화면으로 리다이렉트를 해준다.

🗣 RedirectAttributes

  • redirect 시 데이터를 전달하는 방법이다. (리다이렉트가 될때)
  • string 형태와 map, list, vo 등의 object 형태로 넘겨줄 수 있다.
  • [코드]Controller.java
    @PostMapping("/removeMember")
    public String removeMember(@RequestParam(value = "memberId") String memberId,
                               @RequestParam(value = "memberPw") String memberPw,
                               RedirectAttributes reAttr) {
    
        reAttr.addAttribute("memberId", memberId);
    
        return "redirect:/member/memberList";
    }

3️⃣
Service

➡️ 탈퇴처리시 회원 검증을 하고, 비밀번호가 일치해야 탈퇴처리가 가능하다.

➡️ Map을 이용해서 검증여부(boolean타입)와 검증이 완료된 회원객체(Member타입)를 담아서 리턴 해준다.

  • 성공시 : 검증여부, 객체 / 실패시 : 검증여부

🗣 Map에는 여러가지 데이터 타입이 담길 수 있다.

  • [코드]Service.java
    public Map<String, Object> isValidMember(String memberId, String memberPw) {
    
      Map<String, Object> resultMap = new HashMap<String, Object>();
    
      boolean isValid = false;
    
      // 회원 검증
      Member member = memberMapper.getMemberInfoById(memberId);
      if (member != null) {
          String checkPw = member.getMemberPw();
          if (checkPw.equals(memberPw)) {
              isValid = true;
              resultMap.put("memberInfo", member);
          }
      }
    
      resultMap.put("isValid", isValid);
    
      return resultMap;
    }

4️⃣
Controller

➡️ Service에서 작성한 로직을 호출한다.

➡️ 회원 검증 성공시, 실패시 리다이렉트 경로를 다르게 설정해준다.

  • 검증 실패시 RedirectAttributes 클래스를 통해서 메세지를 넘겨준다.
  • [코드]Controller.java
    @PostMapping("/removeMember")
    public String removeMember(@RequestParam(value = "memberId") String memberId,
                               @RequestParam(value = "memberPw") String memberPw,
                               RedirectAttributes reAttr) {
    
    		// 회원 여부 검증
        Map<String, Object> isVaildMap = memberService.isValidMember(memberId, memberPw);
        boolean isValid = (boolean) isVaildMap.get("isValid");
    
        // 비밀번호 일치 회원탈퇴
        if(isValid) {
            Member memberInfo = (Member) isVaildMap.get("memberInfo");
    
            return "redirect:/member/memberList";
        }
    
        reAttr.addAttribute("memberId", memberId);
        reAttr.addAttribute("msg", "회원의 정보가 일치하지 않습니다.");
    
        return "redirect:/member/removeMember";
    }

5️⃣
회원과 상품을 나눠서 처리한다.

➡️ 회원 → 구매자별 구매이력 삭제, 로그인 이력 삭제, 회원삭제

➡️ 상품 → 판매자가 등록한 상품코드별 주문이력 삭제, 판매자별 상품 삭제

  • MemberMapper와 GoodsMapper에 나눠서 쿼리문과 각각의 mapper를 연결시켜준다.

6️⃣
MemberMapper

데이터 삭제처리를 위한 쿼리를 작성 후 인터페이스와 연결시켜준다.

➡️ 구매이력, 로그인 이력, 회원 삭제 쿼리 작성

  • [코드]MemberMapper.xml
    <delete id="removeOrderById" parameterType="String">
        /* 구매자별 구매이력 삭제 */
        DELETE
        FROM
            tb_order
        WHERE
            o_id = #{memberId}
    </delete>
    
    <delete id="removeLoginHistoryById" parameterType="String">
        /* 로그인 이력 삭제 */
        DELETE
        FROM
            tb_login
        WHERE
            login_id = #{memberId}
    </delete>
    
    <delete id="removeMemberById" parameterType="String">
        /* 회원 삭제 */
        DELETE
        FROM
            tb_member
        WHERE
            m_id = #{memberId}
    </delete>
  • [코드]MemberMapper.java
    // 구매자별 구매이력 삭제
    public int removeOrderById(String memberId);
    
    // 로그인 이력 삭제
    public int removeLoginHistoryById(String memberId);
    
    // 회원 삭제
    public int  removeMemberById(String memberId);

7️⃣
Goods

상품정보를 담는 dto를 만든다.

➡️ Goods dto에 member 정보를 가져오고 싶다.

  • 상품에서 바라봤을 때 상품과 회원과는 1:1 관계이다.
  • 회원에서 바라봤을 때 회원과 상품은 1:N 관계이다.

🗣 private Member sellerInfo(Member정보 1:1관계시)를 추가한다.

  • [코드]Goods.java
    package ksmart.mybatis.dto;
    
    import lombok.Data;
    
    @Data
    public class Goods {
    
        private String goodsCode;
        private String goodsName;
        private int goodsPrice;
        private String goodsSellerId;
        private String goodsRegDate;
    
        // 판매자의 정보 (1:1)
        // Member dto
        private Member sellerInfo;
    
    }

8️⃣
GoodsMapper

상품과 관련된 Mapper를 새롭게 생성한다.

➡️ 1) 판매자가 등록한 상품코드별 주문이력 삭제 쿼리

2) 판매자별 상품 삭제 쿼리

🗣 DELETE시 원래는 컬럼을 명시를 안하는데, 조인을 하면 별칭을 써줘야한다. 별칭을 썼을 때는 컬럼에 별칭을 넣어줘야 한다.

  • [코드]GoodsMapper.xml
    <delete id="removeOrederByGCode" parameterType="String">
      /* 판매자가 등록한 상품코드별 주문이력 삭제 */
      DELETE
          o
      FROM
          tb_goods AS g
          INNER JOIN
          tb_order AS o
          ON
          g.g_code = o.o_g_code
      WHERE
          g.g_seller_id = #{sellerId}
    </delete>
    
    <delete id="removeGoodsById" parameterType="String">
      /* 판매자별 상품 삭제 */
      DELETE
      FROM
          tb_goods
      WHERE
          g_seller_id = #{sellerId};
    </delete>
  • [코드]GoodsMapper.java
    // 판매자가 등록한 상품코드별 주문이력 삭제
    public int removeOrederByGCode(String sellerId);
    
    // 판매자별 상품 삭제
    public int removeGoodsById(String sellerId);

9️⃣
Service

회원등급별로 탈퇴가 처리되는 로직을 작성한다.

➡️ 매개변수에 회원 커맨드 객체를 받아서 회원의 정보를 한번에 불러온다. (커맨트 객체 → dto에 set 되어있는 데이터)

➡️ 회원의 정보에서 아이디와 등급을 가져와서 해당 권한이 일치하면 mapper의 메소드를 아이디 값을 통해서 호출한다.

  • [코드]Service.java
    /**
     * 회원등급별 탈퇴 처리
     * @param memberInfo
     */
    public void removeMember(Member memberInfo) {
    
        if(memberInfo != null) {
            String memberId = memberInfo.getMemberId();
            int memberLevel = memberInfo.getMemberLevel();
    
            switch (memberLevel) {
    						// 판매자일 경우 삭제처리
                case 2 -> {
                    goodsMapper.removeOrederByGCode(memberId);
                    goodsMapper.removeGoodsById(memberId);
                }
    						// 구매자일 경우 삭제처리
                case 3 -> {
                    memberMapper.removeOrderById(memberId);
                }
            }
    				// 공통 삭제처리 부분
            memberMapper.removeLoginHistoryById(memberId);
            memberMapper.removeMemberById(memberId);
        }
    
    }

🔟
Controller

➡️ 회원의 정보를 삭제하는 service 메서드를 호출한다. (비밀번호 일치가 완료 됐을 경우에)

➡️ 호출 후 리턴으로 회원 목록화면이 리다이렉트된다.

  • [코드]Controller.java
    // 비밀번호 일치 회원 탈퇴
    if(isValid) {
        Member memberInfo = (Member) isValidMap.get("memberInfo");
    
    		// ========= 기존 컨트롤러에서 추가되는 부분 ========
        // 회원 탈퇴 메서드 호출
        memberService.removeMember(memberInfo);
    
        return "redirect:/member/memberList";
    }


tag : #Spring #Mybatis #Controller #Service #Mapper #SQL #ERD #커맨드객체


Uploaded by N2T

728x90
320x100
Comments