본문 바로가기

TIL

3/28 - MyBatis Setting, 다중 테이블 조회, 다중 테이블 조회

mybatis-config.xml 환경설정

<properties> element

별도의 properties 파일을 가져와 환경변수값을 사용 가능하게 도와주는 태그

프로퍼티 값을 꺼내올 때에는 ${key} 표현식 사용


https://mybatis.org/dtd/mybatis-3-config.dtd>">

    
    
        
        
    
    
        
            
            
                
                
                
                
            
        
    
    
        
    

수정된 전문은 위와 같다 😊

<typeAliases>

Alias 제공하는 태그

<typeAliases>
	<typeAlias type="com.ino.dto.MenuDto" alias="Menu"/>
	<typeAlias type="com.ino.dto.CategoryDto" alias="Category"/>
</typeAliases>

위와 같이 사용 가능하다 😊

compileOnly("org.projectlombok:lombok:1.18.24")
annotationProcessor("org.projectlombok:lombok:1.18.24")

getter setter 자동화를 위해 lombok 패키지 추가를 해주자. intellij는 annotationprocessor라는 걸 추가로 넣어줘야 정상작동한다.

Lombok 유의사항

필드 네이밍 시 cCode와 유사하게 지을 경우, MyBatis 프레임워크는 setcCode(), getcCode() 메소드를 기본으로 찾는데,

롬복의 경우 setCCode(), getCCode() 의 형태로 생성해주기에 오류 발생 가능성이 있음.

→ 필드 네이밍에 신경쓰자

<resultMap>

조회 결과 데이터를 특정 객체에 매핑(로드)하는 방법 정의 엘리먼트

ResultSet으로부터 데이터를 가져와서 필드에 매핑하는 JDBC 코드 대체

MyBatis에서 가장 중요, 강력 엘리먼트

주요 속성

  1. <id>: resultMap 식별자(Mandatory)
  2. <type>: 결과 매핑 대상의 JavaType(Mandatory)
  3. <extends>: 자바의 상속과 같이 기 정의 resultMap을 상속받아 추가 매핑정보 기술(Not Mandatory)

하위 속성

  1. <id>: PK 컬럼 매핑을 위한 태그(성능향상(성능최적화))
  2. <association> : 복잡한 타입의 연관관계로 1:1 포함관계(has-a) 매핑시 사용
  3. <collection> : 복잡한 타입의 연관관계로 1:n 포함관계(has-many) 매핑시 사용

상단에 몰아두어 사용하는게 좋다.

<resultMap id="menuMap1" type="Menu">
	<id column="menu_code" property="menuCode"/>
	<result column="menu_name" property="menuName"/>
	<result column="menu_price" property="menuPrice"/>
</resultMap>
<resultMap id="menuMap2" type="Menu" extends="menuMap1">
	<result column="category_code" property="categoryCode"/>
	<result column="orderable_status" property="orderableStatus"/>
</resultMap>

JOIN을 통한 다중 테이블 조회

1:1(has - a) 관계

1:1 (has-a) 포함관계 구현을 위해,

상위 테이블에 하위 테이블 DTO를 필드로 넣어주자

<resultMap id="menuResultMap3" type="Menu">
	<id column="menu_code" property="menuCode"/>
	<result column="menu_name" property="menuName"/>
	<result column="menu_price" property="menuPrice"/>

	<association property="categoryDto" javaType="Category">
		<id column="category_code" property="categoryCode"/>
		<result column="category_name" property="categoryName"/>
		<result column="ref_category_code" property="refCategoryName"/>
	</association>
</resultMap>

association의 property 항목은 mandatory하며, JOIN하고자 하는 항목(부모테이블)의 어떤 필드에 매핑할지 DTO의 필드명을 적어주면 되고, javaType 항목에는 Alias, 혹은 주소에 대해 기술하면 된다.

테이블을 조인할 때 사용되는 AS.columnName에서, AS와 같은 것들은 내부적으로 사라지기에 고려하지 않아도 괜찮다.

<resultMap id="categoryDto" type="Category">
        <id column="category_code" property="categoryCode"/>
        <result column="category_name" property="categoryName"/>
        <result column="ref_category_code" property="refCategoryCode"/>
    </resultMap>
    <resultMap id="menuResultMap3" type="Menu">
        <id column="menu_code" property="menuCode"/>
        <result column="menu_name" property="menuName"/>
        <result column="menu_price" property="menuPrice"/>
        <association property="categoryDto" resultMap="categoryDto"/>
    </resultMap>

위의 방법의 연장으로, 기존 정의된 resultMap을 재사용하여 association하는 방법도 있다.

