기타

mybatis: resultMap ( 1:1 / 1:n select )

hojomu 2023. 6. 21. 09:25

이번 프로젝트에서는 DB를 기능마다 나눠서, 기능을 추가하거나 데이터를 관리하기 편리하게

다음과 같이 설계했다.

 

다만 데이터를 select 하는데 있어서 두 가지 방법에 대해 이해가 필요했다

 

1 : 1 관계

두 가지 테이블을 join 할 때, 각 각 한 개의 행끼리 매치가 되도록 만드는 것이다.

위의 erd에서는 주문 테이블과 주문취소 테이블이 1:1 방법이다.

 

1개의 주문에는 해당 주문이 취소되었는지 아닌지 확인하는 주문 취소 열이 필요하다.

SELECT
	*
FROM `order` o
JOIN order_cancel oc ON o.order_id = oc.order_id
WHERE oc.state = 'normal'

이렇게 쿼리문을 작성하면, 주문 테이블과 주문 취소 테이블을 order_id 값을 기준으로 조인하고

주문 취소 테이블의 state 열의 값이 'normal'인 경우의 데이터만 select 할 수 있다.

결과로, 1개의 행에 주문에 대한 정보와 주문 취소 여부가 나타나게 된다.

 

 

1 : n 관계

특정 테이블의 1개의 행과 다른 테이블의 여러개의 행이 관계성을 가질 때는 조금 복잡해진다.

예를 들어서 1개의 주문에는 여러개의 주문된 제품과 제품의 갯수가 존재한다.

SELECT
	*
FROM `order` o
JOIN order_product oc ON o.order_id = op.order_id

해당 쿼리문을 실행시키면 1개의 주문 정보와 여러개의 ( 제품, 구매 수량 ) 데이터를 가지기 때문에

select의 결과로 여러개의 행을 불러온다.

 

다음과 같은 경우, resultMap을 사용해서 중복되는 데이터 (주문 정보) 를 하나로 만들고

여러개의 데이터 ( 제품, 구매 수량 ) 을 LIST 형태로 저장해서

1개의 VO에 저장할 수 있다.

 

resultMap 사용하기

<!-- 출하 계획표 불러오기 -->
	<select id="list" resultMap="adminOrderResultMap">
		SELECT
			o.order_id AS orderId,
			DATE_FORMAT(o.delivery_date, '%Y-%m-%d') AS deliveryDate,
			DATE_FORMAT(o.order_date, '%Y-%m-%d') AS orderDate,
			c.hospital AS hospital,
			c.name AS name,
			c.address AS address,
			c.email AS email,
			c.phone AS phone
		FROM `order` o
		JOIN order_cancel oc ON o.order_id = oc.order_id
		JOIN customer c ON o.customer_id = c.customer_id
		WHERE oc.state = 'normal'
			<choose>
				<when test="orderId != 0">
					AND o.order_id = #{orderId}
				</when>
			</choose>
		ORDER BY o.order_id DESC
		<!-- LIMIT ${(pageNum-1)*amount}, #{amount} -->
	</select>
	<!-- 주문한 물건 목록 불러오기 (이름, 갯수) -->
	<select id="selectOrderedProductList" parameterType="control.suppliers.model.AdminOrderVO" resultType="control.suppliers.model.OrderedProductVO">
		Select
			op.count AS count,
			p.name AS product,
			p.code AS code,
			p.count AS stock,
			p.tax AS tax,
			p.price AS price
		From ordered_product op
		LEFT JOIN product p ON op.product_id = p.product_id
		WHERE op.order_id = #{orderId}
	</select>

프로젝트에서 사용했던 쿼리문이다.

해당 쿼리문을 사용해서, 주문에 대한 정보(1개)와 주문 된 물건들에 대한 정보, 물건의 갯수 등(여러개)을 조회하고 싶었다.

 

우선은, 주문에 대한 정보를 select 하기 위해 list쿼리의 결과물을 resultMap으로 설정했고

<!-- 출하 계획표 resultMaps -->
<resultMap id="adminOrderResultMap" type="control.suppliers.model.AdminOrderVO">
    <id property="orderId" column="orderId" javaType="int"/>
    <result property="deliveryDate" column="deliveryDate" javaType="String"/>
    <result property="orderDate" column="orderDate" javaType="String"/>
    <result property="hospital" column="hospital" javaType="String"/>
    <result property="name" column="name" javaType="String"/>
    <result property="address" column="address" javaType="String"/>
    <result property="email" column="email" javaType="String"/>
    <result property="phone" column="phone" javaType="String"/>
    <collection column="orderId = orderId" property="orderedProduct" javaType="List"
    ofType="control.suppliers.model.OrderedProductVO" select="selectOrderedProductList"/>
</resultMap>

resultMap을 작성했다.

 

id 태그 : 해당 resultMap데이터의 key (index)가 될 column을 로 설정해야한다.

위의 코드에서는 주문번호로 설정했다.

property에는 해당 데이터가 저장될 변수명을 작성하고, column에는 데이터를 가져올 열의 이름을 작성했다.

 

result 태그 : 단 하나의 데이터를 저장하는데 사용했다. 이 코드에서는 주문 1개에 대한 정보를 result 태그에 저장했다

 

collection 태그 : 해당 태그는 여러개의 데이터를 List 형태로 저장할 수 있다.

이 코드에서는 collection 태그의 결과물의 타입을 ofType으로 VO를 지정했고, VO에 넣을 데이터를 select 문을 새로 연결해서 가져오도록 만들었다.

만약 select 문을 사용하지 않으면, list 쿼리문의 테이터를 id 나 result 형태로 가져올 수 있다.

 

또한 resultMap을 사용할 때는 꼭 태그들의 순서를 지켜야한다 ( 위의 코드에서 id - result - collection 순서가 지켜지지 않으면 에러가 발생한다. )

 

resultMap의 결과물은

이 VO에 저장해서 사용할 수 있다.