[Spring] MySQL LIMIT 기능을 이용한 무한 스크롤 구현

yunSeok·2023년 10월 18일
1

사이드 프로젝트

목록 보기
14/14
post-thumbnail

어떤걸 구현해야겠다! 할때면 다들 구글링을 많이 하실텐데요. 저 또한 그렇습니다. 이것저것 많은 포스트를 보면서 어떻게 구현해야겠다..! 하는 대략적인 방향을 찾게되는데요. 그럴때마다 느끼는게 참 정답은 없는 거 같아요. 같은 기능이라도 보면 각자 다양한 방법으로 구현한 글들을 많이 만나게 되는 거 같아요. 이번 무한 스크롤 기능도 어떤 사람은 페이징 처리를 응용해서 구현하는 사람도 있고 LIMIT 기능을 사용하는 사람..등등
기능을 구현하면서 느끼게 되는 구현방식의 단점이나 장점들이 각자 다르고 그것들로 인해 더 나은 구현방식이 나오기 때문에 더 다양해지는게 아닐까 싶어요. 저도 지금은 LIMIT 기능으로 구현하지만 구현하면서 알게되는 단점이나 장점들을 가지고 다음엔 조금 더 좋은 방식으로 구현해보고자 합니다.


구현 화면

먼저, 구현을 하게되면 어떤식으로 화면에 보여지는지 보여드릴게요.


구현 순서

  1. DAO, Service
  2. Controller
  3. Mapper
  4. JSP

무한스크롤 중점으로 기록을 하는거라 데이터 출력하는 쿼리만 작성할거같은데, 전체 코드는 깃허브 참고해주세요..!


1. DAO, Service

✅ PostDao

public interface PostDAO {
	
    // 공지사항 목록 조회 
    List<Post> selectPostList(int offset, int limit, String selectKeyword);
    
}

변수로 offset, limit, selectKeyword 를 받고 있는데요, selectKeyword는 검색기능 구현하기 위해서 같이 사용한거라 offset, limit만 중점적으로 봐주세요.

✅ PostDaoImpl

@Repository
@RequiredArgsConstructor
public class PostDAOImpl implements PostDAO{
	private final SqlSession sqlSession;

	@Override
	public List<Post> selectPostList(int offset, int limit, String selectKeyword) {
		return sqlSession.getMapper(PostMapper.class).selectPostList(offset, limit, selectKeyword);
	}

}

✅ PostService

public interface PostService {
	
    // 공지사항 목록 조회 
    List<Post> getSelectPostList(int offset, int limit, String selectKeyword);

}

✅ PostServiceImpl

@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class PostServiceImpl implements PostService{
	
	private final PostDAO postDAO;

	@Override
	public List<Post> getSelectPostList(int offset, int limit, String selectKeyword) {
		return postDAO.selectPostList(offset, limit, selectKeyword);
	}

}

2. Controller

@GetMapping("list")
public ResponseEntity<List<Post>> postList(
		@RequestParam("offset") int offset
		, @RequestParam("limit") int limit
		, @RequestParam("selectKeyword") String selectKeyword) {
	
	try {
		List<Post> postList = postService.getSelectResentlyPostList(offset, limit, selectKeyword);
		return new ResponseEntity<>(postList, HttpStatus.OK);
	} catch (Exception e) {
		return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
	}
}

일반적인 컨트롤러입니다! ResponseEntity로 응답 코드를 출력 받고
게시글 목록을 postList변수에 저장해 전달받았습니다.


3. Mapper

✅ PostMapper.java

public interface PostMapper {
   
    // 공지사항 목록 조회
    List<Post> selectPostList(@Param("offset") int offset, @Param("limit") int limit, @Param("selectKeyword") String selectKeyword);

}

❗️❗️ Mapper 부분이 중요합니다!!!
저는 이거 때문에 400에러 해결하지 못해서 엄청 고생했습니다...

위사진 처럼 Network쪽에서 400오류가 발생했었습니다.

📌 문제점
mapper에서 2개 이상의 변수를 전달받을 때는 @Param 어노테이션을 반드시!! 사용해야합니다.

✅ PostMapper.xml

