생계유지형 개발자/Spring Framework

[MyBatis] 계층형 결과 리스트 생성 (resultMap, collection 활용)

이 가을 2021. 2. 25. 17:48

대표적으로 카테고리의 계층형 결과를 조회할 때 이 방법이 사용될 수 있다.

실제로 실무에서 사용 중인 테이블 구조와 소스코드를 필요한 부분만을 예제로 사용했다.

오라클처럼 계층형 쿼리가 있는 dbms라면 좋겠지만, mysql은 쿼리에서 지원되지 않는다.

별도의 함수를 만들거나 with recursive 라는 지시어를 사용하던데 mybatis로 해결이 되는 일이기 때문에 일일이 시도해보지 않았다.

 

샘플 데이터 계층 구조

데이터의 계층 구조는 다음과 같다. (* 코드명 ... 코드 형식으로 표기)

  1. 인사기준 ... L1
    1. 출장 ... L1_M1
      1. 국내 출장 ... L1_M1_S1
      2. 해외 출장 ... L1_M1_S2
    2. 퇴직 ... L1_M2
      1. 퇴직금 산출기준 ... L1_M2_S1
      2. 퇴직금 중간정산 ... L1_M2_S2
  2. 복리후생 ... L2
    1. 어학 지원 ... L2_M1
    2. 경조사 지원 ... L2_M2

 

 

데이터베이스 테이블

위 데이터를 저장하고 있는 데이터베이스 테이블은 다음과 같다. (* 테이블 명: tb_category)

id code code_name up_code
1 L1 인사기준 null
2 L2 복리후생 null
3 L1_M1 출장 L1
4 L1_M2 퇴직 L1
5 L2_M1 어학 지원 L2
6 L2_M2 경조사 지원 L2
7 L1_M1_S1 국내 출장 L1_M1
8 L1_M1_S2 해외 출장 L1_M1
9 L1_M2_S1 퇴직금 산출기준 L1_M2
10 L1_M2_S2 퇴직금 중간정산 L1_M2

 

JAVA 모델 객체

위의 데이터를 아래의 모델 객체에 Set 할 것이다.

@Getter
@Setter
@ToString
Class Category {

    private long id;			// 고유 id
    private String code;		// 코드
    private String codeName;		// 코드 명   
    private String upCode;		// 상위 카테고리 코드
    private List<Category> children;	// 하위 카테고리 목록	
}

children 필드에는 하위 카테고리 목록이 Set 된다.

 

데이터 조회 쿼리

Mybatis의 Mapper.xml 설정을 보기 전에 mysql은 계층형 쿼리를 지원하지 않는데 어떻게 데이터를 조회하는지, 그 쿼리문을 보자.

select 
	l.id l_id, l.code l_code, l.code_name l_codeName, l.up_code l_upCode,
	m.id m_id, m.code m_code, m.code_name m_codeName, m.up_code m_upCode,
	s.id s_id, s.code s_code, s.code_name s_codeName, s.up_code s_upCode
from
    tb_category l
    left outer join tb_category m on (l.code = m.up_code)
    left outer join tb_category s on (m.code = s.up_code)

위의 쿼리 결과는 계층형으로 나오지 않는다.

계층 구조가 예제와 같이 L > M > S 라면 하나의 row에 L,M,S 컬럼이 모두 조회된다.

그래서 총 결과 수는 각 Category 별 최하위 분류 개수 만큼 결과 row가 나온다.

 

위의 쿼리에서 몇몇 컬럼을 덜어내고 결과를 표시해보자면 다음과 같다.

l_code l_codeName m_code m_codeName s_code s_codeName
L1 인사기준 L1_M1 출장 L1_M1_S1 국내 출장
L1 인사기준 L1_M1 출장 L1_M1_S2 해외 출장
L1 인사기준 L1_M2 퇴직금 L1_M2_S1 퇴직금 산출 기준
L1 인사기준 L1_M2 퇴직금 L1_M2_S2 퇴직금 중간 정산
L2 복리후생 L2_M1 어학지원 (null) (null)
L2 복리후생 L2_M2 경조사 지원 (null) (null)

이렇게 한 줄에 한 분류를 모두 표시하는 쿼리문을 작성한 다음 Mapper.xml 을 작성할 수 있다.

 

Mapper.xml

<resultMap />과 <select />을 다음과 같이 작성한다.

<?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">

    <resultMap id="categoryHierarchyMap" type="com.navercorp.app.user.vo.Category">
        <id column="l_id" property="id" javaType="long"/>
        <result column="l_code" property="code" javaType="string"/>
        <result column="l_codeName" property="codeName" javaType="string"/>
        <result column="l_upCode" property="upCode" javaType="string"/>
        <collection property="children" ofType="com.navercorp.app.user.vo.Category">
        	<id column="m_id" property="id" javaType="long"/>
	        <result column="m_code" property="code" javaType="string"/>
    	    <result column="m_codeName" property="codeName" javaType="string"/>
       	 	<result column="m_upCode" property="upCode" javaType="string"/>
        	<collection property="children" ofType="com.navercorp.app.user.vo.Category">
            	<id column="s_id" property="id" javaType="long"/>
                <result column="s_code" property="code" javaType="string"/>
                <result column="s_codeName" property="codeName" javaType="string"/>
                <result column="s_upCode" property="upCode" javaType="string"/>
            </collection>
        </collection>
    </resultMap>

    <select id="selectCategoryHierarchy" resultMap="categoryHierarchyMap">
        select 
            l.id l_id, l.code l_code, l.code_name l_codeName, l.up_code l_upCode,
            m.id m_id, m.code m_code, m.code_name m_codeName, m.up_code m_upCode,
            s.id s_id, s.code s_code, s.code_name s_codeName, s.up_code s_upCode
        from
            tb_category l
            left outer join tb_category m on (l.code = m.up_code)
            left outer join tb_category s on (m.code = s.up_code)
    </select>

</mapper>