무한스크롤

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

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

PageNation.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//현재 스크롤 위치 저장
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

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

NoticeMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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를 추가해야겠다.