1:N(has-many)

제일 처음, DTO 설계부터 진행해주자.

자식 테이블인 orderMenuDTO

public class OrderMenuDto {
    private int orderCode;
    private int menuCode;
    private int orderAmount;
}

부모 테이블인 OrderDTO

public class OrderDto {
    private int orderCode;
    private String orderDate;
    private String orderTime;
    private int totalOrderPrice;

    private List<OrderMenuDto> orderMenuDtoList;
}

many 요소가 되는 자식 테이블의 요소를 List에 담아 만들어주자 😊

id 칼럼의 역할 → 동일한 id값의 경우, 상위객체를 또 생성하지 않고 내부의 하위 객체만 계속하여 새로 추가한다.

이해하기가 약간 어렵지만, DB ↔ Controller 데이터 흐름에 따라 차근차근 정리해보면,

menu-mapper.xml

<resultMap id="orderMenuResultMap" type="OrderMenu">
        <result column="menu_code" property="menuCode"/>
        <result column="order_amount" property="orderAmount"/>
</resultMap>
<resultMap id="orderResultMap" type="Order">
        <id column="order_code" property="orderCode"/>
        <result column="order_date" property="orderDate"/>
        <result column="order_time" property="orderTime"/>
        <result column="total_order_price" property="totalOrderPrice"/>

        <collection property="orderMenuDtoList" ofType="OrderMenu">
            <result column="menu_code" property="menuCode"/>
            <result column="order_amount" property="orderAmount"/>
        </collection> // 직접 정의하는 방식
</resultMap>
<select id="testResultMapCollection" resultMap="orderResultMap">
        SELECT
              o.order_code
            , o.order_date
            , o.order_time
            , o.total_order_price
            , om.menu_code
            , om.order_amount
        FROM
            tbl_order o
                JOIN tbl_order_menu om ON om.order_code = o.order_code
        WHERE
            o.order_code = #{code}
</select> 

해당 쿼리문의 결과는 이렇고,

이 중에서 중복되는 order_code, order_date, order_time, total_order_price (부모테이블) → OrderDTO에,

하위 테이블의 칼럼인 menu_code, order_amount는 하위 DTO인 OrderMenuDTO에 리스트 형태로 담긴다.

public OrderDto testResultMapCollection(int code) {
        SqlSession sqlSession = getSqlSession();
        menuMapper = sqlSession.getMapper(MenuMapper.class);

        OrderDto od = menuMapper.testResultMapCollection(code);
        sqlSession.close();
        return od;
    } // Service
    
OrderDto testResultMapCollection(int code); // Mapper

public static void main(String[] args) {
        // orderCode -> OrderInfo(ordercode, ordetdate, ordertime, totalprice, ordermenu(menucode, quantity))
        MenuService ms = new MenuService();
        // getAllMenuList(번호, 메뉴명, 가격)
        Scanner sc = new Scanner(System.in);
        System.out.print("input num: ");
        String num = sc.nextLine();
        OrderDto od = ms.testResultMapCollection(Integer.parseInt(num));
        System.out.println(od.toString());
    } // Controller단

OrderDto(orderCode=1, orderDate=20230101, orderTime=12:00:00, totalOrderPrice=30000, orderMenuDtoList=[OrderMenuDto(orderCode=0, menuCode=4, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=5, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=6, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=7, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=8, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=9, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=10, orderAmount=2)])

OrderDto(orderCode=1, orderDate=20230101, orderTime=12:00:00, totalOrderPrice=30000, orderMenuDtoList=[OrderMenuDto(orderCode=0, menuCode=4, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=5, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=6, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=7, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=8, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=9, orderAmount=2), OrderMenuDto(orderCode=0, menuCode=10, orderAmount=2)])

이런 형태로.

orderCode값이 0으로 삽입되는건, 간단하게 resultMap에 칼럼을 하나 추가해줌으로서 해결 가능하다.

<resultMap id="orderMenuResultMap" type="OrderMenu">
        <result column="order_code" property="orderCode"/>
        <result column="menu_code" property="menuCode"/>
        <result column="order_amount" property="orderAmount"/>
</resultMap>
<resultMap id="orderResultMap" type="Order">
        <id column="order_code" property="orderCode"/>
        <result column="order_date" property="orderDate"/>
        <result column="order_time" property="orderTime"/>
        <result column="total_order_price" property="totalOrderPrice"/>

        <collection property="orderMenuDtoList" resultMap="orderMenuResultMap"/>
</resultMap> // resultMap 을 통한 재사용 방식

<sql>

공통 사용 SQL 문자열의 일부를 정의하고 재사용을 위해 사용

