Spring Projcect/학습 관리 시스템 & 백오피스 구축

Chapter 10. 회원 목록

계란💕 2022. 8. 20. 16:11

10.1 회원 목록 구현

  Ex) 관리자 페이지에서 회원 목록 구현하기

    - list 파일 - 리스트 만들고 스타일을 적용한다.

    - style: border-collapse: collapse (테이블 간격 없애기)

    - 예를 들어, 테이블 안에 아래와 같은 방식으로 회원 데이터를 넣을 예정이다. (jpa로 가져오고 MyBatis를 이용할 것이다.)

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>

    <style>
        .list table{
            border-collapse: collapse;
        }
        .list table th, .list table td{
            border: solid 1px #000;
        }
    </style>
</head>
<body>
   <h1>관리자 회원 관리</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-body-menu" ></div>-->
   <div>
       <a href="/admin/main.do">관리자 메인</a>
       |
       <a href="/admin/member/list.do">회원 관리</a>
       |
       <a href="#">카테고리 관리</a>
       |
       <a href="#">강의 관리</a>
       |
       <a href="/member/logout">로그 아웃</a>
       <br>
   </div>

    <div class="list">
        <table>
            <thead>
                <tr>
                    <th>No</th>
                    <th>아이디(이메일)</th>
                    <th>이름</th>
                    <th>연락처</th>
                    <th>이메일 인증 여부</th>
                    <th>가입일</th>
                    <th>관리자 여부</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>1</td>
                    <td>goran@kakao.com</td>
                    <td>ran</td>
                    <td>010-0000-9999</td>
                    <td>y</td>
                    <td>2022.08.20</td>
                    <td>Y</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

    Note) 실행 결과

 

 

  Ex) MemberService에 메서드 추가

<hide/>
/**
 * 회원 목록을 가져온다. - 관리자에서만 사용 가능
 */
List<Member> list();

 

    - Impl 클래스 => list()의 반환형 => memberReposite.findAll() ....회원 정보를 모두 가져온다.

<hide/>
@Override
public List<Member> list() {
    return memberRepository.findAll();
}

 

    - 컨트롤러에서 가져온다.

       -> final은 필수적으로 초기화를 해줘야하는데 생성자를 통해 초기화해줄 수 있다.(@RequiredArgs)

      -> Model이란?

 

 

Model Interface - Spring

  • Model객체는 컨트롤러에서 데이터를 생성해 이를 JSP, 즉 View에 전달하는 역할을 한다.
  • HashMap형태를 가지며, Key - Value 값을 저장한다. (Servlet의 Request.setAttribute()와 비슷)
  • Key는 String형태이다.

 

      - AdminMemberCtrller

<hide/>
package com.zerobase.fastlms.admin;
import com.zerobase.fastlms.member.entity.Member;
import com.zerobase.fastlms.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class AdminMemberController {
    private final MemberService memberService;

    @GetMapping("/admin/member/list.do")
    public String list(Model model){

        List<Member> members = memberService.list();
        model.addAttribute("list", members);
        return "admin/member/list";
    }
}

  Note) 실행 결과 

    - Json형식으로 데이터가 출력되는데 이를 표에 들어간 데이터 형태로 바꾸로 출력하도록 작성할 계획이다.

    - ThymeLeaf - 뷰 엔진을 사용한다.

 

  Ex

    - ThymeLeaf 문법을 이용한다.

    - th:each는 자바의 for-each문과 비슷하다.

    - th:if는 if문이 참일 때만 동작한다.

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>

    <style>
        .list table{
            border-collapse: collapse;
        }
        .list table th, .list table td{
            border: solid 1px #000;
        }
    </style>
</head>
<body>
   <h1>관리자 회원 관리</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-body-menu" ></div>-->
   <div>
       <a href="/admin/main.do">관리자 메인</a>
       |
       <a href="/admin/member/list.do">회원 관리</a>
       |
       <a href="#">카테고리 관리</a>
       |
       <a href="#">강의 관리</a>
       |
       <a href="/member/logout">로그 아웃</a>
       <br>
   </div>
    <div class="list">
        <table>
            <thead>
                <tr>
                    <th>No</th>
                    <th>아이디(이메일)</th>
                    <th>이름</th>
                    <th>연락처</th>
                    <th>이메일 인증 여부</th>
                    <th>가입일</th>
                    <th>관리자 여부</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="x : ${list}">

                    <td>1</td>
                    <td th:text="${x.getUserId()}"></td>
                    <td th:text="${x.getUserName()}"></td>
                    <td th:text="${x.getPhone()}"></td>

                    <td th:if="${x.emailAuthYn}">Y</td>
                    <td th:if="${x.emailAuthYn eq false}">N</td>

                    <td th:text="${x.getRegDt()}"></td>

                    <td th:if="${x.isAdminYn}">Y</td>
                    <td th:if="${x.isAdminYn eq false}">N</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

  Note) 실행 결과

 

 

