MyBatis 쿼리 가이드
기본 원칙
#{}사용으로 PreparedStatement 파라미터 바인딩 (SQL Injection 방지)${}는 동적 컬럼명/정렬 등 불가피한 경우에만, 화이트리스트 검증 필수- 동적 쿼리는
<if>,<where>,<choose>태그 활용
SQL 파일 위치
/src/main/resources/mybatis/{업무명}/mapper/{업무명}Mapper.xml
예시:
src/main/resources/
└── mybatis/
├── user/
│ └── mapper/
│ └── UserMapper.xml
└── board/
└── mapper/
└── BoardMapper.xml
ANSI 쿼리 규칙
| 규칙 | 설명 |
|---|---|
| 키워드 대문자 | SELECT, FROM, WHERE, AND, OR 등 |
| 테이블명 대문자 | T_USER, T_BOARD |
| 컬럼명 대문자 | USER_NM, REG_DT |
SELECT * 금지 | 필요한 컬럼만 명시 |
| 컬럼 콤마 위치 | 줄바꿈 후 앞에 붙임 |
| 테이블 Alias | A → B → C 순 |
| JOIN 규칙 | INNER JOIN / LEFT JOIN 기본, RIGHT JOIN 최소화 |
| 시간 | SYSDATE 금지 → SYSTIMESTAMP 사용 |
컬럼 콤마 위치 규칙
-- 올바른 예 (콤마를 줄바꿈 후 앞에 붙임)
SELECT A.USER_SN
, A.USER_ID
, A.USER_NM
, A.USER_EMAIL
FROM T_USER A
-- 잘못된 예
SELECT A.USER_SN,
A.USER_ID,
A.USER_NM
FROM T_USER A
Mapper XML 구조
<!-- /src/main/resources/mybatis/user/mapper/UserMapper.xml -->
<?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">
<mapper namespace="com.{회사명}.{프로젝트명}.user.mapper.UserMapper">
<!-- 단건 조회 -->
<select id="get" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo"
resultType="com.{회사명}.{프로젝트명}.user.vo.UserVo">
SELECT A.USER_SN
, A.USER_ID
, A.USER_NM
, A.USER_EMAIL
, A.REG_DT
FROM T_USER A
WHERE A.USER_SN = #{userSn}
</select>
<!-- 목록 조회 -->
<select id="getList" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo"
resultType="com.{회사명}.{프로젝트명}.user.vo.UserVo">
SELECT A.USER_SN
, A.USER_ID
, A.USER_NM
, A.USER_EMAIL
FROM T_USER A
<where>
<if test="searchKeyword != null and searchKeyword != ''">
AND A.USER_NM LIKE '%' || #{searchKeyword} || '%'
</if>
</where>
ORDER BY A.USER_SN DESC
OFFSET #{offset} ROWS FETCH NEXT #{rowPerPage} ROWS ONLY
</select>
<!-- 전체 수 -->
<select id="getTotalCount" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo"
resultType="int">
SELECT COUNT(*)
FROM T_USER A
<where>
<if test="searchKeyword != null and searchKeyword != ''">
AND A.USER_NM LIKE '%' || #{searchKeyword} || '%'
</if>
</where>
</select>
<!-- 등록 -->
<insert id="regist" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo">
INSERT INTO T_USER (
USER_ID
, USER_NM
, USER_EMAIL
, REG_DT
) VALUES (
#{userId}
, #{userNm}
, #{userEmail}
, SYSTIMESTAMP
)
</insert>
<!-- 수정 -->
<update id="update" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo">
UPDATE T_USER
SET USER_NM = #{userNm}
, USER_EMAIL = #{userEmail}
, UPD_DT = SYSTIMESTAMP
WHERE USER_SN = #{userSn}
</update>
<!-- 삭제 -->
<delete id="delete" parameterType="com.{회사명}.{프로젝트명}.user.vo.UserVo">
DELETE FROM T_USER
WHERE USER_SN = #{userSn}
</delete>
</mapper>
시간 필드 타입 매핑
<!-- OffsetDateTime 타입 매핑 -->
<result property="regDt"
column="REG_DT"
javaType="java.time.OffsetDateTime"
jdbcType="TIMESTAMP_WITH_TIMEZONE"/>
- Java 타입:
java.time.OffsetDateTime - DB 타입:
TIMESTAMP WITH TIME ZONE SYSDATE사용 금지 →SYSTIMESTAMP사용
페이징
Oracle 기준 페이징:
OFFSET #{offset} ROWS FETCH NEXT #{rowPerPage} ROWS ONLY
GeneralVO의 offset, rowPerPage 필드를 자동으로 사용한다.
JOIN 규칙
-- INNER JOIN (기본)
SELECT A.USER_SN
, A.USER_NM
, B.DEPT_NM
FROM T_USER A
INNER JOIN T_DEPT B ON A.DEPT_SN = B.DEPT_SN
-- LEFT JOIN
SELECT A.USER_SN
, A.USER_NM
, B.DEPT_NM
FROM T_USER A
LEFT JOIN T_DEPT B ON A.DEPT_SN = B.DEPT_SN
WHERE A.USE_YN = 'Y'
Mapper 인터페이스
// com.{회사명}.{프로젝트명}.user.mapper.UserMapper
@Mapper
public interface UserMapper {
UserVo get(UserVo vo);
List<UserVo> getList(UserVo vo);
int getTotalCount(UserVo vo);
void regist(UserVo vo);
void update(UserVo vo);
void delete(UserVo vo);
}
동적 쿼리 태그
<!-- <where>: WHERE 키워드 자동 처리 (불필요한 AND 제거) -->
<where>
<if test="status != null">AND STATUS = #{status}</if>
<if test="keyword != null">AND USER_NM LIKE '%' || #{keyword} || '%'</if>
</where>
<!-- <choose>: switch-case -->
<choose>
<when test="type == 'A'">AND STATUS = 'ACTIVE'</when>
<when test="type == 'I'">AND STATUS = 'INACTIVE'</when>
<otherwise>AND STATUS IS NOT NULL</otherwise>
</choose>
<!-- <foreach>: IN 절 -->
<foreach collection="snList" item="sn" open="(" separator="," close=")">
#{sn}
</foreach>
application.yml MyBatis 설정
mybatis:
mapper-locations: classpath:mybatis/**/*Mapper.xml
configuration:
map-underscore-to-camel-case: true # SNAKE_CASE → camelCase 자동 변환
default-fetch-size: 100
default-statement-timeout: 30
체크리스트
- [ ] SQL 파일 위치:
/src/main/resources/mybatis/{업무명}/mapper/ - [ ] 키워드/테이블명/컬럼명 대문자
- [ ]
SELECT *금지 — 필요한 컬럼만 명시 - [ ] 컬럼 콤마는 줄바꿈 후 앞에 붙임
- [ ] 테이블 Alias:
A → B → C순 - [ ]
SYSDATE금지 →SYSTIMESTAMP사용 - [ ] 시간 필드:
javaType=java.time.OffsetDateTime, jdbcType=TIMESTAMP_WITH_TIMEZONE - [ ] 페이징:
OFFSET #{offset} ROWS FETCH NEXT #{rowPerPage} ROWS ONLY - [ ]
#{}파라미터 바인딩 사용