Let's Talk

Feel free to reach out. I'll get back to you as soon as possible.

Study 3 min

무한스크롤

Mybatis와 jQuery Ajax를 사용하여 NonOffset 방식의 무한 스크롤을 구현하는 방법을 알아봅니다.

무한스크롤

무한 스크롤을 구현하는 방법에는 Offset과 NonOffset 방식이 있는데 Offset의 경우 전체를 조회해서 선택하기 때문에 데이터양이 많을 경우 불러오는데 성능이 떨어질 수 있다.

최적화를 위해서는 NonOffset 방식을 사용하는 것이 좋다.

PageNation.js

//현재 스크롤 위치 저장
let lastScroll = 0;
let page = 1;	// 기본 시작값 (고정)
let nowPageLimit = 0;
let nextPageLimit = 0;
let beforePageLimit = parseInt($('#PgInfo').val());	// 기본 종료값 ()
// let beforePageLimit = 4;	// 기본 종료값 (pageInfo 변경될 때마다 변경 필요함 PageInfo.ROW_OF_PAGE)
let bucket = $('input[name=bucket]').val();

//데이터 가져오는 함수
function getData(limit){
	//다음페이지
	nextPageLimit = (page + 1) * limit;
	var currentScroll = $(this).scrollTop(); 
	
	$.ajax({
		type: "POST",
		enctype: 'multipart/form-data',
		url: "/api/getNotice",
		async : false,
		data: {
			 "end" : nextPageLimit,
			 "start" : beforePageLimit+1
		},
		success: function(data) {
			let html = ``;
			data.forEach((notice, idx) => {
				
	        html = 
        		`<tr>
					<input type="hidden" name="noti_no" value="${notice.noti_no}">
   					<td class="tbcol ncol1">
	   					<input type="text" name="subject" value="&nbsp;&nbsp;${notice.subject}"
	    					class="noticeSub noticeDetail" readonly="readonly">
	    			</td>
    				<td class="tbcol ncol2">
						<input type="text" name="writer" value="${notice.writer}"
						     					class="noticeDetail" readonly="readonly">
					</td>
     				<td class="tbcol ncol3">
     					<input type="text" name="regdate" value="${notice.regdate}"
	   						class="number" readonly="readonly">
	   				</td>
     				<td class="tbcol ncol4">
     					<input type="text" name="vdate" value="${notice.vdate}"
	   						class="number" readonly="readonly">
	   				</td>
	   				<td class="tbcol ncol5">
	   					<input type="text" name="readcount" value="${notice.readcount}"
		   					class="" readonly="readonly">
	   				</td>
   				</tr>`;
   				
				$("tbody").append(html);
			});
			
		},
		error: function (data, status, err) {
			alert("페이지 불러오기에 실패했습니다.");
		}, complete: function(){
			page += 1;
			beforePageLimit = beforePageLimit + limit;
		}
	});
}

$(document).scroll(function(e) {
    var currentScroll = $(this).scrollTop(); 		//현재 높이 저장
    var documentHeight = $(document).height();     //전체 문서의 높이

    var nowHeight = $(this).scrollTop() + $(window).height();     //(현재 화면상단 + 현재 화면 높이)

    if(currentScroll > lastScroll) {    //스크롤이 아래로 내려갔을때만 해당 이벤트 진행.
  
      //nowHeight을 통해 현재 화면의 끝이 어디까지 내려왔는지 파악가능 
      //즉 전체 문서의 높이에 일정량 근접했을때 글 더 불러오기)

      if(documentHeight < (nowHeight + (600))) {	
        // 기존 값 : documentHeight*0.2, 현재 값 : 600
        //함수콜
		    getData(8);	// 증가값
      }
        
    }

    //현재위치 최신화
    lastScroll = currentScroll;

});

아래는 서버단의 API 코드이다.

RestAPIController.java

@RestController
@Slf4j
public class RestApiController {
	
	@Autowired
	NoticeService noticeService;
	
	@PostMapping("/api/getNotice")
	public List<NoticeVO> getNotice(NoticeVO nvo) {
		List<NoticeVO> noticeList = noticeService.getNotice(nvo);
		return noticeList;
	}
}

그리고 해당 기능은 기존의 프로젝트하면서 적용한 내용이기 때문에 프로젝트 기준으로 작성되어있다.

그래서 Service와 ServiceImpl로 구현되어 있다.

자세한 사항

배우는 입장으로써 구현한 것이다. 이유는 다음과 같다.

  1. 인터페이스와 구현 클래스를 분리할 수 있다.
  2. 스프링 프레임워크가 제공하는 IoC(Inversion of Control) 기능과 함께 사용할 수 있다.

아무튼 ServiceImpl 쪽 코드를 보면 Notice

NoticeServiceImpl.java

@Slf4j
@Service
public class NoticeServiceImpl implements NoticeService {
	
	@Autowired
	private NoticeDao noticeDao;
	
	@Override
	public List<NoticeVO> getNotice(NoticeVO nvo) {
		List<NoticeVO> NoticeList = noticeDao.getNoticeList(nvo);
		return NoticeList;
	}
}

혹시 몰라 NoticeVO 코드도 올려놓았다.

NoticeVO.java

@Data
@ToString
public class NoticeVO {
	private int rn;
	private int noti_no;
	private int readcount;
	private String subject;
	private String content;
	private String writer;
	private String vdate;
	private String regdate;
	private String state;
	private String text;
	private int start;
	private int end;
}

Dao또한 인터페이스와 구현체인 Mapper 나눠져 있다. 파일 구조 자체가 현재 유연성을 높이기 위해서 전부 인터페이스로 나눠놓은 상태이다.

NoticeDao.java

@Mapper
public interface NoticeDao {
  List<NoticeVO> getNoticeList(NoticeVO nvo);
}

NoticeMapper.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.ecom6.dao.notice.NoticeDao">
  <select id="getNoticeList" parameterType="nvo" resultType="nvo">
	 SELECT * FROM (
	 SELECT ROWNUM RN, A.* FROM(
	 SELECT NOTI_NO, SUBJECT, CONTENT, READCOUNT, WRITER,
	        VDATE, REGDATE, STATE
	  FROM NOTICET
	  WHERE TO_DATE(VDATE) >= SYSDATE
	    	 AND  STATE = 'A'
		<if test="text!=null and text!=''">
	    	AND SUBJECT LIKE '%'||#{text}||'%'
		</if>  
	            ) A
	       ) 
		<![CDATA[
		WHERE RN >= #{start} AND RN <= #{end}
		 ]]> 
</select>
</mapper>

나중에 GIF를 추가해야겠다.