코딩짜는 일상

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 19차 - 프로젝트에서 MyBatis 사용하기 (feat.Oracle) 본문

TIL/TIL 챌린지

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 19차 - 프로젝트에서 MyBatis 사용하기 (feat.Oracle)

Remily 2025. 11. 25. 23:25
반응형

📚 서론

과거 PHP를 쓸 때는 sql_query() 함수를 사용해 DB 통신을 구현했었습니다.

 

쿼리를 문자열로 작성해 param으로 제공하면 되는 간단한 방식이기에

if문을 이용한 문자열 편집으로 간단하게 쿼리를 조립할 수 있었지만...

 

사실 이건 인젝션 공격에 취약하기 때문에 그리 현명한 선택은 아니었습니다.😥

 

물론 자체적으로 입력값에 UNION을 차단하는 기능을 추가해 방어했지만 여전히 위험요소가 있었죠...🥲

PDO를 왜 안 쓰셨는지는 모르겠지만...?

 

 

현재 팀 프로젝트에선 MyBatis를 채택해 DB 통신을 구현하고 있습니다.

 

SQL을 직접 작성할 수 있어서 복잡한 쿼리 작성이 번거롭지 않고

쿼리는 모두 XML에 모아두기 때문에 유지보수나 협업에도 용이하고

무엇보다 값 바인딩 #{} 을 통해 입력값을 쿼리에 삽입하기 때문에 SQL 인젝션을 방지할 수 있습니다.

 

물론 sql_query()를 이용해 직접 쿼리를 작성할 때에 비하면

동적 SQL태그를 이용해 쿼리를 조립해야 하는 정도의 차이는 있습니다.

 

 

이번 포스팅에선 이런 MyBatis를 이용해 실제 기능을 구현할 때

어떤 식으로 쿼리를 작성하고 데이터를 다루는지에 대해 정리해 보겠습니다.

 

 

 

 

 

🎷 ORDER BY절 동적으로 안전하게 쓰기

순서를 따지자면 WHERE절 추가에 대해서 먼저 이야기 해야 할 것 같지만

값 바인딩 이야기가 나온 김에 ORDER BY절에 대해 먼저 이야기 해보겠습니다.

 

예시로 가져온 네이버 가격비교의 제품목록 정렬

 

흔히 쇼핑몰이던 게시판이던 사용자가 원하는 순서로 목록을 볼 수 있도록

정렬 방식을 선택하는 기능을 제공하곤 합니다.

 

이 경우 선택된 조건에 따라 ORDER BY절이 동적으로 바뀌어야 하는데

service에서 ORDER BY절을 만들어 mapper로 넘기면

값이 바인딩 되면서 SQL문법이 아닌 문자열로 인식되어 오류가 발생합니다.

ORA-00933 에러 발생

 

위 에러 메시지는 제가 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 가격 &lt;= #{가격값}</if>
ORDER BY 상품번호 DESC

 

WHERE절에 1=1을 추가하는 이유는 검색 조건이 아무것도 없을 경우를 대비한 것입니다.

 

아무것도 없이 WHERE만 있으면 문법 오류가 발생하지만 항상 참인 1=1이 있으면

검색 조건이 없을 때 문법 오류를 방지하면서 동시에 조건에 따라 추가되는 문법에 영향을 주지 않습니다.

 

 

특이한 점이 하나 더 있는데 XML이나 HTML 같은 마크업 문서

< 기호를 태그의 시작으로 인식하기 때문에 <= 같은 등기호를 곧장 사용할 순 없습니다.

 

이때는 엔티티로 변환해서 < 가 문자 그대로 출력될 수 있도록

&lt; 로 바꿔서 쿼리를 작성해줘야 합니다.

이걸 몰라서 어디에 문법을 틀렸는지 한참 찾았어요...🥲

 

 

이것 외에도 컬렉션(List, Array)을 반복해 쿼리를 추가해주는 <foreach>가 있고

UPDATE문에서 갱신할 컬럼을 동적으로 추가할 때 사용하는 <set>도 있습니다.

 

 

 

 

 

📯 날짜 포맷 추가

이번건 오라클 세션 타임존과 자바(저희 프로젝트는 스프링 부트) 서버의 타임존을 맞추면 발생하지 않는 문제지만

팀 프로젝트를 하게되면 모든 작업자의 로컬PC 타임존을 일일이 손보기 번거로워서 추가한 날짜 포맷 추가입니다.

 

 

이 문제를 간단히 설명하자면 오라클 세션 타임존은 Asia/Seoul으로 설정된 반면

자바 서버 타임존은 UTC기준 ISO 8601포맷으로 설정되어 발생했습니다.

 

문제가 발생한 날짜값. 원본값은 2025-11-05 00:00:00.

 

해결 방법은 서버와 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을 이용하였습니다.

 

 

 

 

 

🔥 결론

처음으로 다뤄보는 기술스택이라 그런지 생각보다 다양한 문제가 발생하는 것 같습니다.

 

비즈니스 로직만 문제인 줄 알았는데 이런 자잘한 문제가 계속 나오면

기간을 맞추기 위해 밤샘을 해야 할 수도 있겠네요...

내가 웃는게 웃는게 아니야~

 

음... 그치만 벌써 걱정해봐야 소용 없겠죠!?

 

최대한 밤샘을 안 할 수 있도록

모든 코드를 최대한 재사용 가능한 형태로 작성해서

빠르게 기능을 복제하는 방법을 써야겠습니다!

3년 경력이 뭔지 보여주겠어...!

 

 

반응형