[멋쟁이사자처럼부트캠프] 진짜 좋은거 같은 MyBatis

[멋쟁이사자처럼부트캠프] 진짜 좋은거 같은 MyBatis
©Pokémon. ©Nintendo/Creatures Inc./GAME FREAK inc.

오늘字

아무리 생각해봐도 MyBatis가 최고인 것 같다. 하지만 어째서 Orcle은 JDBC을 더 개선하지 않을까? 그래서 오늘 배운 자원관리 개념과 XML 문법, 동적 SQL을 정리해보겠습니다.


Human Error

본격적인 내용에 앞서, 기억해야될 실수 하나...언제나 컴퓨터는 죄가 없습니다..

Trouble

List<EmpDTO> list6 = session.selectList("com.config.EmpMapper.selectif2", dto2);

EmpDTO dto2 = new EmpDTO();

dto2.setJob("SALESMAN");
dto2.setSal(800);

프로그래밍의 [순차적 실행(Sequential Execution)] 원칙으로 인해 먼저 호출된 쿼리(무슨 생각을 하고 있었길래..)가 값 없이 실행되었습니다. 반드시 변수를 먼저 선언하고 설정한 뒤에 쿼리를 호출해야 합니다ㅠㅠ

EmpDTO dto2 = new EmpDTO();

// 쿼리 실행 '전'에 조건을 먼저 설정
dto2.setJob("SALESMAN");
dto2.setSal(800);

// 설정된 조건이 포함된 DTO를 전달하며 쿼리 호출
List<EmpDTO> list6 = session.selectList("com.config.EmpMapper.selectif2", dto2);

자원관리

JDBC를 사용할 때는 각 메소드마다 커넥션을 만들고 close() 하는 방식이 단순히 커넥션을 초기화하는 과정으로만 생각했습니다. 하지만 아래 사진처럼 "생성→사용→반납" 패턴입니다. 정말 비효율적으로 보이기에,

MyBatis에서는 성능 이슈를 해결하기 위해 커넥션 풀(Connection Pool) 방식을 도입했습니다.

  • 커넥션 풀이란? 네트워크 경로 탐색 등 커넥션 생성에 드는 큰 비용을 줄이기 위해, 커넥션을 미리 여러 개 만들어두고 필요할 때 빌려주고 사용 후 반환받는 방식입니다. 마치 VPN처럼 전용 통로를 열어두고 운용하는 방식과 비슷합니다.
  • close()는 필수! 풀(Pool) 환경에서 close()는 실제 연결을 끊는 것이 아니라 풀에 커넥션을 반납하는 것을 의미합니다. 통로를 닫지 않고 놔두면 자원이 계속 고갈되므로, 누락 시 다른 요청이 커넥션을 받지 못해 장애가 발생할 수 있습니다. 따라서 세션의 close()는 필수입니다.

XML 문법

MyBatis Mapper의 XML 파일에서는 select, insert, update, delete 태그를 사용하며, 구문을 식별하기 위한 id 속성이 필수입니다.

속성 설명
id 구문을 찾기 위해 사용될 수 있는 네임스페이스 내 유일한 구분자
parameterType 구문에 전달될 파라미터의 패키지 경로를 포함한 전체 클래스명이나 별칭
resultType 구문에 의해 리턴되는 기대 타입의 패키지 경로를 포함한 전체 클래스명이나 별칭. collection인 경우 collection 타입 자체가 아닌 collection 이 포함된 타입이 될 수 있다. resultType이나 resultMap을 사용하라.

바인딩 변수

기존 JDBC에서 파라미터를 넘길 때 사용하던 ? 기호는 MyBatis에서 #{}로 바뀝니다. #{} 문법을 사용하면 MyBatis가 내부적으로 PreparedStatement 파라미터를 생성하여 안전하게 값을 세팅해 줍니다.

${} 문자열 치환도 있지만, 사용을 지양하고 #{}를 사용하는 것이 안전합니다.


CRUD

작업 호출 메소드 리턴값 의미 트랜잭션
Insert session.insert(id, param) 저장된 행(row) 수입니다. commit() 필요합니다.
Update session.update(id, param) 수정된 행(row) 수입니다. commit() 필요합니다.
Delete session.delete(id, param) 삭제된 행(row) 수입니다. commit() 필요합니다.

MyBatis에서 DML(데이터 조작어)인 Insert, Update, Delete를 실행하면 리턴되는 값은 수정된 행(row)의 수(int)입니다.

  • 자동 커밋 비활성화: 기존 JDBC와 달리 MyBatis는 기본적으로 오토커밋(Auto-commit)이 비활성화되어 있습니다.
  • 트랜잭션 처리: 따라서 session.insert(), session.update(), session.delete()를 호출하여 성공 메시지를 확인했더라도, 반드시 session.commit()을 호출해야 실제 DB에 반영됩니다. 실패 시에는 session.rollback()으로 트랜잭션을 되돌려야 합니다.

