MyBatis 실무 패턴 — 시퀀스 채번·1:N 컬렉션 매핑·${} vs #{}·동적 컬럼 바인딩
시퀀스 채번, 1:N 컬렉션 매핑, ${} vs #{} 차이, Map의 동적 컬럼 바인딩 등 Mybatis 사용 중 마주친 패턴들.
MyBatis 실무 패턴 — 시퀀스 채번·1:N 컬렉션 매핑·${} vs #{}·동적 컬럼 바인딩
MyBatis로 개발하다 보면 프레임워크가 알아서 처리해주지 않아 매번 XML 매퍼를 직접 손봐야 하는 지점들이 있다. 시퀀스로 PK를 채번하는 방법, 조인 결과를 객체 하나에 1:N으로 묶는 방법, 언뜻 비슷해 보이는 ${}와 #{}의 차이, 그리고 컬럼명 자체를 런타임에 결정하는 동적 바인딩이 대표적이다.
이 글은 그 네 가지 패턴을 실무에서 마주친 순서대로 정리한 기록이다. 아래 순서로 다룬다.
- Sequence 채번 — INSERT 전에 시퀀스 값을 미리 확보해 PK로 사용하기
- 1:N 컬렉션 매핑 — 조인 결과를 부모 객체 + 자식 리스트로 묶기
${}vs#{}— 문자열 치환과 값 바인딩의 차이, 그리고 보안- 동적 컬럼 바인딩 — 컬럼명을 런타임에 Map으로 주입하기
Sequence 사용하기
- 오라클처럼 시퀀스로 PK를 채번하는 DB에서, INSERT 직전에 다음 시퀀스 값을 미리 받아 그대로 PK 컬럼에 넣고 싶을 때 쓴다.
<selectKey>로 채번 쿼리를 INSERT에 붙여 한 번의 호출로 처리한다.
1
2
3
4
5
6
7
8
9
<mapper namespace="io.test.TestDao">
<insert id="save">
<selectKey keyProperty="IDENTIFIER_SEQUENCE" resultType="java.lang.Integer" order="BEFORE">
select SEQUENCE_GENERATOR.NEXTVAL from dual
</selectKey>
INSERT INTO tbl_test (id, title, description)
VALUES (#{IDENTIFIER_SEQUENCE}, 'title', 'desciption')
</insert>
</mapper>
- order는 before, after 두가지가 있다. before는 쿼리문 실행 전에 시퀀스 값을 생성하고, after는 쿼리문 실행 후에 시퀀스 값을 생성한다.
-
가 사용하는 parameterType 에 sequence keyProperty 를 setting 할 수 있는 속성과 setter 메서드가 있어야 한다.
컬렉션 매핑
- 팀 하나에 여러 멤버가 딸린 것처럼, 조인으로 펼쳐진 여러 행을 부모 객체 한 개 + 자식 리스트로 묶고 싶을 때 쓴다. 조인 결과의 자식 행들을
<collection>으로 모아 하나의 부모 객체에 담는다. - 컬렉션 매핑은 1:N 관계를 매핑할 때 사용한다.
- 컬렉션 매핑은 collection 태그를 사용한다.
- collection 태그는 property, javaType, ofType 속성을 사용한다.
- property : 컬렉션을 담을 프로퍼티 이름
- javaType : 컬렉션의 타입 (ArrayList, HashSet, …)
- ofType : 컬렉션에 담길 객체의 타입
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<mapper namespace="io.test.TeamDao">
<resultMap id="teamResultMap" type="io.test.Team">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="description" column="description"/>
<collection property="members" javaType="java.util.ArrayList" ofType="io.test.Member">
<id property="id" column="member_id"/>
<result property="name" column="name"/>
</collection>
</resultMap>
<select id="selectTest" resultMap="teamResultMap">
SELECT
t.id,
t.title,
t.description,
m.id AS member_id,
m.name
FROM tbl_team t
LEFT JOIN tbl_member m ON t.id = m.team_id
</select>
</mapper>
mybatis ${} 와 #{} 차이
- ${} : 문자열 치환할 때 사용한다.
- #{} : 매개변수로 전달된 값을 바인딩할 때 사용한다.
| 구분 | ${} | #{} |
|---|---|---|
| 사용처 | 변수, 컬럼명 | 값 |
| 사용예 | ${column} | #{value} |
| 원리 | Mybatis가 문자열로 치환 | Mybatis가 값에 대한 바인딩을 사용 |
| 주의 | SQL Injection 공격에 취약 | SQL Injection 공격에 안전 |
| 예시 | SELECT ${column} FROM tbl_test | SELECT * FROM tbl_test WHERE id = #{id} |
mybatis key-value map 동적으로 사용하기
- 컬럼 목록을 코드에 고정할 수 없고 런타임에 결정해야 할 때(예: CSV 헤더를 그대로 테이블에 적재) 쓴다. 컬럼명은
${}로 SQL에 심고, 값은#{}로 바인딩한다. - #{row.${column}} 처럼 사용하면 row(map객체)의 column(key)에 해당하는 값(value)을 동적으로 읽어들일 수 있다. 즉
${column}이 먼저 문자열로 치환돼#{row.name}같은 형태가 된 뒤, 그 값이 바인딩된다.
보안 주의: 여기서
${tableName}과${column}은 문자열 그대로 SQL에 이어 붙기 때문에, 사용자 입력값을${}에 직접 넣으면 SQL Injection에 그대로 노출된다. 테이블명·컬럼명처럼${}로 넣어야만 하는 값은 반드시 서버가 관리하는 화이트리스트(허용된 컬럼명 목록)와 대조해 검증한 뒤 사용한다. 값(value)은 예시처럼#{}로 바인딩하면 안전하다.
- header : List
, row : Map<String, Object> 이고 map 객체에 header와 row를 담아서 파라미터로 전달할때 아래와 같이 사용할 수 있다. ```xml
```
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.