예제를 만들자 뚱땅뚱땅

API 만들기 연습 #4 페이징

NEWDODORIPYO 2022. 8. 26. 09:24

어떤 SQL을 사용하느냐에 따라 페이징 방법이 살짝 다를 수 있습니다 저도 MySQL로 만 만들어 보다 최근 PGSQL로 만들어야 했을 때 참... 난감하더군요 하지만 이 글을 보고 계신 분들을 뛰어나신 분들이기에 뚱땅 뚱땅 만드실 수 있을 거라 생각합니다.

 

자 그럼 select 와 페이징을 만들어보겠습니다

ApiRepository

package com.apiservice.repository;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ApiRepository {

    void insert(ApiDTO apiDTO);

    void update(ApiDTO apiDTO);

    void delete(Integer memberNumber);

    List<ApiVO> selectList(@Param("skip") int skip,@Param("size") int size);

}
  • 데이터를 하나가 아닌 여러개를 찾기 위해 List 형식으로 만들어 주고 @Param으로 skip와 size를 받았습니다 

ApiRepository. 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="com.apiservice.repository.ApiRepository">

    <insert id="insert">
        insert into api(member_id , member_pw , member_name , member_phone , advert)
        values (#{memberId} , #{memberPw} , #{memberName} , #{memberPhone} , #{advert})
    </insert>


    <update id="update">
        update api
        set member_id = #{memberId},
            member_pw = #{memberPw},
            member_name = #{memberName},
            member_phone = #{memberPhone},
            advert = #{advert},
            update_date = now()
        where member_number = #{memberNumber}
    </update>

    <delete id="delete">
        delete
        from api
        where member_number = #{memberNumber}
    </delete>

    <select id="selectList" resultType="com.apiservice.model.ApiVO">
        select member_number ,member_id , member_pw , member_name , member_phone , advert , update_date
        from api
        order by member_number desc
        limit #{skip},#{size}
    </select>

</mapper>
  • limit로 skip size를 설정해 주고 
  • order by를 pk 값으로 주었습니다 그리고 등록된 최신순으로 보기 위해 desc를 춉 
  • insert 할 때 입력되는 reg_date 빼고 전부 찾아오는 쿼리를 작성

페이지 기능을 할 ListDTO 만들기

package com.apiservice.model;

import lombok.Getter;
import lombok.ToString;

@ToString
@Getter
public class ListDTO{

   private int page;

   private int size;

   public ListDTO(){
           this.page = 1; //page 는 기본이 1 page
           this.size = 10; // size 는 기본 10개
        }

   public void setPage(int page){

           this.page = page <= 0? 1:page;
   }

   public void setSize(int size){

           this.size = size < 10? 10:size;
   }

   public int getSkip(){

           return (this.page - 1) * size;
   }

}
  • 이 DTO를 사용해서 페이징 처리를 할 생각인데 여기서 고민해볼 건 페이징 해서 데이터를 가지고 올 때 전체 데이터 값 즉 Total 값을 가져올 것인가? 하는 고민이다

ApiService

package com.apiservice.service;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;

import java.util.List;

public interface ApiService {

    void register(ApiDTO apiDTO);

    void update(ApiDTO apiDTO);

    void delete(Integer memberNumber);

    List<ApiDTO> getList(ListDTO listDTO);

}
  • 리스트 기능해줄 getList 만들기

ApiServiceImpl

package com.apiservice.service;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;
import com.apiservice.repository.ApiRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@Log4j2
@RequiredArgsConstructor
public class ApiServiceImpl implements ApiService {

    private final ApiRepository apiRepository;
    private final ModelMapper modelMapper;

    @Override
    public void register(ApiDTO apiDTO) {

        log.info("service insert");
        log.info(apiDTO);

        apiRepository.insert(apiDTO);

    }

    @Override
    public void update(ApiDTO apiDTO) {
        log.info("update service");
        log.info(apiDTO);

        apiRepository.update(apiDTO);
    }

    @Override
    public void delete(Integer memberNumber) {
        log.info("delete service");
        log.info(memberNumber);

        apiRepository.delete(memberNumber);
    }

    @Override
    public List<ApiDTO> getList(ListDTO listDTO) {

        List<ApiVO> apiList = apiRepository.selectList(listDTO);

        return null;
}
  • getList 구현
  • 이후 작업을 위해 필요한 ModelMapper 설정을 추가하러 가보자

ModelMapper Maven 추가


<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.0.0</version>
</dependency>

config 패키지에 ModelMapper 생성

package com.apiservice.config;

import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapper {

    @Bean
    public org.modelmapper.ModelMapper getMapper() {
        org.modelmapper.ModelMapper modelMapper = new org.modelmapper.ModelMapper();
        modelMapper.getConfiguration()
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
                .setMatchingStrategy(MatchingStrategies.LOOSE);

        return modelMapper;
    }
}

  • xml로 빈 설정할 수 도있지만 xml보다 자바로 설정하는 게 짧아서 이렇게 함

ApiServiceImpl

package com.apiservice.service;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;
import com.apiservice.repository.ApiRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@Log4j2
@RequiredArgsConstructor
public class ApiServiceImpl implements ApiService {

    private final ApiRepository apiRepository;
    private final ModelMapper modelMapper;

    @Override
    public void register(ApiDTO apiDTO) {

        log.info("service insert");
        log.info(apiDTO);

        apiRepository.insert(apiDTO);

    }

    @Override
    public void update(ApiDTO apiDTO) {
        log.info("update service");
        log.info(apiDTO);

        apiRepository.update(apiDTO);
    }

    @Override
    public void delete(Integer memberNumber) {
        log.info("delete service");
        log.info(memberNumber);

        apiRepository.delete(memberNumber);
    }

    @Override
    public List<ApiDTO> getList(ListDTO listDTO) {

        List<ApiVO> apiList = apiRepository.selectList(**listDTO**);

        //apiVO 를 ApiDTO타입의 객체로 맵핑
        List<ApiDTO> dtoList =
                apiList.stream().map(apiVO -> modelMapper.map(apiVO,ApiDTO.class))
                        .collect(Collectors.toList());

        return dtoList;
    }
}
  • 파라미터를 하나하나 따로 받는 거보다 DTO타입으로(객체 타입)으로 받는 것이 더 유연하다

Controller

지금까지 만든 기능을 주입해보자

package com.apiservice.controller;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;
import com.apiservice.service.ApiService;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/")
@Log4j2
@RequiredArgsConstructor
public class ApiController {

    private final ApiService apiService;

    @ApiOperation("Api Get list")
    @GetMapping("/list")
    public List<ApiDTO> list(ListDTO listDTO ){

        log.info("board list.........");

        log.info(listDTO);

        List<ApiDTO> dtoList = apiService.getList(listDTO);

        return dtoList;

    }

    @ApiOperation("Api Post register")
    @PostMapping("/register")
    public ResponseEntity registerPost(ApiDTO apiDTO){
        log.info(apiDTO);
        log.info("controller register");

        apiService.register(apiDTO);

        return new ResponseEntity(HttpStatus.OK);
    }

    @ApiOperation("api post update")
    @PostMapping("/update/{memberNumber}")
    public ResponseEntity update(@PathVariable("memberNumber") Integer memberNumber , ApiDTO apiDTO){

        log.info("-----------------------");
        log.info("update");
        log.info("------------------");
        apiService.update(apiDTO);

        return new ResponseEntity(HttpStatus.OK);
    }

    @ApiOperation("api post delete")
    @PostMapping("/delete/{memberNumber}")
    public ResponseEntity delete(@PathVariable("memberNumber") Integer memberNumber){
        log.info("-----------------------");
        log.info("delete");
        log.info("------------------");

        apiService.delete(memberNumber);

        return new ResponseEntity(HttpStatus.OK);
    }
    

}
  • 기본적인 List 기능은 끝난 것 같으니 추가적으로 total 값을 처리해보자

ApiRepository

package com.apiservice.repository;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ApiRepository {

    void insert(ApiDTO apiDTO);

    void update(ApiDTO apiDTO);

    void delete(Integer memberNumber);

    List<ApiVO>selectList(ListDTO listDTO);

    int getTotal(ListDTO listDTO);

}

ApiRepository.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="com.apiservice.repository.ApiRepository">

    <insert id="insert">
        insert into api(member_id , member_pw , member_name , member_phone , advert)
        values (#{memberId} , #{memberPw} , #{memberName} , #{memberPhone} , #{advert})
    </insert>


    <update id="update">
        update api
        set member_id = #{memberId},
            member_pw = #{memberPw},
            member_name = #{memberName},
            member_phone = #{memberPhone},
            advert = #{advert},
            update_date = now()
        where member_number = #{memberNumber}
    </update>

    <delete id="delete">
        delete
        from api
        where member_number = #{memberNumber}
    </delete>

    <select id="selectList" resultType="com.apiservice.model.ApiVO">
        select member_number ,member_id , member_pw , member_name , member_phone , advert , update_date
        from api
        order by member_number desc
        limit #{skip},#{size}
    </select>

    <select id="getTotal" resultType="int">
        select count (member_number) from api
    </select>

</mapper>

Generic(제네릭) 구조 사용해보기

  • list를 구해올 때 total 값도 같이 구해오려고 하는데 그러면 하나의 객체에서 처리하면 안 될까? 하는 생각으로 만든 제네릭 구조

ListResponseDTO

package com.apiservice.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ListResponseDTO <E>{

    private List<E> dtoList;

    private int total;

    //- 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
    //- 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.
    //- 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.
}

ApiService

package com.apiservice.service;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;

import java.util.List;

public interface ApiService {

    void register(ApiDTO apiDTO);

    void update(ApiDTO apiDTO);

    void delete(Integer memberNumber);

    ListResponseDTO<ApiDTO> getList(ListDTO listDTO);

}
  • 기존 List 였던걸 ListResponseDTO로 변경

ApiService.Impl

package com.apiservice.service;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ApiVO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;
import com.apiservice.repository.ApiRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@Log4j2
@RequiredArgsConstructor
public class ApiServiceImpl implements ApiService {

    private final ApiRepository apiRepository;
    private final ModelMapper modelMapper;

    @Override
    public void register(ApiDTO apiDTO) {

        log.info("service insert");
        log.info(apiDTO);

        apiRepository.insert(apiDTO);

    }

    @Override
    public void update(ApiDTO apiDTO) {
        log.info("update service");
        log.info(apiDTO);

        apiRepository.update(apiDTO);
    }

    @Override
    public void delete(Integer memberNumber) {
        log.info("delete service");
        log.info(memberNumber);

        apiRepository.delete(memberNumber);
    }

    @Override
    public ListResponseDTO<ApiDTO> getList(ListDTO listDTO) {

        List<ApiVO> apiList = apiRepository.selectList(listDTO);

        //apiVO 를 ApiDTO타입의 객체로 맵핑
        List<ApiDTO> dtoList =
                apiList.stream().map(apiVO -> modelMapper.map(apiVO,ApiDTO.class))
                        .collect(Collectors.toList());

        return ListResponseDTO.<ApiDTO>builder()
                .dtoList(dtoList)
                .total(apiRepository.getTotal(listDTO))
                .build();
    }
}

ApiController

package com.apiservice.controller;

import com.apiservice.model.ApiDTO;
import com.apiservice.model.ListDTO;
import com.apiservice.model.ListResponseDTO;
import com.apiservice.service.ApiService;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/")
@Log4j2
@RequiredArgsConstructor
public class ApiController {

    private final ApiService apiService;

    @ApiOperation("Api Get list")
    @GetMapping("/list")
    public ListResponseDTO<ApiDTO> list(ListDTO listDTO){

        log.info("Api list");

        log.info(listDTO);

        ListResponseDTO<ApiDTO> responseDTO = apiService.getList(listDTO);

        log.info("List Controller ");
        log.info(responseDTO);

        return responseDTO;

    }

    @ApiOperation("Api Post register")
    @PostMapping("/register")
    public ResponseEntity registerPost(ApiDTO apiDTO){
        log.info(apiDTO);
        log.info("controller register");

        apiService.register(apiDTO);

        return new ResponseEntity(HttpStatus.OK);
    }

    @ApiOperation("api post update")
    @PostMapping("/update/{memberNumber}")
    public ResponseEntity update(@PathVariable("memberNumber") Integer memberNumber , ApiDTO apiDTO){

        log.info("-----------------------");
        log.info("update");
        log.info("------------------");
        apiService.update(apiDTO);

        return new ResponseEntity(HttpStatus.OK);
    }

    @ApiOperation("api post delete")
    @PostMapping("/delete/{memberNumber}")
    public ResponseEntity delete(@PathVariable("memberNumber") Integer memberNumber){
        log.info("-----------------------");
        log.info("delete");
        log.info("------------------");

        apiService.delete(memberNumber);

        return new ResponseEntity(HttpStatus.OK);
    }
    

}

swagger-ui에서 테스트해보기

  • 중간중간 기능을 만들 때마다 swagger-ui로 테스트하면서 진행했지만 분량상 마지막 테스트만 올렸습니다 이걸 만들어보시는 분들은 작은 기능 하나하나 만들 때마다 꼭 테스트하면서 돌려보세요!! 나중에 에러 나면 잡기가 어려워요 ㅠ

이렇게 기본적인 페이징 그리고 total 값을 가져오는 코드까지 완성했습니다 다음에는 기존 부실했던 코드들을 보완하는 작업을 해보려 합니다