비교 연산자

Mapper SQL 내부에서 <> 같은 비교 연산자를 그대로 사용하면, XML 파서가 이를 XML 태그의 시작/종료로 오인하여 에러가 발생합니다.

이를 회피하는 방법은 두 가지입니다:

  1. 엔티티 치환: <&lt;, >&gt;로 바꾸어 씁니다.
<select id="id" resultType="DTO" parameterType="type" > 
  select *
  from tableName
  where columnName &gt; #{columnName}
</select>
  1. CDATA 활용: 쿼리 전체 또는 해당 부분을 <![CDATA[ ... ]]>로 감싸서, 파서가 해석하지 않고 순수한 문자 데이터로 처리하도록 만듭니다. (실무에서 널리 쓰이는 방식입니다.)
<select id="id" resultType="DTO" parameterType="type" > 
  <![CDATA[select * from tableName where columnName < #{columnName}]]>
</select>

동적 SQL (Dynamic SQL)

foreach

<foreach> 태그는 List나 배열 같은 컬렉션을 반복하여 SQL 조각을 생성합니다.

  • 멀티 Insert:
    • VALUES 뒤의 ( ... ) 튜플을 반복 생성하며 separator=","로 쉼표를 자동 삽입합니다.
<insert id="insertMulti" parameterType="arraylist">
  INSERT INTO EMP (EMPNO, ENAME, SAL)
  VALUES
  <foreach collection="list" item="dto" separator=",">
    (#{dto.empno}, #{dto.ename}, #{dto.sal})
  </foreach>
</insert>
  • 멀티 Delete / Select(IN):
    • IN 조건 절에 값을 나열할 때 사용합니다. open="(", close=")" 속성을 활용하면 반복 전후에 괄호를 자동으로 생성해주어 문법 오류를 방지할 수 있습니다.
<!-- 멀티 delete-->
  <delete id="deleteMulti" parameterType="arraylist">
    delete from EMP
      <where>
        <foreach item="num"  separator="," collection="list" open="empno IN (" close=")">
          #{num}
        </foreach>
	  </where>
  </delete>
  
<!-- 멀티 select-->
  <select id="selectMulti" parameterType="arraylist" resultType="EmpDTO">
    select empno, ename, sal from EMP 
      <where>
        <foreach collection="list" item="ename" separator="," open="ename IN(" close=")">
          #{ename}
        </foreach>
      </where>
  </select>

if 와 where

  • 가장 빈번하게 쓰이는 <if> 태그는 조건에 따라 SQL 조각을 덧붙입니다. 여러 <if> 조건을 조합할 때 <where> 태그로 감싸주면 아주 유용합니다. 조건이 하나도 만족하지 않으면 WHERE 절을 아예 만들지 않고, 조건이 생성될 때 문장 시작에 불필요한 ANDOR가 있다면 알아서 제거해 줍니다.
<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

set으로 조건부 Update

  • 동적 Update 구문에서 수정할 컬럼이 조건에 따라 달라질 때 <set> 태그를 사용합니다. <set> 태그 안에서 <if>를 통해 컬럼을 업데이트하도록 구성하면, 마지막에 남는 불필요한 쉼표(,)를 자동으로 제거해주어 SQL 문법 오류를 완벽히 막아줍니다.
<select id="updateif" parameterType="EmpDTO" resultType="EmpDTO">
  update EMP
    <set>
      <if test="empno != 0">
        empno=#{empno}, 
      </if>
      <if test="ename != null">
        ename  = #{ename}, 
      </if>
      <if test="sal != 0">
        sal = #{sal}
      </if>
    </set>
  where empno=#{empno}
</select>

choose, when, otherwise

  • 자바의 switch-case 문과 유사한 분기 처리 구조입니다. 여러 조건 중 최초로 참(true)이 되는 <when> 블록 하나만 택하며, 어떤 조건도 맞지 않으면 <otherwise> 블록의 구문을 적용합니다.
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

출처
[Spring] Mybatis의 특징
a. JDBC의 예외처리(try/catch/finally)를 사용할 필요가 없다.b. 기본적으로 PreparedStatement 구조이다.c. Select 결과를 객체(custom Dto, map 등)에 매핑한다.d. 트랜잭션을 관리할 수 있다.1\. 객체를 생성 ->
마이바티스 3 | 소개 – mybatis