어떤걸 구현해야겠다! 할때면 다들 구글링을 많이 하실텐데요. 저 또한 그렇습니다. 이것저것 많은 포스트를 보면서 어떻게 구현해야겠다..! 하는 대략적인 방향을 찾게되는데요. 그럴때마다 느끼는게 참 정답은 없는 거 같아요. 같은 기능이라도 보면 각자 다양한 방법으로 구현한 글들을 많이 만나게 되는 거 같아요. 이번 무한 스크롤 기능도 어떤 사람은 페이징 처리를 응용해서 구현하는 사람도 있고 LIMIT 기능을 사용하는 사람..등등
기능을 구현하면서 느끼게 되는 구현방식의 단점이나 장점들이 각자 다르고 그것들로 인해 더 나은 구현방식이 나오기 때문에 더 다양해지는게 아닐까 싶어요. 저도 지금은 LIMIT 기능으로 구현하지만 구현하면서 알게되는 단점이나 장점들을 가지고 다음엔 조금 더 좋은 방식으로 구현해보고자 합니다.
먼저, 구현을 하게되면 어떤식으로 화면에 보여지는지 보여드릴게요.
무한스크롤 중점으로 기록을 하는거라 데이터 출력하는 쿼리만 작성할거같은데, 전체 코드는 깃허브 참고해주세요..!
public interface PostDAO {
// 공지사항 목록 조회
List<Post> selectPostList(int offset, int limit, String selectKeyword);
}
변수로 offset, limit, selectKeyword 를 받고 있는데요, selectKeyword는 검색기능 구현하기 위해서 같이 사용한거라 offset, limit만 중점적으로 봐주세요.
@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);
}
}
public interface PostService {
// 공지사항 목록 조회
List<Post> getSelectPostList(int offset, int limit, String selectKeyword);
}
@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);
}
}
@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변수에 저장해 전달받았습니다.
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 어노테이션을 반드시!! 사용해야합니다.
<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을 전달받아 어느 범위만 출력받을지 설정했습니다.
// 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로 설정해 무한스크롤 기능이 끝나도록 했습니다.