iBatis에서의 습관을 버리고 myBatis로 옮겨올  시기가 된듯합니다.

Spring3.2에서도 iBatis는 deprecated 되어 더이상 myBatis로 옮기는데 주저할수는 없는 현실이 되어버렸습니다.

 

오늘은 예전 iBatis에서 iterate 문으로 사용했던 부분을 forEach로 변경하는 테스트를 시험삼아 해보았습니다.

myBatis를 쓰면서 느끼는것은 이제 xml문법스타일은 버려도 되겠구나 하는 것입니다.

 

foreach

동적 SQL 에서 공통적으로 필요한 것은 collection 에 대해 반복처리를 하는 것이다. 종종 IN 조건을 사용하게 된다. 예를 들면,

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 요소는 매우 강력하고 collection 을 명시하는 것을 허용한다. 요소 내부에서 사용할 수 있는 item, index 두가지 변수를 선언한다. 이 요소는 또한 열고 닫는 문자열로 명시할 수 있고 반복간에 둘 수 있는 구분자도 추가할 수 있다.

참고 파라미터 객체로 MyBatis 에 List 인스턴스나 배열을 전달 할 수 있다. 그렇게 하면 MyBatis 는 Map 으로 자동으로 감싸고 이름을 키로 사용한다. List 인스턴스는 “list” 를 키로 사용하고, 배열 인스턴스는 “array” 를 키로 사용한다. 

 

 

 

위의 내용은 myBatis 한국어 사이트 번역판에 설명되어진 것입니다만 사실 제대로된 설명이라고 하기엔, 특히나 개발감각 없는 저 같은 개발자에겐 한방에 와닿지 않습니다.

 

그저 따라하기 예제 샘플이 최고입니다.

 

User.java

public class User {

  private int userIdx;

  private String userName;

  private Integer [] userIdxArray;

  private List<Company> compList;

  public ... 이하는 getter, setter 메소드이므로 생략.

 

 

}

 

 

1. 배열을 파라메터로 전달하여 foreach문을 사용하는 예

UserMapper.xml

<select id="getUserListByUserIdxArray" resultMap="User-Result" parameterType="User">

  SELECT

           USER_IDX as "userIdx"

, USER_NAME as "userName"

  FROM USER

  <trim prefix="WHERE" prefixOverrides="AND|OR">
         <if test="userIdxArray  != null">
           AND USER_IDX IN (
              <foreach collection="userIdxArray" item="a" separator=",">#{a}</foreach>
          )
        </if>
 </trim>

 

</select>

 

 

WHERE 문이 오는 곳에 위치한 trim  도 iBatis에는 없던 myBatis에서 새로이 추가된 구문입니다. 보통 꼼수(?)로

WHERE  1=1

위와 같이 사용하던 부분인데, 이러한 꼼수를 쓰지말라고 제공하는 구문이라고 생각합니다.

보통 if 문을 사용하는 곳 앞부분에 추가하는데, 이전 iBatis에서는 <dynamic...> 이란 구문이 아마 이에 해당했던 걸로 기억합니다.

 

- collection : 말 그대로 collection 객체들이 오는 곳입니다.

- item : Collection 객체들의 instance? 라고 할수 있습니다.  jstl에서의 forEach를 예로 들면 "var"에 해당하는 놈이라고 할수 있겠네요. 처음엔 글자가 jstl의 items와 비슷해서 같은걸로 이해했었습니다.

- separator : 구분자.

 

배열이기 때문에 이 alias를 그대로 넣어주면 쿼리에 적용됩니다. 

 

2. ArrayList를 파라메터로 전달하여 foreach문을 사용하는 예.

 

...

      <if test="compList  != null">
           AND COMP_NAME IN (
              <foreach collection="compList" item="a" separator=",">#{a.compName}</foreach>
          )
      </if>
... 

 

 

배열 사용법과 별 차이는 없습니다.

 

이전 iBatis를 사용할때는 array 객체가 필요할때는 이상하게 꼭 hashmap 객체에 넣어서 보냈던 기억이 있습니다. POJO 에 넣어서 보내면 뭔가 모르게 오류가 나서 잘 안되던 기억이 있었는데(어쩌면 사용법을 틀려서 그런걸수도 있겠지만), myBatis 에 와서는 그런부분이 없습니다. 좀더 명확해졌다고 해야할까요?

 

오늘은 myBatis의 foreach문을 통한 반복문 사용법을 테스트 해보았습니다.

 

 

2019.01.09 추가

 

iBatis를 여전히 사용하는 와중에 자꾸만 헷갈리는터라 추가로 남깁니다.

 

iBatis의 동적쿼리 forEach중에서 제일 헷갈리면서 자주 범하는 실수입니다.

 

1. ParameterClass가 Object 임. Map으로 넘기는 부분과 다를까 싶지만 동일함.

2. Object 내에 ArrayList<Map<String, Object>> 를  get/set 하도록 추가.

3. 아래의 예제 쿼리는 여러개의 테이블에서 통계값을 가져와 UNION ALL 하는 쿼리이다.

 

BBsVO 내부 구조 및 파라메터에는 아래와 같이 데이터가 추가된다.

 