정의부

<sql id="columns">
          menu_code
        , menu_name
        , menu_price
        , category_code
        , orderable_status
</sql>

구현부

<select id="selectMenuByCode" resultMap="menuMap2">
        SELECT
            <include refid="cloumns"/>
        FROM
            tbl_menu
</select>

Mapper, Service, Controller 모두 기존 구현부와 동일하게 구현해주면 된다.

but 식별이 어려워 자주 쓰이진 않는다고 한다.

테이블 연쇄 삽입

다중테이블 삽입의 경우, JDBC 는 INSERT A table → LASTINDEX() → INSERT B Table 이었지만,

MyBatis는 INSERT a → INSERT B로 손쉽게 가능하다.

Builder Pattern

Builder 패턴을 통해 일부 필드만 값의 적용이 가능하다.

CategoryDto category = CategoryDto.builder()
                                .categoryName(cateName)
                                .refCategoryCode(refCateCode)
                                .build();

이런 식으로 간편하게 할 수 있다.

DML문 공통 속성

<insert>, <update>, <delete>

  1. id
  2. parameterType
  3. flushCache : 매핑 구문 실행시 캐시 삭제 여부 결정 - 잘 사용 x
  4. timeout : sql문 실행 후 응답 기다리는 최대 시간 - 잘 사용 x
  5. statementType : JDBC 구문 타입 지정(STATEMENT, PREPARED(default), CALLABLE) - 간혹 사용
    • CALLABLE : PROCEDURE 실행시, statementType Callable하게 설정해주어야함.

<insert> 주요 추가 속성

  1. useGeneratedKeys : auto-increment를 통한 pk 생성(생성 키값) 활용 여부 설정, default = false Oracle은 불가.
  2. keyProperty : 생성 키값을 어떤 프로퍼티에 반영시킬것인지 결정 속성, DTO 속성명 작성 필요
<insert id="insertCategory" parameterType="Menu" useGeneratedKeys="true" keyProperty="categoryCode">
        <selectKey order="AFTER" keyProperty="categoryCode" resultType="_int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO
            tbl_category
            (
              category_name
            , ref_category_code
            )
        VALUES
            (
              #{categoryDto.categoryName}
            , #{categoryDto.refCategoryCode}
            )
</insert>

<insert id="insertMenu" parameterType="Menu" useGeneratedKeys="true" keyProperty="menuCode">
        INSERT INTO
            tbl_menu
            (
              menu_name
            , menu_price
            , category_code
            , orderable_status
            )
        VALUES
            (
              #{menuName}
            , #{menuPrice}
            , #{categoryCode}
            , #{orderableStatus}
            )
</insert>

위쪽부터 천천히 짚고 넘어가면, insert문의 id를 통해 Mapper와 연결시켜주고, parameterType을 통해 모델과 연결, useGeneratedKeys를 통해 auto_increment 옵션을 활성화 시켜준다. 그리고 해당 값을 keyProperty에 담아주기 위해 DTO의 값을 넣어준다.

selectKey는, useGeneratedKeys가 비활성화돼있거나, Oracle DB의 경우에는 해당 옵션을 통한 마지막 키 조회가 필수적이다.

그리고 밑의 INSERT문을 통해 MenuDTO의 필드명인 categoryDto에 접근하여 두 값을 가져와 삽입하고,

useGeneratedKeys를 통해 set된 categoryCode에 접근해 tbl_menu에도 값을 삽입해주자.

public boolean insertMenuAndCategory(MenuDto menu) {
        SqlSession sqlSession = getSqlSession();
        menuMapper = sqlSession.getMapper(MenuMapper.class);

        int result1 = menuMapper.insertCategory(menu);
        // 실행 이후 categoryCode필드에 SELECT LAST_INSERT_ID() 값이 포함되게 바뀜

        int result2 = menuMapper.insertMenu(menu);
        if(result1>0 && result2>0) {
            System.out.println("completely inserted");
            sqlSession.commit();
            return true;
        } else {
            System.out.println("insert failed");
            sqlSession.rollback();
            return false;
        }
    }
    // service단에서의 트랜잭션 처리

실행시 성공적으로 동작하는것을 볼 수 있다.

'TIL' 카테고리의 다른 글

4/7 - test code  (1) 2025.04.07
3/31 - MyBatis 동적쿼리, Mapper 태그 활용  (0) 2025.03.31
TIL 3/27 - MyBatis(UPDATE, DELETE, SELECT)  (0) 2025.03.27
TIL 3/26 - MyBatis  (0) 2025.03.26
TIL 3/25 - CI  (0) 2025.03.25