10.2  MyBatis 설정 및 쿼리 실행

 

전자정부 프레임워크

  • 전자정부 표준프레임워크는 대한민국의 공공부문 정보화사업 시 플랫폼별 표준화된 개별 프레임워크를 말한다. 
  • Java는 사설 표준으로 업체가 자체적으로 프레임워크를 개발하고 적용하다보니 각 프레임워크의 구조와 수준 차이가 있어서 여러 가지 문제점이 있다. 
  • 최근에는 2008년 스프링 프레임워크 등 오픈소스를 기반으로 웹 사이트 개발 시 필요한 여러 가지 기능을 이미 구현해 놓았으며 최근에는 모바일 개발을 위한 프레임워크도 출시되었다.
  • 데이터베이스 핸들링하는 부분은 MyBatis가 표준으로 되어있다. 가장 많이 사용되는 라이브러리이다.

 

  Ex) MyBatis 세팅

<hide/>
<?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="com.zerobase.fastlms.admin.mapper.MemberMapper">
    <select id="selectList" resultType="com.zerobase.fastlms.admin.dto.MemberDto">
        select *
        from member;
    </select>
</mapper>

    - pom.xml파일에 MyBatis를 추가한다.

    - yml 파일에  다음 내용을 추가한다.

      -> .xml 위치 설정

      -> 데이터베이스는 언더바 표기법이 기본으로 되어 있는데 이를 카멜 표기법으로 바꾼다. 플러그인 설치해서 변경 가능

      -> StdOutImpl: 화면에 바로 보여준다.

    - https://mybatis.org/mybatis-3/ko/getting-started.html 사이트에 접속해서 "매핑된 SQL  구문 살펴보기"에 있는 코드를 가져온다.

      -> MemberMapper.xml 파일을 만들고 데이터를 저장한다.

      -> 샘플 코드에 namespaceMemberMapper인터페이스의 패키지 주소로 바꾼다.

      -> 그러면 namespace에 있는 내용(MemberMapper 인터페이스)이 xml 파일과 매칭된다.

    - xml 파일의 resutlType을 위와 같이 MemberDto가 있는 주소로 바꾸면 자동으로 MemberDto에 값이 매핑된다.

 

 

    - MemberDto 클래스 만들기 - xml파일과 매핑된다. (selectList()의 반환형 => Dto)

<hide/>
package com.zerobase.fastlms.admin;
import java.time.LocalDateTime;
@Data
public class MemberDto {
    String userId;
    String emailAuthKey;
    String  password;
    String phone;
    String userName;
    String resetPasswordKey;
    
    LocalDateTime regDt;
    LocalDateTime emailAuthDt;
    LocalDateTime  resetPasswordLimitDt;
    
    boolean  emailAuthYn;
    boolean adminYn;
}

      -> 데이터베이스에 기존에 있던 데이터를 그대로 가져와서 변수명은 카멜 표기법으로 바꾼다.

      -> Entity클래스인  Member는 JPA를 통해 생으로 데이터를 가져오는데 반해 MemberDto는 한 번 가공해서 다듬어진 데이터

    -> 카멜 표기법으로 바꾸는 플러그인: String Manipulation

 

 

    - MemberMapper 

<hide/>
package com.zerobase.fastlms.admin.mapper;
import com.zerobase.fastlms.admin.dto.MemberDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper // 데이터베이스에 대해 쿼리를 실행 가능
public interface MemberMapper {
    List<MemberDto> selectList(MemberDto memberDto);
}

      -> @Mapper를 이용하면 데이터베이스를 이용해서 쿼리를 실행할 수 있다. 

      -> 리스트를 가져오는 메서드 selectList() 만든다.

      -> xml파일에 SELECT로 member의 값을 모두 가져오는 쿼리 실행문이 있기 때문에 이를 MemberMapper 인터페이스에서 selectList() 를 구현해서 사용가능하다.

 

 

    - Impl클래스 - list() 수정한다. (클래스 안에 memberMapper를 전역 변수로 선언)