public class BbsStatsDefaultVO extends ComDefaultVO implements Serializable {
   private String searchCategoryId;
   private String searchStartDate;
   private String searchEndDate;
   private List<Map<String,Object>> statsParamList;
...
}

 

 

List<Map<String, Object>> bbsInfoList = ...from DB결과
BbsVO  bbsVO = new BbsVO();

 

bbsVO.setSearchStartDate("2018-01-01");

bbsVO.setSearchEndDate("2018-10-31");
bbsVO.setStatsParamList(bbsInfoList);

 

 

 

<select id="selectBbsList" parameterClass="BbsVO" resultClass="hashmap">
      SELECT BBS_CNT
       , ANSWER_CNT
       , READ_CNT
       , ATCHMNFL_CNT
       , COMMUNITY_ID
   FROM
   <iterate property="statsParamList" open="(" close=")" conjunction="UNION ALL">
    <isEqual property="statsParamList[].communityType" compareValue="BBS">
     (
      SELECT NVL(COUNT(a.BBS_ID),0) BBS_CNT
       , NVL(SUM(a.ANSWER_COUNT),0) ANSWER_CNT
       , NVL(SUM(a.READ_COUNT),0) READ_CNT
       , NVL(COUNT(b.ATCHMNFL_ID),0) ATCHMNFL_CNT
       , '$statsParamList[].communityId$' AS COMMUNITY_ID
          FROM  $statsParamList[].bbsTableNm$ a
           , $statsParamList[].bbsAttchTableNm$ b
          WHERE a.BBS_ID = b.BBS_ID(+)
           <isNotEmpty property="searchStartDate">
            <isNotEmpty property="searchEndDate">
            AND a.REG_DT BETWEEN REPLACE(#searchStartDate#, '-', '')
            AND REPLACE(#searchEndDate#,'-','') ||'235959'
            </isNotEmpty>
           </isNotEmpty>
        )
    </isEqual>
      </iterate>
   </select>

 

 

 

추가로 참고할 블로그 소개 :  http://fogs0.blogspot.com/2012/04/ibatis-iterate-in-isequal.html

 

끝.

 

 

 

 



iBatis에서 myBatis로 넘어오면서 selectKey에서 last_insert_id 를 가져오는 방식에 변화가 생겼습니다.

기존 iBatis 방식으로 넘겨받으면 1만 넘어오는데, 이는 increase된 selectKey 가 아닌 inserted row count 입니다.
myBatis에서는 insert시 전달받은 parameter class에 keyProperty로 정의한 값 또는 object로 자동으로 inject 됩니다.

이전 iBatis에서도 그랬는지는 기억이 가물가물하네요. 암튼 경험한 부분을 명시합니다.

 

간단한 예를 들어서 설명합니다.

 

<insert id="insertUser" parameterMap="map">
    INSERT INTO USER (
      NAME, ID, PW 

    )VALUES(
        #{NAME}, #{ID}, #{PW}
    )
<selectKey keyProperty="USER_IDX" resultClass="java.lang.Integer">
    SELECT LAST_INSERT_ID()
</selectKey>
</insert>

 

int insertRowCnt = userMapper.insertUser(map);
int userIdx = map.get("USER_IDX");


다시말해서 위와 같은 방법으로 가져와야 합니다.

 

이전 iBatis에서는 아래와 같이 요청해도 last_insert_id를 리턴해줬던것 같았는데...

int userIdx = userMapper.insertUser(map);

 

만약 map 대신 POJO클래스(UserObject 같은)를 사용한다면 pojo 형태로 keyProperty를 정의합니다.

 

<insert id="insertUser" parameterType="UserObject">
INSERT INTO USER (
  NAME,  ID, PW
)VALUES(
  #{name},  #{id}, #{pw}
)
<selectKey keyProperty="userIdx" resultClass="java.lang.Integer">
    SELECT LAST_INSERT_ID()
</selectKey>
</insert>


int insertRowCnt = userMapper.insertUser(userObject);
int userIdx = userObject.getUserIdx(); <==

 

결론 : 변수로 넘겨준 class에 자동으로 injection된다는 것.

차라리 iBatis를  경험하지 않았더라면 이해가 더 빨랐을 수도 있을것 같습니다. 그저 비슷하려니 하는 막연함이 부른 삽질.

아니면 이전부터 있었던 동일한  기능이었는데, 제대로 알지 못하고 사용했는지도 모르겠습니다. 집에가서 iBatis 책을 찾아봐야겠습니다.

 

이글을 읽는 분들은 부디 저처럼 삽질하지 마시길 바랍니다. 화이팅~

 

 



일반적인 쿼리를 사용할때, 즉 Select, Update, Insert, Delete 등은 #value# 과 $value$ 어느것을 써도 오류가 발생하지 않는다.
하지만 RENAME TABLE tableName명 등과 같은 쿼리문에서는 preparedStatement가 작동하는 #value#를 사용할 수 없다.
당연(?)한 얘기일수도 있겠으나 사용시 주의할것. 테스트 한번만 해보면 바로 알수 있는 팁이긴하다.

RENAME TABLE oriTableName to newTableName_$month$ ;

으로 하면 된다.


« PREV : 1 : 2 : 3 : 4 : 5 : NEXT »