<select id="selectPostList" resultType="com.project.dto.Post">
	SELECT 
		post_idx
		, userinfo_id
		, post_title
		, post_loc
		, post_tag
	FROM post 
	  <choose>
           <when test="selectKeyword != null and selectKeyword != ''">
                WHERE (p.post_title LIKE CONCAT('%', #{selectKeyword}, '%') 
                OR p.post_content LIKE CONCAT('%', #{selectKeyword}, '%')
                OR p.post_loc LIKE CONCAT('%', #{selectKeyword}, '%'))
                AND p.post_status = 1
           </when>
           <otherwise>
               WHERE p.post_status=1 
           </otherwise>
      </choose>
    ORDER BY post_regdate DESC
    LIMIT #{offset}, #{limit}
</select>

<choose></choose> 부분은 검색어 처리 부분입니다.
LIMIT #{offset}, #{limit}을 이용해 offset과 limit을 전달받아 어느 범위만 출력받을지 설정했습니다.


4. JSP

// false이면 무한스크롤 중지 (모든 게시물 출력상태)
// 초기값 true로 설정했습니디.
var postScroll = true;

// 초기값으로 0~16번째 게시물만 출력받도록 설정
var offset = 0; 
var limit = 16; 
var selectKeyword = ''; 

$(document).ready(function() {
    // 게시글 목록 출력 
    postListDisplay(offset, limit, selectKeyword);
  
    if (postScroll === true) {
        // 무한스크롤 함수 실행
    	$(window).scroll(scrollHandler);
    }
});
// 무한 스크롤 기능
function scrollHandler() {
    // 스크롤이 화면 가장 아래에 닿으면 실행됨
    if (postScroll === true && $(window).scrollTop() + $(window).height() >= $(document).height() - 30) {
        // 초기값은 limit 값을 더한 값으로 설정
        offset += limit;
        // 변경된 초기값으로 다시 목록이 출력됨
        postListDisplay(offset, limit, selectKeyword);
    }
}

//게시글 목록 출력
function postListDisplay(offset, limit, selectKeyword) {
	postScroll = true;
	
    $.ajax({
        method: "GET",
        url: "<c:url value='/post/list'/>",
        
        data: {"offset": offset
        		, "limit": limit
        		, "selectKeyword": selectKeyword
        	},
        dataType: "json",
        success: function(result) {
        	
            // 출력 데이터가 없다면 무한스크롤을 false(중지상태)로 설정
        	if (result.content.length === 0) { 
        		postScroll = false;
            // 출력 데이터가 16이하이면 목록 출력하고 무한스크롤 false 상태로 변경 
        	} else if(result.content.length <= 16) {
        		for (var i = 0; i < result.content.length; i++) {
	                var postList = result.content[i];
	                var postElement2 = 
	                	$("<div class='col-12 col-sm-6 col-md-4 col-lg-3 mb-4 clickable-post' data-aos='fade-up' data-aos-delay='100' data-aos-duration='800'>" +
	                		"<a href='${pageContext.request.contextPath}/post/" + postList.postIdx + "'>" +
	                                                     .
	                                                     .
	                                                     .
	                              "<span class='bottom-right'>" + postList.postViewcnt + " views</span>" +
	                            "</div>" +
	                          "</div>" +
	                        "</a>" +
	                    "</div>");
	                
	                $("#postList").append(postElement2);
	            }
	            postScroll = false;
            // 데이터가 16개 초과이면 16개까지만 출력하고 무한스크롤 상태 true로 유지됨
            } else {
				for (var i = 0; i < result.content.length; i++) {
	                var postList = result.content[i];
	                var postElement2 = 
	                	$("<div class='col-12 col-sm-6 col-md-4 col-lg-3 mb-4 clickable-post' data-aos='fade-up' data-aos-delay='100' data-aos-duration='800'>" +
	                		"<a href='${pageContext.request.contextPath}/post/" + postList.postIdx + "'>" +
                    	  						        .			
                     					     		    .
                     			        			    .
	                              "<span class='bottom-right'>" + postList.postViewcnt + " views</span>" +
	                            "</div>" +
	                          "</div>" +
	                        "</a>" +
	                    "</div>");
	                
	                $("#postList").append(postElement2);
	                
	            }
            }
        },
        error: function(error) {
          console.log(error);
        }
    });
}

중간에 ... 부분은 출력 태그가 길어 생략했습니다. 구조를 봐주시면 될 거 같아요!
전체코드는 위 구현순서 부분에 링크 첨부했습니다.

저는 offset += limit; 이런식으로 스크롤이 화면 아래에 닿으면 값이 추가되는 방식을 사용했고, 무한스크롤 상태를 true와 false로 설정해서 게시글이 더 있을때는 무한스크롤이 계속 동작되도록 하고, 게시글이 더이상 없을때는 false로 설정해 무한스크롤 기능이 끝나도록 했습니다.

0개의 댓글