계정 정보 뷰 화면에 권한 정보를 뿌리도록 서브 그리드를 추가합니다.
화면 코드에서는 jqGrid를 사용해서 코드를 작성합니다.
    <p class="ui-widget-content">
        <h2 class="smdis-pane-title" id="smdis-grid-title">
            <ul class="smdis-left-icons">
                <li id="smdis-add-button" class="ui-state-default ui-corner-all" title="권한 추가"><span class="ui-icon ui-icon-plusthick"></span></li>
                <li id="smdis-delete-refresh" class="ui-state-default ui-corner-all" title="권한 삭제"><span class="ui-icon ui-icon-minusthick"></span></li>
            </ul>
            ${model.loginId} 권한 정보
        </h2>
        <table id="smdis-sub-grid"></table>
        <div id="smdis-sub-pager"></div>
    </p>
    <script type="text/javascript">
        $(function(){
            $("#smdis-sub-grid").jqGrid({
                colNames:['id', '이름', '설명'],
                colModel :[
                    {name:'id', index:'id', width:55, hidden:true},
                    {name:'name', index:'name', width:100},
                    {name:'descr', index:'descr', width:100}
                ],
                pager: '#smdis-sub-pager',
                url:'${baseUrl}/${model.id}/rights'
            });
        });
    </script>
빨간 부분을 그리는데 이런 코드가 필요했습니다. 아직 버튼에 이벤트를 안달아서;; 좀 더 코드가 늘것 같네요. 일단은 서브 그리드를 그리는 기능만 만들겠습니다.
중요한 저 그리드에서 사용할 URL 핸들러를 만드는 거죠.
@Controller
@RequestMapping("/system/member")
public class MemberController extends GenericController<MemberService, MemberRef, Member, MemberSearchParam> {
    @RequestMapping("/{id}/rights")
    public void rights(@PathVariable int id, Model model, PageParam pageParam){
        model.addAttribute("list", service.rightListOfMember(id, pageParam));
    }
}
MemberController에 핸들러를 추가합니다. 평범해 보이지만 스프링 3.0 컨텐츠 네고 기능을 타고 JSON으로 바껴서 보내지게 됩니다. (물론 브라우저 주소창에 저 URL을 입력하면 JSP 페이지를 찾다가 원하는 페이지가 없다고 404 에러 코드가 보여지겠죠.)
public interface MemberService extends GenericService<Member, MemberSearchParam>{
    List<Right> rightListOfMember(int id, PageParam pageParam);
}
MemberService 인터페이스를 타고.. 
@Service
@Transactional
public class MemberServiceImpl extends GenericServiceImpl<MemberDao, Member, MemberSearchParam> implements MemberService {
    @Autowired RightService rightService;
    @Override
    public List<Right> rightListOfMember(int id, PageParam pageParam) {
        Member member = dao.getById(id);
        return rightService.listOfMember(member, pageParam);
    }
}
MemberServiceImpl에 와서.. id에 해당하는 Member를 꺼내주고 나머진 RightService에 위임합니다.
public interface RightService extends GenericService<Right, RightSearchParam>{
    List<Right> listOfMember(Member member, PageParam pageParam);
}
그럼 RightService 인터페이스를 타고..
@Service
@Transactional
public class RightServiceImpl extends GenericServiceImpl<RightDao, Right, RightSearchParam> implements RightService{
    public List<Right> listOfMember(Member member, PageParam pageParam) {
        pageParam.initCurrentPageInfosWith(member.getRights().size());
        return dao.listOf(member, pageParam);
    }
}
RightServcieImpl에서는 PageParam 값을 설정하고, DB에 다녀와야 할 일은 RightDao로 위임합니다.
public interface RightDao extends GenericDao<Right, RightSearchParam> {
    List<Right> listOf(Member member, PageParam pageParam);
}
그럼 RightDao 인터페이스를 타고..
@Repository
public class RightDaoImpl extends GenericDaoImpl<Right, RightSearchParam> implements RightDao {
    protected void applySearchParam(Criteria c, RightSearchParam rightSearchParam) {
        CriteriaUtils.addOptionalLike(c, "name", rightSearchParam.getName());
    }
    public List<Right> listOf(Member member, PageParam pageParam) {
        Criteria c = getCriteriaOf(Right.class);
        c.createAlias("members", "m");
        c.add(Restrictions.eq("m.id", member.getId()));
        applyPagingParam(c, pageParam);
        applyOrderingParam(c, pageParam);
        
        return c.list();
    }
}
여기서 member가 가지고 있는 권한 목록을 가져옵니다.
public class RightDaoImplTest extends SpringTest {
    @Autowired RightDao rightDao;
    @Test
    public void di(){
        assertThat(rightDao, is(notNullValue()));
    }
    @Test
    public void listOf() throws Exception {
        insertXmlData("testData.xml");
        Member member = new Member();
        member.setId(1);
        PageParam pageParam = new PageParam();
        pageParam.setRows(20);
        pageParam.setPage(0);
        List<Right> rights = rightDao.listOf(member, pageParam);
        assertThat(rights.size(), is(2));
        Right right = (Right)rights.get(0);
        assertThat(right.getId(), is(1));
        assertThat(right.getName(), is("admin"));
    }
}
<dataset>
<member id="1" loginid="whiteship" password="1" email="email@com" name="기선" />
    <rights id="1" name="admin"/>
    <rights id="2" name="user"/>
    <member_rights members_id="1" rights_id="1"/>
    <member_rights members_id="1" rights_id="2"/>
</dataset>
DAO 테스트에서는 원하는 Right를 가져오는지 확인합니다.
    @Test
    public void listOfMember() throws Exception {
        insertXmlData("testData.xml");
        Member member = memberDao.getById(1);
        PageParam pageParam = new PageParam();
        pageParam.setPage(0);
        pageParam.setRows(20);
        List<Right> rs = rightService.listOfMember(member, pageParam);
        assertThat(pageParam.getListSize(), is(2));
        assertThat(pageParam.getTotalPageSize(), is(1));
    }
서비스 테스트에서는 PageParam에 값이 잘 설정되는지 확인했습니다.
이제 잘 될 줄 알고 서버를 켜고 돌려봤더니.. 이런;;;
이런 문제가 생기더군요. 물론 그리드에도 제대로 표시가 되지 않았습니다.
Member <--> Right 양방향 관계로 설정했을 때 쌍방에서 가지고 있는 rights나 members가 비어있더라도 JSON 뷰로 넘어갔을 때 OSIV 필터에 의해서 무조건 값을 읽어오게 됩니다. 실제로 해당 레퍼런스가 아주 null이었으면 가져오려는 시도도 안할텐데 null도 아닙니다. Member나 Right에서 초기화 해준적도 없는데도 null이 아닙니다. 그건 하이버네이트 Lazy loading 하려고 프록시 객체를 만들어 놔서 그런것 같더군요. 어떻게 막아야 하나... 고민을 했습니다.
프로젝션과 DTO로 해결하려고 했으나.. 실패. 
흠.. JSON으로 만들지 않았으면 좋겠는데.. 그런거 없나 찾아보다 @JsonIgnore 발견.
그걸 적용해서 성공했습니다.
휴.. 집에가야지