| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
- 부트캠프후기
- 함수
- oracle
- ES6
- 노션
- 현대이지웰java풀스택개발자아카데미6월
- node.js
- strpos()
- react
- 오류
- error
- JDBC
- 객체지향
- DOM
- 정규식
- Java
- MySQL
- formula
- dao
- php
- 멀티캠퍼스it부트캠프
- 배열
- 깃허브
- JavaScript
- OOP
- Excel
- explode()
- DTO
- myshortcut
- jQuery
- Today
- Total
코딩짜는 일상
[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 19차 - 프로젝트에서 MyBatis 사용하기 (feat.Oracle) 본문
[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 19차 - 프로젝트에서 MyBatis 사용하기 (feat.Oracle)
Remily 2025. 11. 25. 23:25📚 서론
과거 PHP를 쓸 때는 sql_query() 함수를 사용해 DB 통신을 구현했었습니다.
쿼리를 문자열로 작성해 param으로 제공하면 되는 간단한 방식이기에
if문을 이용한 문자열 편집으로 간단하게 쿼리를 조립할 수 있었지만...
사실 이건 인젝션 공격에 취약하기 때문에 그리 현명한 선택은 아니었습니다.😥
물론 자체적으로 입력값에 UNION을 차단하는 기능을 추가해 방어했지만 여전히 위험요소가 있었죠...🥲

현재 팀 프로젝트에선 MyBatis를 채택해 DB 통신을 구현하고 있습니다.
SQL을 직접 작성할 수 있어서 복잡한 쿼리 작성이 번거롭지 않고
쿼리는 모두 XML에 모아두기 때문에 유지보수나 협업에도 용이하고
무엇보다 값 바인딩 #{} 을 통해 입력값을 쿼리에 삽입하기 때문에 SQL 인젝션을 방지할 수 있습니다.
물론 sql_query()를 이용해 직접 쿼리를 작성할 때에 비하면
동적 SQL태그를 이용해 쿼리를 조립해야 하는 정도의 차이는 있습니다.
이번 포스팅에선 이런 MyBatis를 이용해 실제 기능을 구현할 때
어떤 식으로 쿼리를 작성하고 데이터를 다루는지에 대해 정리해 보겠습니다.
🎷 ORDER BY절 동적으로 안전하게 쓰기
순서를 따지자면 WHERE절 추가에 대해서 먼저 이야기 해야 할 것 같지만
값 바인딩 이야기가 나온 김에 ORDER BY절에 대해 먼저 이야기 해보겠습니다.

흔히 쇼핑몰이던 게시판이던 사용자가 원하는 순서로 목록을 볼 수 있도록
정렬 방식을 선택하는 기능을 제공하곤 합니다.
이 경우 선택된 조건에 따라 ORDER BY절이 동적으로 바뀌어야 하는데
service에서 ORDER BY절을 만들어 mapper로 넘기면
값이 바인딩 되면서 SQL문법이 아닌 문자열로 인식되어 오류가 발생합니다.

위 에러 메시지는 제가 service에서 order_sql이라는 파라미터를 만들고
ORDER BY 상품번호 DESC 라는 문자를 담아서 넘긴 뒤 쿼리 맨 마지막에 바인딩해줘서 생긴 에러입니다.
제 의도는 서비스에서 작성한 ORDER BY절이 본래의 기능대로 쓰이는 것이었지만
제가 넘긴 order_sql 파라미터는 문자열로 인식되어 최종적으로 아래와 같은 쿼리가 실행된 것입니다.
SELECT *
FROM 상품테이블
WHERE 조건
'ORDER BY 상품번호 DESC'
MyBatis에서 동적으로 쿼리를 작성하려면 동적 SQL 태그를 활용해야 합니다.
ORDER BY절은 맞는 조건에 따라 하나만 추가되어야 하고
조건이 안 맞아도 기본적으로 적용되어야 하는 정렬순서(=기본값defult)가 있으므로
switch문과 유사한 <choose>, <when>, <otherwise>를 쓰는 것이 가장 적합합니다.
사용 예시는 다음과 같습니다.
SELECT *
FROM 상품테이블
WHERE 조건
<choose>
<when test="type == 상품명순">ORDER BY 상품명 ASC</when>
<when test="type == 낮은가격순">ORDER BY 가격 ASC</when>
<otherwise>ORDER BY 상품번호 DESC</otherwise>
</choose>
위 방식대로 쿼리를 작성하면
상품명순으로 정렬을 선택했을 때 상품의 이름을 기준으로 정렬하고
낮은가격순으로 정렬을 선택했을 때 상품의 가격이 낮은 순으로 정렬하고
그 외엔 최근 상품이 먼저 보일 수 있도록 쿼리를 동적으로 작성할 수 있습니다.
🎺 조건에 따라 WHERE절 추가
쇼핑몰 검색 기능을 보면 검색어 외에도 다양한 조건을 추가해서
원하는 제품을 빠르게 찾을 수 있는 검색 기능이 존재합니다.

이번에는 조건이 맞을때만 WHERE절에 해당 조건이 추가되어야 하므로
if문과 유사한 <if>를 쓰는 것이 가장 적합합니다.
최대한 간단하게 제품의 색상, 가격 정도만 추가해서 예시를 보여드리겠습니다.
SELECT *
FROM 상품테이블
WHERE 1 = 1
<if test="색상값 != null">AND 색상 = #{색상값}</if>
<if test="가격값 != null">AND 가격 <= #{가격값}</if>
ORDER BY 상품번호 DESC
WHERE절에 1=1을 추가하는 이유는 검색 조건이 아무것도 없을 경우를 대비한 것입니다.
아무것도 없이 WHERE만 있으면 문법 오류가 발생하지만 항상 참인 1=1이 있으면
검색 조건이 없을 때 문법 오류를 방지하면서 동시에 조건에 따라 추가되는 문법에 영향을 주지 않습니다.
특이한 점이 하나 더 있는데 XML이나 HTML 같은 마크업 문서는
< 기호를 태그의 시작으로 인식하기 때문에 <= 같은 등기호를 곧장 사용할 순 없습니다.
이때는 엔티티로 변환해서 < 가 문자 그대로 출력될 수 있도록
< 로 바꿔서 쿼리를 작성해줘야 합니다.
이걸 몰라서 어디에 문법을 틀렸는지 한참 찾았어요...🥲
이것 외에도 컬렉션(List, Array)을 반복해 쿼리를 추가해주는 <foreach>가 있고
UPDATE문에서 갱신할 컬럼을 동적으로 추가할 때 사용하는 <set>도 있습니다.
📯 날짜 포맷 추가
이번건 오라클 세션 타임존과 자바(저희 프로젝트는 스프링 부트) 서버의 타임존을 맞추면 발생하지 않는 문제지만
팀 프로젝트를 하게되면 모든 작업자의 로컬PC 타임존을 일일이 손보기 번거로워서 추가한 날짜 포맷 추가입니다.
이 문제를 간단히 설명하자면 오라클 세션 타임존은 Asia/Seoul으로 설정된 반면
자바 서버 타임존은 UTC기준 ISO 8601포맷으로 설정되어 발생했습니다.

해결 방법은 서버와 DB의 타임존을 일치시키거나
API 응답 반환 전에 DateTimeFormatter로 직접 변환해 주거나
// KST 포맷터
private static final DateTimeFormatter KST_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.of("Asia/Seoul"));
// 날짜 필드 목록 (필요 시 추가 가능)
private static final List<String> DATE_FIELDS = Arrays.asList(
"regdate"
);
public List<Map<String, Object>> listProduct(param){
List<Map<String, Object>> resultList = dao.listProduct(param);
for (Map<String, Object> row : resultList) {
for (String field : DATE_FIELDS) {
Object value = row.get(field);
if (value instanceof Timestamp ts) {
row.put(field, KST_FORMATTER.format(ts.toInstant()));
}
}
}
return resultList;
}
DTO를 만들어 날짜 필드에 @JsonFormat 어노테이션으로 포멧을 지정해주면 됩니다.
public class ProductDTO {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private Date regdate;
}
팀 프로젝트에선 개발환경 설정 시간을 최소화 하고자
DateTimeFormatter와 @JaonFormat을 이용하였습니다.
🔥 결론
처음으로 다뤄보는 기술스택이라 그런지 생각보다 다양한 문제가 발생하는 것 같습니다.
비즈니스 로직만 문제인 줄 알았는데 이런 자잘한 문제가 계속 나오면
기간을 맞추기 위해 밤샘을 해야 할 수도 있겠네요...

음... 그치만 벌써 걱정해봐야 소용 없겠죠!?
최대한 밤샘을 안 할 수 있도록
모든 코드를 최대한 재사용 가능한 형태로 작성해서
빠르게 기능을 복제하는 방법을 써야겠습니다!

'TIL > TIL 챌린지' 카테고리의 다른 글
| [현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 18차 - MSA (0) | 2025.11.18 |
|---|---|
| [현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 17차 - 오라클과 MySQL의 차이 (0) | 2025.11.11 |
| [현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 16차 - 깃허브 그룹 프로젝트(이슈 생성, 커밋, 풀 리퀘스트) (0) | 2025.11.05 |
| [현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 15차 - 스프링 부트 프로젝트에 React 뷰 적용 방법 (0) | 2025.10.29 |
| [현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 14차 - 비동기 통신 (0) | 2025.10.21 |