<hide/>
@Override
public List<MemberDto> list() {
    MemberDto parameter = new MemberDto();
    List<MemberDto> list = memberMapper.selectList(parameter);
    return list;
}


    - 그러고나서 AdminMemberCtrller 클래스도 다음과 같이 수정

<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model){
    List<MemberDto> members = memberService.list();
    model.addAttribute("list", members);
    return "admin/member/list";
}

  Note) 실행 결과

    - 아까는 JPA와 findAll() 메서드를 이용해서 다음과 같이 데이터 목록을 가져왔고 지금은 쿼리를 이용해서 데이터를 가져온 것이다.

    - JPA는 객체를 매핑하면  자바가 자동으로 쿼리를 만들지만 MyBatis는 쿼리를 직접 작성 가능하다.

    - xml 파일의 중간에 MySql을 이용하듯이 쿼리문을 넣을 수 있다.

 

 

 

10.3 회원 검색 기능 구현

  Ex) 검색 기능 구현

 

    - 관리자 페이지에서 검색 기능 추가한다.

      -> <input>: 값을 입력하려면 쓰는 태그

      -> <button>: 버튼 추가, 누르면 뭔가 실행된다. 변수 이름(name)은 있어도 되고 없어도 된다.

      -> <select>: 옵션 리스트 중 하나만 고를 수 있는 기능이다.

      -> <select>를 <form>으로 감싸야만 검색 버튼이 동작한다. form method="get"으로 지정해줘야 url에 값이 뜨는 걸 볼수 있다. 즉, 검색한 주소로 이동할 수 있도록 한다.

      -> 주소 검색은 post가 아니라 get 방식으로 url 호출한다.

 

    - 회원관리 창에서 사용자가 빈 칸에 데이터를 넣는다. 이 값을 어떻게 가져올까? => "param"이라는 객체 이용

 

    - 검색 누르고 난 다음 화면, 상단 주소가 바뀐다. (근ㄷ)

 

    cf) 다음을 출력하면?

<div th:text="${param}"></div>

    - 결과

      -> 즉, param을 이용해서 사용자가 검색한 값을 가지고 내 서버로 가져올 수 있다.

 

 

 

  Ex) param 이용해서 객체 마다 유저 정보 나타내기

    - ThymeLeaf 문자열 비교

    - url로 넘어온 searchType과 searchValue에 대한 처리가 필요하다. 

    - type="search"로 해줘야한다.

<hide/>
<!doctype html>
<html lang="ko"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>관리자 화면</title>

    <style>
        .list table{
            border-collapse: collapse;
        }
        .list table th, .list table td{
            border: solid 1px #000;
        }
    </style>
</head>
<body>
   <h1>관리자 회원 관리</h1>
<!--   <div th:replace="/fragments/layout.html :: fragment-body-menu" ></div>-->
   <div>
       <a href="/admin/main.do">관리자 메인</a>
       |
       <a href="/admin/member/list.do">회원 관리</a>
       |
       <a href="#">카테고리 관리</a>
       |
       <a href="#">강의 관리</a>
       |
       <a href="/member/logout">로그 아웃</a>
       <br>
       <hr/>
   </div>

    <div class="list">

        <div>
            <form method="get">
                <select name="searchType">
                    <option value="all">전체</option>
                    <option th:selected="${#strings.equals(param.searchType, 'userId')}" value="userId">아이디</option>
                    <option th:selected="${#strings.equals(param.searchType, 'userName')}" value="userName">이름</option>
                    <option th:selected="${#strings.equals(param.searchType, 'phone')}" value="phone">연락처</option>
                </select>
                <input th:value="${param.searchValue}" type="search" name="searchValue" placeholder="검색어 입력"/>
                <button type="submit">검색</button>
            </form>

        </div>

        <table>
            <thead>
                <tr>
                    <th>No</th>
                    <th>아이디(이메일)</th>
                    <th>이름</th>
                    <th>연락처</th>
                    <th>이메일 인증 여부</th>
                    <th>가입일</th>
                    <th>관리자 여부</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="x : ${list}">

                    <td>1</td>
                    <td th:text="${x.getUserId()}"></td>
                    <td th:text="${x.getUserName()}"></td>
                    <td th:text="${x.getPhone()}"></td>

                    <td th:if="${x.emailAuthYn}">Y</td>
                    <td th:if="${x.emailAuthYn eq false}">N</td>

                    <td th:text="${x.getRegDt()}"></td>

                    <td th:if="${x.isAdminYn}">Y</td>
                    <td th:if="${x.isAdminYn eq false}">N</td>

                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>

     

 

    - 와이파이에서 SearchWifi 만든 것처럼 여기서도 MemberParam을 만든다.

      -> 관리자 컨트롤러 수정

      -> impl 메서드 수정한다.

      -> 그 외에도 list와 관련한 문제들에 대해 모두 MemberParam으로 바꿔준다.

