멋쟁이v의 개발일지

[Spring] Mybatis 회원관리(1) - 회원조회, 회원가입 본문

0년차/Spring

[Spring] Mybatis 회원관리(1) - 회원조회, 회원가입

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


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

회원 목록 조회
1️⃣
프로세스에서 데이터를 전송하기 위한 dto 객체를 생성한다.
  • [코드]Member.java
    package ex.mybatis.dto;
    
    public class Member {
    
        private String memberId;
        private String memberPw;
        private String memberName;
        private int memberLevel;
        private String memberEmail;
        private String memberAddr;
        private String memberRegDate;
    
        public String getMemberId() {
            return memberId;
        }
    
        public void setMemberId(String memberId) {
            this.memberId = memberId;
        }
    
        public String getMemberPw() {
            return memberPw;
        }
    
        public void setMemberPw(String memberPw) {
            this.memberPw = memberPw;
        }
    
        public String getMemberName() {
            return memberName;
        }
    
        public void setMemberName(String memberName) {
            this.memberName = memberName;
        }
    
        public int getMemberLevel() {
            return memberLevel;
        }
    
        public void setMemberLevel(int memberLevel) {
            this.memberLevel = memberLevel;
        }
    
        public String getMemberEmail() {
            return memberEmail;
        }
    
        public void setMemberEmail(String memberEmail) {
            this.memberEmail = memberEmail;
        }
    
        public String getMemberAddr() {
            return memberAddr;
        }
    
        public void setMemberAddr(String memberAddr) {
            this.memberAddr = memberAddr;
        }
    
        public String getMemberRegDate() {
            return memberRegDate;
        }
    
        public void setMemberRegDate(String memberRegDate) {
            this.memberRegDate = memberRegDate;
        }
    
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Member{");
            sb.append("memberId='").append(memberId).append('\'');
            sb.append(", memberPw='").append(memberPw).append('\'');
            sb.append(", memberName='").append(memberName).append('\'');
            sb.append(", memberLevel='").append(memberLevel).append('\'');
            sb.append(", memberEmail='").append(memberEmail).append('\'');
            sb.append(", memberAddr='").append(memberAddr).append('\'');
            sb.append(", memberRegDate='").append(memberRegDate).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }

2️⃣
DB의 data에 접근하기 위한 객체(dao)를 생성한다.

➡️ mapper.java → 인터페이스 역할

➡️ resource에 mapper.xml에 SQL문 작성

<mapper namespace=””> : 인터페이스의 위치(경로)를 설정한다.

<select id=”” resultType=””> : id(메소드명과 일치), resultType(리턴타입과 일치)

  • [코드]MemberMapper.java
    package ex.mybatis.mapper;
    
    import ex.mybatis.dto.Member;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    @Mapper
    public interface MemberMapper {
    
        // 회원 목록 조회
        public List<Member> getMemberList();
    
    }

  • [코드]MemberMapper.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="ex.mybatis.mapper.MemberMapper">
    
        <select id="getMemberList" resultType="Member">
            <!--회원목록 조회-->
            SELECT
            m_id AS memberId,
            m_pw AS memberPw,
            m_name AS memberName,
            m_level AS memberLevel,
            m_email AS memberEmail,
            m_addr AS memberAddr,
            m_reg_date AS memberRegDate
            FROM
            tb_member AS m;
        </select>
    
    </mapper>

3️⃣
비즈니스 로직(객체, 메소드 생성 등)을 처리하기 위한 서비스를 생성한다.

➡️ @Transcational

  • 트랜잭션 처리.
  • 동시 접근하는 여러 프로그램 간 격리를 제공.
  • 특정 실행단위에서 오류 발생시 전체 실행 내용을 rollback해주는 기능이다.
  • [코드]MemberService.java
    package ex.mybatis.service;
    
    import ex.mybatis.dto.Member;
    import ex.mybatis.mapper.MemberMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    /**
     * @Transactional : 기본적인 트랜잭션 처리 A(원자성) C(일관성) I(고립성) D(영속성)
     * SQLException dao 관련된 작업 rollback
     * 정상적으로 수행이 되었다면 commit
     */
    @Service
    @Transactional // 클래스 선언 위치 : 클래스 내부에 한번에 적용하고 싶을 떄
    public class MemberService {
    
        private static final Logger log = LoggerFactory.getLogger(MemberService.class);
    
        public final MemberMapper memberMapper;
    
        public MemberService(MemberMapper memberMapper) {
            this.memberMapper = memberMapper;
        }
    
        // @Transactional // 메소드 선언 위치 : 세세하게 메소드마다 적용하고 싶을 때
        public List<Member> getMemberList() {
    
            List<Member> memberList = memberMapper.getMemberList();
    
            log.info("memberList: {}", memberList);
    
            return memberList;
        }
    
    }

4️⃣
사용자 요청과 응답을 처리하고, 주소의 분기와 맵핑을 담당하는 컨트롤러를 생성한다.

➡️ @RequestMapping : 공통적인 url을 class에 @RequestMapping으로 설정해준다.

  • [코드]MemberController.java
    package ex.mybatis.controller;
    
    import ex.mybatis.dto.Member;
    import ex.mybatis.service.MemberService;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.List;
    
    @Controller // 주소 분기 및 맵핑
    @RequestMapping("/member") // 공통적인 url
    public class MemberController {
    
        public final MemberService memberService;
    
        public MemberController(MemberService memberService) {
            this.memberService = memberService;
        }
    
    		/**
         * 회원목록 조회
         * @param model
         * @return
         */
        @GetMapping("/memberList") // /member/memberList
        public String getMemberList(Model model) {
    
            List<Member> memberList = memberService.getMemberList();
            model.addAttribute("title", "회원목록");
            model.addAttribute("memberList", memberList);
    
            return "member/memberList";
        }
    
    }

5️⃣
html에 모델에 담긴 데이터를 타임리프로 화면에 보여준다.
  • [코드]memberList.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    
    
    <table>
        <thead>
        <tr>
            <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>
        </tr>
        </tbody>
    </table>
    </html>

💡
현재 DB에 member 테이블에 level은 숫자로 되어 있다.

member_level 테이블을 이용해서 숫자로 출력되는 권한을 관리자, 판매자, 구매자로 출력해보자.

6️⃣
Member dto에 memberLevelName 변수를 추가한다.

그 다음 MemberService.java에서 기존에 선언했던 getMemberList() 메소드안에 권한이름 배열을 하나 만들고,

memberList를 순회해서 level 숫자를 가져와서 숫자에 맞는 권한이름을 memberLevelName에 세팅한다.

// dado 호출
List<Member> memberList = memberMapper.getMemberList();
String [] memberLevelArr = {"관리자", "판매자", "구매자", "테스트"};

if(memberList != null) {
	memberList.froEach(member -> {
		int memberLevel = member.getMemberLevel();
		String memberLevelName = memberLevelArr[memberLevel-1];
		member.setMemberLevelName(memberLevelName);
	});
}

02. mybatis를 활용한 회원관리 - 회원가입

회원 가입(회원 등급 조회, 아이디 체크)
1️⃣
데이터를 전송하기 위한 dto를 생성한다.

➡️ lombok 라이브러리 추가

  • dto 생성시 @Data 를 해주면 getter, setter, tostring이 자동으로 생성된다.
  • [코드]MemberLevel.java (lombok 사용)
    package ex.mybatis.dto;
    
    import lombok.Data;
    
    /**
     * @Data : get, set, tostring 자동생성
     * @Setter : set만 생성
     */
    @Data // lombok
    public class MemberLevel {
    
        private int levelNum;
        private String levelName;
        private String levalRegDate;
    
    }

2️⃣
Mapper.xml에 SQL쿼리문을 추가하고, Mapper 인터페이스에 메소드를 추가한다. (xml에서 쿼리 id와 인터페이스에서 메소드명 일치)

➡️ 회원 등급 조회, 아이디 체크 → select 쿼리

➡️ 회원 가입 → insert 쿼리

  • [코드]Mapper.xml
    <select id="getMemberLevel" resultType="MemberLevel">
        /* 회원등급 조회 */
        SELECT
            l.level_num AS levelNum,
            l.level_name AS levelName
        FROM
            tb_member_level AS l
    </select>
    
    <select id="idCheck" resultType="boolean" parameterType="String">
        /* 아이디 여부 */
        SELECT
            IF(COUNT(m.m_id), 1, 0)
        FROM
            tb_member AS m
        WHERE
            m.m_id = #{memberId}
    </select>
    
    <insert id="addMember" parameterType="Member">
        /* 회원가입 */
        INSERT INTO tb_member
            (m_id, m_pw, m_name, m_level, m_email, m_addr, m_reg_date)
        VALUES
            (#{memberId}, #{memberPw}, #{memberName}, #{memberLevel}, #{memberEmail}, #{memberAddr}, CURDATE())
    </insert>
  • [코드]Mapper.java
    // 회원 등급 조회
    public List<MemberLevel> getMemberLevel();
    
    // 아이디 체크
    public boolean idCheck(String memberId);
    
    // 회원가입
    public int addMember(Member member);

3️⃣
서비스쪽에서 dao를 호출하는 메소드를 추가한다.
  • [코드]MemberService.java
    public List<MemberLevel> getMemberLevel() {
    
    	// dao 호출
      List<MemberLevel> memberLevelList = memberMapper.getMemberLevel();
    
      return memberLevelList;
    }
    
    public void addMember(Member member) {
    
    		// dao 호출
        memberMapper.addMember(member);
    }

4️⃣
컨트롤러에서 회원 가입 화면으로 주소를 설정해주고, 데이터를 model에 넘겨준다.

➡️ lombok : Slf4j, AllArgsConstructor

➡️ @PostMapping : POST방식으로 요청을 처리하겠다.

비동기통신을 하기 위해서는 클라이언트에서 서버로 요청 메세지를 보낼 때, 본문에 데이터를 담아서 보내야 하고, 서버에서 클라이언트로 응답을 보낼 때에도 본문에 데이터를 담아서 보내야 한다. 즉, 요청본문(requestBody), 응답본문(responseBody)

➡️ @RequestBody : json 기반의 HTTP Body를 자바 객체로 변환

➡️ @ResponseBody : 자바 객체를 json 기반의 HTTP Body로 변환

➡️ @RestController : 자동으로 @ResponseBody가 붙게 된다.

  • [코드]MemberController.java (lombok 사용)
    package ksmart.mybatis.controller;
    
    import jakarta.annotation.PostConstruct;
    import ksmart.mybatis.dto.Member;
    import ksmart.mybatis.dto.MemberLevel;
    import ksmart.mybatis.mapper.MemberMapper;
    import ksmart.mybatis.service.MemberService;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @Controller //사용자 요청과 응답을 처리
    @Slf4j // 롬복
    @RequestMapping("/member")
    @AllArgsConstructor // 생성자 메소드 생성안해도 된다.
    public class MemberController {
    
        private final MemberService memberService;
        private final MemberMapper memberMapper;
    
        @PostConstruct
        public void memberControllerInit() {
            System.out.println("memberController 생성");
        }
    
        /**
         * 회원목록 조회
         * @param model
         * @return
         */
        @GetMapping("/memberList")
        public String getMemberList(Model model) {
    
            List<Member> memberList = memberService.getMemberList();
            model.addAttribute("title", "회원목록");
            model.addAttribute("memberList", memberList);
    
            return "member/memberList";
        }
    
        /**
         * 회원가입 화면(폼)
         * @param model
         * @return
         */
        @GetMapping("/addMember")
        public String addMemeber(Model model) {
    
            List<MemberLevel> memberLevelList = memberService.getMemberLevelList();
            log.info("회원등급조회 : {}", memberLevelList);
    
            model.addAttribute("title", "회원가입");
            model.addAttribute("memberLevelList", memberLevelList);
    
            return "member/addMember";
        }
    
        /**
         * 아이디 중복 체크
         * @param memberId
         * @return
         */
        @PostMapping("/idCheck")
        @ResponseBody
        public boolean idCheck(@RequestParam(value = "memberId") String memberId) {
    
            log.info("id 중복체크 : {}", memberId);
            boolean result = memberMapper.idCheck(memberId);
            log.info("id 중복체크 결과값:{}", result);
    
            return result;
        }
    
        @PostMapping("/addMember")
        public String addMember(Member member) {
    
            log.info("회원가입시 입력정보 : {}", member);
    
            memberService.addMember(member);
    
            // response.sendRedirect("/member/memberList");
            // spring framework mvc에서는 controller의 리턴값에 redirect : 키워드로 작성
            // redirect: 키워드를 작성할 경우 그 다음의 문자열은 html파일 논리 경로가 아닌 주소를 의미
            return "redirect:/member/memberList";
        }
    
    }

5️⃣
model로 넘겨준 데이터와 반환값을 이용해서 처리한다.

➡️ get, post 요청 / 유효성 검사, ajax 비동기 통신

  • [코드]addMember.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;
        }
    
        #idCheck {
          width: 99%;
          height: 25px;
        }
    
        #addMemberBtn, #resetBtn {
          width: 49%;
          height: 25px;
        }
      </style>
    </head>
    
    <th:block layout:fragment="customJs">
      <script th:src="@{/js/jquery-3.7.0.js}"></script>
      <script>
    
    		// 제이쿼리 사용. 클릭 이벤트
        $('#idCheck').click(function () {
          const $memberId = $('#memberId');
          let memberId = $memberId.val();
    
          // 유효성 검사
          let valid = validCheck($memberId);
          if(!valid) return false;
    
          // ajax 호출 
          const request = $.ajax({
            url: '/member/idCheck',
            method: 'POST',
            data: { 'memberId' : memberId },
            dataType: 'text' // json은 해당 타입을 유지해준다.
          });
    
    			// http 요청 성공시 (boolean 반환값)
          request.done(function( response ) {
            // json이면 if(response) { }
            if(response == 'true') {
              alert('이미 등록된 아이디입니다.');
              $memberId.val('');
              $memberId.focus();
              return false;
            }else{
              $('#addMemberForm input').prop('disabled', false);
              $('#addMemberForm select').prop('disabled', false);
              $('#addMemberForm button').prop('disabled', false);
            }
    
            // dataType이 text로 넘어와서 String으로 찍힌다.
            // console.log(typeof response);
            console.log(response);
          });
    
    			// http 요청 실패시
          request.fail(function( jqXHR, textStatus ) {
            alert( "Request failed: " + textStatus );
          });
    
        });
    
        // 유효성 검사 함수
        const validCheck = element => {
          let data = $(element).val();
          let eleId = $(element).attr('id');
          if(typeof data == 'undefined' || data == null || data == '') {
            let cate = $(`label[for="${eleId}"]`).text();
            alert(`${cate} 필수 입력항목입니다.`);
            $(element).focus();
            return false;
          }
          return true;
        }
    
        // 회원아이디 변경 시 이벤트 처리
        $('#memberId').change(function () {
          $('#addMemberForm input').not('#memberId').val('');
          $('#addMemberForm input').not('#memberId').prop('disabled', true);
          $('#addMemberForm select').prop('disabled', true);
          $('#addMemberForm button').not('#idCheck').prop('disabled', true);
        });
    
        // 회원가입 버튼 시 이벤트(유효성검사)
        $('#addMemberBtn').click(function () {
          const validateEle = $('#addMemberForm input').not('#memberId');
          let isSubmit = false;
          validateEle.each((idx, item) => {
            isSubmit = validCheck(item);
            return isSubmit;
          });
          if(isSubmit) {
            console.log('전송');
            $('#addMemberForm').submit();
          }
        });
    
        // 입력 취소 버튼 클릭시 이벤트 처리
        $('#resetBtn').click(function (e) {
          // 태그가 가지고 있는 기본 이벤트 동작 방지
          e.preventDefault();
          $('#addMemberForm input').not('#memberId').val('');
        });
    
    
      </script>
    </th:block>
    
    <th:block layout:fragment="customContents">
      <form id="addMemberForm" th:action="@{/member/addMember}" method="post">
        <table>
          <tbody>
          <tr>
            <td>
              <label for="memberId">회원아이디</label>
            </td>
            <td>
              <input type="text" id="memberId" name="memberId" placeholder="회원의 아이디를 입력해주세요."/>
            </td>
            <td>
              <button type="button" id="idCheck">아이디 중복체크</button>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberPw">회원비밀번호</label>
            </td>
            <td colspan="2">
              <input type="text" id="memberPw" name="memberPw" placeholder="회원의 비밀번호를 입력해주세요." disabled="disabled"/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberName">회원이름</label>
            </td>
            <td colspan="2">
              <input type="text" id="memberName" name="memberName" placeholder="회원의 이름을 입력해주세요." disabled="disabled"/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberLevel">회원등급</label>
            </td>
            <td colspan="2">
              <select id="memberLevel" name="memberLevel" disabled="disabled">
                <th:block th:unless="${#lists.isEmpty(memberLevelList)}"
                          th:each="l : ${memberLevelList}">
                  <option th:value="${l.levelNum}">[[${l.levelName}]]</option>
                </th:block>
                <th:block th:if="${#lists.isEmpty(memberLevelList)}">
                  <option th:value="">등록된 회원의 등급이 없습니다.</option>
                </th:block>
              </select>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberEmail">회원이메일</label>
            </td>
            <td colspan="2">
              <input type="text" id="memberEmail" name="memberEmail" placeholder="회원의 이메일을 입력해주세요." disabled="disabled"/>
            </td>
          </tr>
          <tr>
            <td>
              <label for="memberAddr">회원주소</label>
            </td>
            <td colspan="2">
              <input type="text" id="memberAddr" name="memberAddr" placeholder="회원의 주소를 입력해주세요." disabled="disabled"/>
            </td>
          </tr>
          <tr>
            <td colspan="3">
              <button type="button" id="addMemberBtn" disabled="disabled">회원가입</button>
              <button type="reset" id="resetBtn" disabled="disabled">입력취소</button>
            </td>
          </tr>
          </tbody>
        </table>
      </form>
    </th:block>
    </html>


tag : #Spring #Mybatis #Controller #Service #Mapper #SQL #Lombok


Uploaded by N2T

728x90
320x100
Comments