[회사일] 검색및 페이지 처리 구현하기
사실 이 작업은 따로 따로 했지만 중간에 정리해 두지 않아서 다시 코드를 빼면서 정리하기는 귀찮아서 한번에 정리한다.
먼저 화면에서 요청을 보내도록 한다.
$("#smdis-grid").jqGrid({
caption: '코드 목록',
url:'/base/code/list?' + $("#searchForm").serialize(),
colNames:['id', '코드값', '코드명', '설명'],
colModel :[
{name:'id', index:'id', width:55, hidden:true},
{name:'code', index:'code', width:80},
{name:'name', index:'name', width:90},
{name:'descr', index:'descr', width:90},
],
autoencode:true
});
그리드 구현은 이걸로 끝이다.
다음은 /base/code/list URL을 처리한 핸들러를 구현한다.
@RequestMapping
public void list(Model model, CodeSearchParam searchParam, PageParam pageParam) {
model.addAttribute("codeList", codeService.list(pageParam, searchParam));
}
화면에서 넘어온 파라미터들 중에 CodeSearchParam과 PageParam을 구분해서 받는다. @ModelAttribute가 적용된것이며 스프링 바인딩 기능이 적용되어 요청에 들어있는 파라미터들이 저 객체들의 속성으로 들어오게 된다.
처음엔 잘 넘어오는지 궁금해서 sout을 사용해서 콘솔에 찍어보면서 확인을 했었다. 특히 잘 넘어오더라도 한글이 잘 찍히는지 확인했다. 한글이 ??.?? 이렇게 넘어왔고, 파이어버그로 봤을 때는 화면에서 한글은 잘 넘어간 것 같다.
톰캣 server.xml을 설정해줘야겠다.
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" useBodyEncodingForURI="true" URIEncoding="UTF-8" />
이 뒤로는 한글이 잘 넘어왔다.
public class CodeSearchParam {
private String name;
private String code;
}
검색 옵션이고, 도메인 코드에 따라 검색 매개변수가 달랄 질 수 있으니 매번 만들어줘야겠다.
public class PageParam {
//화면으로 넘여줄 값.
int totalPageSize; // 전체 페이지 갯수
int listSize; // 전체 목록 갯수
int currentPageNumber; // 현재 보여줄 페이지
//화면에서 넘어오는 값
int page; //요청 받은 페이지
int rows; //한 화면당 보여줄 줄 수
String sidx; //정렬 기준 컬럼
String sord; //정렬 방향
}
이건 여러 컨트롤러에서 공통으로 사용할 수 있는 코드다. 따라서 두 클래스 패키지를 잘 분리해둔다. CodeSearchParam은 base/code/support에 넣고 PageParam은 common/page 에 뒀다. PageParam에 주석으로 속성들을 분리해 뒀지만 사실 별도의 클래스로 나눌까 생각도 해봤다. 그런데 좀 귀찮았다. 어차피 서로 관련있는 정보들이기 때문에 그냥 한 곳에 뒀다.
다음은 서비스 인터페이스에 필요한 걸 정의하고 서비스 구현 클래스에서 메서드를 구현한다.
public List<Code> list(PageParam pageParam, CodeSearchParam searchParam) {
pageParam.initCurrentPageInfosWith(codeDao.totalSize(searchParam));
return codeDao.list(pageParam, searchParam);
}
이전에 만들었던 dao 코드를 써먹을 수 있게됐다. 그런데 변경해야겠다. 전체 사이즈를 구할때는 searchParam만 있으면 되고 실제 list를 가져올 땐 둘 다 넘겨줘야된다.
public int totalSize(CodeSearchParam searchParam) {
Criteria c = getCriteriaOf(Code.class);
applySearchParam(c, searchParam);
return (Integer)c.setProjection(Projections.count("id"))
.uniqueResult();
}
public List<Code> list(PageParam pageParam, CodeSearchParam searchParam) {
Criteria c = getCriteriaOf(Code.class);
applySearchParam(c, searchParam);
if(pageParam != null){
c.setFirstResult(pageParam.getFirstRowNumber());
c.setMaxResults(pageParam.getRows());
}
return c.list();
}
private void applySearchParam(Criteria c, CodeSearchParam searchParam) {
if(!searchParam.getCode().isEmpty()){
c.add(Restrictions.ilike("code", searchParam.getCode(), MatchMode.ANYWHERE));
}
if(!searchParam.getName().isEmpty()){
c.add(Restrictions.ilike("name", searchParam.getName(), MatchMode.ANYWHERE));
}
}
Criteria API를 사용해서 검색 옵션과 페이징 처리를 했다. PageParam 클래스에는
public int getFirstRowNumber() {
return Math.max((getPage() - 1) * getRows() ,0);
}
public void initCurrentPageInfosWith(int totalListSize) {
setListSize(totalListSize);
setTotalPageSize((int)Math.ceil((double)totalListSize /getRows()));
setCurrentPageNumber(getPage());
}
이런 코드가 추가됐다. 이제 테스트좀 해볼까. 인텔리J에 단축키를 Ctrl+J로 등록해놨다. 이클립스에서는 별도로 플러그인을 설치해야 이런 기능을 사용할 수 있는데.. 귀찮지 아니한가?
public class PageParamTest {
@Test
public void testGetFirstRowNumber() throws Exception {
PageParam pageParam = new PageParam();
pageParam.setRows(20);
pageParam.setPage(0);
assertThat(pageParam.getFirstRowNumber(), is(0));
pageParam.setPage(-2);
assertThat(pageParam.getFirstRowNumber(), is(0));
pageParam.setPage(1);
assertThat(pageParam.getFirstRowNumber(), is(0));
pageParam.setPage(3);
assertThat(pageParam.getFirstRowNumber(), is(40));
}
@Test
public void testInitCurrentPageInfosWith() throws Exception {
PageParam pageParam = new PageParam();
pageParam.setRows(20);
pageParam.initCurrentPageInfosWith(20);
assertThat(pageParam.getListSize(), is(20));
assertThat(pageParam.getTotalPageSize(), is(1));
pageParam.initCurrentPageInfosWith(41);
assertThat(pageParam.getListSize(), is(41));
assertThat(pageParam.getTotalPageSize(), is(3));
}
}
CodeDao도 테스트 해주지 않으면 왠지 섭섭하다. Criteria API를 잔뜩 썼는데 제대로 쓴 건지 확인차 학습차 확인 해보자.
/**
* testData2.xml
*
* <dataset>
<code id="1" name="블랙" code="BLK" />
<code id="2" name="레드" code="RED" />
<code id="3" name="그린" code="GRN" />
<code id="4" name="블루" code="BLU" />
<code id="5" name="옐로" code="YLW" />
<code id="6" name="골드" code="GLD" />
<code id="7" name="실버" code="SLV" />
</dataset>
*/
@Test
public void testToTalSize() throws Exception {
insertXmlData("testData2.xml");
CodeSearchParam codeSearchParam = new CodeSearchParam();
codeSearchParam.setCode("L");
assertThat(codeDao.totalSize(codeSearchParam), is(5));
}
앗 이런 돌려보니 NullPointerException이다.
if(!searchParam.getName().isEmpty()){
c.add(Restrictions.ilike("name", searchParam.getName(), MatchMode.ANYWHERE));
}
여기서 발생했다. 아무래도 null 체크까지 추가해야겠다. @_@
private void applySearchParam(Criteria c, CodeSearchParam searchParam) {
if(searchParam.getCode() != null && !searchParam.getCode().isEmpty()){
c.add(Restrictions.ilike("code", searchParam.getCode(), MatchMode.ANYWHERE));
}
if(searchParam.getName() != null && !searchParam.getName().isEmpty()){
c.add(Restrictions.ilike("name", searchParam.getName(), MatchMode.ANYWHERE));
}
}
코드 참.. 거시기 하다. @_@ 그래도 일단 테스트부터 성공시키자. 오퀘 테스트가 잘 돌았다. 리팩토링 하자.
public class CriteriaUtils {
public static void addOptionalLike(Criteria c, String fieldName, String value) {
if(value != null && !value.isEmpty()){
c.add(Restrictions.ilike(fieldName, value, MatchMode.ANYWHERE));
}
}
}
이런 유틸 하나를 만들었다.
private void applySearchParam(Criteria c, CodeSearchParam searchParam) {
CriteriaUtils.addOptionalLike(c, "code", searchParam.getCode());
CriteriaUtils.addOptionalLike(c, "name", searchParam.getName());
}
DAO 코딩이 한결 간편해졌다. 테스트 해보자, 잘 돌아간다. 테스트를 보강하자.
@Test
public void testToTalSize() throws Exception {
insertXmlData("testData2.xml");
CodeSearchParam codeSearchParam = new CodeSearchParam();
//code 겁색
codeSearchParam.setCode("L");
assertThat(codeDao.totalSize(codeSearchParam), is(5));
//대소문자 구분 안함.
codeSearchParam.setCode("l");
assertThat(codeDao.totalSize(codeSearchParam), is(5));
//name 검색추가
codeSearchParam.setName("블");
assertThat(codeDao.totalSize(codeSearchParam), is(2));
}
잘 돈다. 이제 사이즈 구하는 쿼리는 안심이다.
@Test
public void testList() throws Exception {
insertXmlData("testData2.xml");
CodeSearchParam codeSearchParam = new CodeSearchParam();
PageParam pageParam = new PageParam();
pageParam.setRows(5); // 한 페이지에 5개씩 보자.
pageParam.setPage(1); // 첫페이지.
List<Code> codeList = codeDao.list(pageParam, codeSearchParam);
assertThat(codeList.size(), is(5));
System.out.println(codeList);
pageParam.setPage(2);
codeList = codeDao.list(pageParam, codeSearchParam);
assertThat(codeList.size(), is(2));
}
페이지 처리 테스트 코드인데 사실 좀 부실하다;; 그래도 이정도 해놓고 화면에서 확인해보니 잘 나온다. 오퀘.