<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class MemberParam {
    String searchType;
    String searchValue;
}
List<MemberDto> list(MemberParam memberParam);

 

 

      cf) https://mybatis.org/mybatis-3/ko/dynamic-sql

        - choose, when, otherwise - 참고용 코드

<hide/>
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

 

 

    - xml 파일의 쿼리 수정한다. MemberMapper에서 가져온 변수는 #{}로 감싸준다.

     -> namsspace로 지정된 MemberMapper에 대해 그 안에 있는 변수 searchType, searchValue를 가져와서 쓸 수 있다.

      -> 마이바티스로 동적 쿼리를 만든다. 홈페이지의 코드를 가져온다.

      -> otherwise는 옵션 전체/아이디/이름/연락처 중에서 전체를 선택한 경우이다.

      -> when, otherwise 구문 안은 and로 시작한다.

<hide/>
<?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="com.zerobase.fastlms.admin.mapper.MemberMapper">
    <select id="selectList"
            parameterType="com.zerobase.fastlms.admin.model.MemberParam"
            resultType="com.zerobase.fastlms.admin.dto.MemberDto">
        SELECT *
        FROM member
        WHERE 1 = 1

            <if test="searchValue != null and searchType != null">
                <choose>
                    <when test="searchType == 'userId'">
                        and user_id like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'userName'">
                        and user_name like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'phone'">
                        and phone like concat('%', #{searchValue}, '%')
                    </when>

                    <otherwise>
                        and( // 셋 중 하나 만족하면 된다.
                            user_id like concat('%', #{searchValue}, '%')
                            or
                            user_name like concat('%', #{searchValue}, '%')
                            or
                            phone like concat('%', #{searchValue}, '%')
                        )
                    </otherwise>
                </choose>
            </if>

    </select>
</mapper>

 

 

========================================오류 ========================================

    - 오류: url에 아래 처럼 나와야 하는데 searchType에 대한 부분이 나타나지 않았다.

http://localhost:8080/admin/member/list.do?searchType=userName&searchValue="입력값"

    - 원인: html 파일의 <input> 안에 type을"text"가 아닌 "search"을 선택해야 두 개의 매개 변수로 검색하고 그 다음 자동으로 url에 변수명과 리터럴이 들어간다.

      -> type을 search로 주면 검색창에 x버튼도 나온다.

<input th:value="${param.searchValue}" type="search" name="searchValue" placeholder="검색어 입력"/>

 

 

  Ex) 검색 form을 오른쪽으로 이동하려면?

    - list안에 <div  class="search-form">.. 이렇게 키워드를  붙여준다.

      -> <div  class="list"> 안에  <div  class="search-form">도 있고 테이블도 있다. padding을 주면 간격 조정된다.

<div class="search-form">

 

    - CSS 태그 안에 다음과 같이 추가한다.  

      -> 위치를 오른쪽으로하고 , padding: 다른 데이터와의 간격을 뜻한다. top => right => bottom => left 순서대로 값을 입력한다.

      -> top, bottom에만 여백을 지정한다. 

<hide/>
.search-form {
    padding: 5px 0 10px 0;
    text-align: right;
}

  Note) 실행 결과

 

 

 

10.4 회원 목록 페이징 처리

  

  Ex) 페이징 처리를 하려면?

    - 데이터의 총 개수가 필요하다. => totalCnt

    - AdiminMemCtrl에 Paging 내용 추가한다.

      -> pageIndex라는 값을 MemberParam에서 받아올 수 있도록 MemberParam에 새로운 변수를 추가한다. (pageIndex)

      -> pager()의 반환값을 모델에 담는다.

<hide/>
package com.zerobase.fastlms.admin;
import com.zerobase.fastlms.admin.dto.MemberDto;
import com.zerobase.fastlms.admin.model.MemberParam;
import com.zerobase.fastlms.member.entity.Member;
import com.zerobase.fastlms.member.service.MemberService;
import com.zerobase.fastlms.util.PageUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class AdminMemberController {
    private final MemberService memberService;

    @GetMapping("/admin/member/list.do")
    public String list(Model model, MemberParam parameter){

        List<MemberDto> members = memberService.list(parameter);
        model.addAttribute("list", members);

        int totalCount = 0;
        if(members != null){
            totalCount = members.size();
        }
        String queryString = "";
        PageUtil pageUtil = new PageUtil(totalCount, parameter.getPageIndex(), queryString);
        model.addAttribute("pager", pageUtil.pager());   // 데이터를 반환
        return "admin/member/list";
    }
}

 

  cf) th:utext

    - th.text는   출력할 문자열에 태그가 포함된 경우, 태그를 반영하지 않고 문자 그대로 출력한다.

    - 태그 형식의 코드를 삽입하려면 text대신 utext를 사용한다.

 

    - pager값을 list에서 사용한다. 테이블 밑에 아래와 같이 추가한다.

