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에서 가장 중요, 강력 엘리먼트
주요 속성
- <id>: resultMap 식별자(Mandatory)
- <type>: 결과 매핑 대상의 JavaType(Mandatory)
- <extends>: 자바의 상속과 같이 기 정의 resultMap을 상속받아 추가 매핑정보 기술(Not Mandatory)
하위 속성
- <id>: PK 컬럼 매핑을 위한 태그(성능향상(성능최적화))
- <association> : 복잡한 타입의 연관관계로 1:1 포함관계(has-a) 매핑시 사용
- <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>
- id
- parameterType
- flushCache : 매핑 구문 실행시 캐시 삭제 여부 결정 - 잘 사용 x
- timeout : sql문 실행 후 응답 기다리는 최대 시간 - 잘 사용 x
- statementType : JDBC 구문 타입 지정(STATEMENT, PREPARED(default), CALLABLE) - 간혹 사용
- CALLABLE : PROCEDURE 실행시, statementType Callable하게 설정해주어야함.
<insert> 주요 추가 속성
- useGeneratedKeys : auto-increment를 통한 pk 생성(생성 키값) 활용 여부 설정, default = false Oracle은 불가.
- 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 |