<div class="paper" th:utext="${pager}"></div>

 

  Note) 실행 결과

    - 어떤 페이지를 넣어도 다음과 같이 전체 데이터가 다 나온다.

    - 이를 10개씩만 나오도록 하려면 어떻게 해야할까?

 

 

  Ex) 페이지마다 리스트가 10개 씩만 나오도록 구현하기

    

     - xml 파일에 내용 추가, 항상 실행되도록 하기 위해 WHERE 1 = 1을 넣는다.

      -> 기존의  쿼리를 복사하고  '*'만 => 'COUNT(*)'로 바꾼다. 반환형은 long타입.

      -> 실행하려면 Mapper가 필요하다. MemberMapper 인터페이스에 추가한다.

<hide/>
<?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="com.zerobase.fastlms.admin.mapper.MemberMapper">
    <select id="selectListCount"
            parameterType="com.zerobase.fastlms.admin.model.MemberParam"
            resultType="long">

        SELECT COUNT(*)
        FROM member
        WHERE 1 = 1
        <if test="searchValue != null and searchType != null">
            <choose>
                <when test="searchType == 'userId'">
                    and user_id like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'userName'">
                    and user_name like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'phone'">
                    and phone like concat('%', #{searchValue}, '%')
                </when>

                <otherwise>
                    and
                    (
                    user_id like concat('%', #{searchValue}, '%')
                    or
                    user_name like concat('%', #{searchValue}, '%')
                    or
                    phone like concat('%', #{searchValue}, '%')
                    )
                </otherwise>
            </choose>
        </if>
    </select>
    <select id="selectList"
            parameterType="com.zerobase.fastlms.admin.model.MemberParam"
            resultType="com.zerobase.fastlms.admin.dto.MemberDto">
        SELECT *
        FROM member
        WHERE 1 = 1
            <if test="searchValue != null and searchType != null">
                <choose>
                    <when test="searchType == 'userId'">
                        and user_id like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'userName'">
                        and user_name like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'phone'">
                        and phone like concat('%', #{searchValue}, '%')
                    </when>

                    <otherwise>
                        and(
                            user_id like concat('%', #{searchValue}, '%')
                            or
                            user_name like concat('%', #{searchValue}, '%')
                            or
                            phone like concat('%', #{searchValue}, '%')
                        )
                    </otherwise>
                </choose>
            </if>
        LIMIT 0, 10
    </select>
</mapper>

 

    - Dto에 전체 데이커의 개수를 뜻하는 속성 totalCount 추가

<hide/>
package com.zerobase.fastlms.admin.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class MemberDto {

    String userId;
    String emailAuthKey;
    String password;
    String phone;
    String userName;
    String resetPasswordKey;

    LocalDateTime regDt;
    LocalDateTime emailAuthDt;
    LocalDateTime  resetPasswordLimitDt;

    boolean emailAuthYn;
    boolean adminYn;

    // 추가 컬럼
    long totalCount;
}

 

    - Impl 

      -> CollectionUtil.isEmpty(list) 

      -> 모든 데이터에 대해 totalCnt값을 하나씩 저장한다.

<hide?>
@Override
public List<MemberDto> list(MemberParam parameter) {
    long totalCount = memberMapper.selectListCount(parameter);
    List<MemberDto> list = memberMapper.selectList(parameter);
    if(!CollectionUtils.isEmpty(list)){
        for(MemberDto x : list){
            x.setTotalCount(totalCount);    // 전체 개수를 하나씩 다 넣어준다.
        }
    }
    return list;
}

 

    - 컨트롤러

      ->members에 값이 들어오면 사이즈 측정 가능

 

 

 ======================================== 오류 ========================================

    - 오류: 회원관리 창이 안 뜬다.

    - 원인: SQL 문법 오류 - xm 파일에 <select> 태그에 '>'를 하나 더 붙여서 틀렸다.

 

  Note) 실행 결과 - 정상

    - 이제 여기서 페이지마다 1 ~ 10, 11~20, .. 이런식으로 데이터가 나오도록 구현해야한다.

 

 

 

  Ex) 페이지 인덱스 구현

    - MemberParam에 시작 번호와 끝 번호를 구하는 메서드를 구하면 이를 MemberMapper에서 사용가능하다.

<hide/>
package com.zerobase.fastlms.admin.model;
import lombok.Data;
@Data
public class MemberParam {
    long pageIndex;
    long pageSize;
    String searchType;
    String searchValue;

    public long getPageStart() {
        init();
        return (pageIndex - 1) * pageSize;
    }

    public long getPageEnd() {
        init();
        return pageSize;
    }

    public void init() {
        if (pageIndex < 1) {
            pageIndex = 1;
        }
        if (pageSize < 10) {
            pageSize = 10;
        }
    }
}

 

    - MemberMapper

      -> MemberParam에서 변수와 메서드를 만들었기 때문에  MemberMapper에서 변수 이름을 사용 가능하다. 

      -> </select>와 </if> 사이에 LIMIT #{pageStart}, #{pageEnd} 

<hide/>
<?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="com.zerobase.fastlms.admin.mapper.MemberMapper">
    <select id="selectListCount"
            parameterType="com.zerobase.fastlms.admin.model.MemberParam"
            resultType="long">

        SELECT COUNT(*)
        FROM member
        WHERE 1 = 1
        <if test="searchValue != null and searchType != null">
            <choose>
                <when test="searchType == 'userId'">
                    and user_id like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'userName'">
                    and user_name like concat('%', #{searchValue}, '%')
                </when>
                <when test="searchType == 'phone'">
                    and phone like concat('%', #{searchValue}, '%')
                </when>

                <otherwise>
                    and
                    (
                    user_id like concat('%', #{searchValue}, '%')
                    or
                    user_name like concat('%', #{searchValue}, '%')
                    or
                    phone like concat('%', #{searchValue}, '%')
                    )
                </otherwise>
            </choose>
        </if>
    </select>
    <select id="selectList"
            parameterType="com.zerobase.fastlms.admin.model.MemberParam"
            resultType="com.zerobase.fastlms.admin.dto.MemberDto">
        SELECT *
        FROM member
        WHERE 1 = 1
            <if test="searchValue != null and searchType != null">
                <choose>
                    <when test="searchType == 'userId'">
                        and user_id like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'userName'">
                        and user_name like concat('%', #{searchValue}, '%')
                    </when>

                    <when test="searchType == 'phone'">
                        and phone like concat('%', #{searchValue}, '%')
                    </when>

                    <otherwise>
                        and(
                            user_id like concat('%', #{searchValue}, '%')
                            or
                            user_name like concat('%', #{searchValue}, '%')
                            or
                            phone like concat('%', #{searchValue}, '%')
                        )
                    </otherwise>
                </choose>
            </if>
        LIMIT #{pageStart} , #{pageEnd}
    </select>
</mapper>

  Note) 실행 결과 - 3 페이지 누른 화면

 

 

  Ex) 데이터 전체 개수 totalCount 구하는 방법


    - list파일에 변수 total-count를 추가한다. (MemberDto에 totalCount, userId, .. 변수가 있다.)

<p calss="total-count"><span th:text="${totalCount}"></span>개</p>

 

    - 컨트롤러

      -> 모델에 addAttribute()를 해줘야 list 파일에서 totalCount 조회 가능하다. 

<hide/>
@GetMapping("/admin/member/list.do")
public String list(Model model, MemberParam parameter){
    parameter.init();   // 유효한 값이 되도록 만든다.
    List<MemberDto> members = memberService.list(parameter);    // 값이 저장되면 토탈카운트가 존재한다.
    long totalCount = 0;
    if(members != null && members.size() > 0){
        totalCount = members.get(0).getTotalCount();
    }
    String queryString = "";
    PageUtil pageUtil = new PageUtil(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);

    model.addAttribute("list", members);
    model.addAttribute("totalCount", totalCount);   // 데이터를 반환
    model.addAttribute("pager", pageUtil.pager());   // 데이터를 반환
    return "admin/member/list";
}

  Note) 실행 결과

 

 

 

  Ex) 전체 개수 출력하기

    - 아래 코드를 추가한다.

      -> pointer-position: absolute (절대 위치 지정)

<p class="total-count">전체 <span th:text="${totalCount}"></span>개</p>
 .search-form .total-count{
            position-position: absolute;
            left: 0; top: 0;
            height: 20px;
            float: left;
        }

  Note) 실행 결과

 

  Ex) pager(페이지 번호 목록)를 가운데 배치하고 간격 늘리기

    - a.on은 특정 페이지 번호가 눌리고 나서 그에대한 화면을 보여주고 있는 것을 뜻한다.

<hide/>
.paper{
    margin-top: 10px;
    text-align: center;
}
.paper a.on{
    font-weight: bold;
    color: red;
}

 

  Note) 실행 결과

 

 

 

  Ex) 테이블에 인덱스 번호 넣기(전체 개수 => 1)

 

    - Dto에 seq 추가한다.

long seq;

 

    - impl 클래스

<hide/>
@Override
public List<MemberDto> list(MemberParam parameter) {
    long totalCount = memberMapper.selectListCount(parameter);
    List<MemberDto> list = memberMapper.selectList(parameter);

    if(!CollectionUtils.isEmpty(list)){
        int i = 0;
        for(MemberDto x : list){
            x.setTotalCount(totalCount);    // 전체 개수를 하나씩 다 넣어준다.
            x.setSeq(totalCount - parameter.getPageStart() - i);
            ++i;
        }
    }
    return list;
}

 

    - list => 1을 안 넣어도 똑같이 실행된다.

<td th:text="${x.seq}">1</td>

 

  Note) 실행 결과

 

 

  Ex) 검색한 데이터를 유지하면서 페이지 넘기기

    - 그런데 지금 문제는 검색한 다음의 조회 페이지이다. 아래 화면에서 2번 페이지를 누르면?

    -  아래와 같이  검색한 게 풀리고 전체 데이터에 대한 2 페이지 값이 나온다. 번호도 틀린다.

    - 컨트롤러

<hide/>

@GetMapping("/admin/member/list.do")
public String list(Model model, MemberParam parameter){

    parameter.init();   // 유효한 값이 되도록 만든다.
    List<MemberDto> members = memberService.list(parameter);    // 값이 저장되면 토탈카운트가 존재한다.

    long totalCount = 0;
    if(members != null && members.size() > 0){
        totalCount = members.get(0).getTotalCount();
    }
    String queryString = parameter.getQueryString();
    PageUtil pageUtil = new PageUtil(totalCount, parameter.getPageSize(), parameter.getPageIndex(), queryString);

    model.addAttribute("list", members);
    model.addAttribute("totalCount", totalCount);   // 데이터를 반환
    model.addAttribute("pager", pageUtil.pager());   // 데이터를 반환
    return "admin/member/list";
}

 

    - getQueryString() 메서드

<hide/>
public String getQueryString() {
    init();

    StringBuilder sb = new StringBuilder();

    if(searchType != null && searchType.length() > 0){
        sb.append(String.format("searchType=%s", searchType));
    }

    if(searchValue != null && searchValue.length() > 0){
        if(sb.length() > 0){
            sb.append("&");
        }
        sb.append(String.format("searchValue=%s", searchValue));
    }
    return sb.toString();
}

  Note) 실행 결과 - 정상적으로 실행된다.

    - 페이지를 넘어가도 전체 개수가 변하지 않고 검색된 결과가 유지된다.