Pages

레이블이 [ MyBatis ]인 게시물을 표시합니다. 모든 게시물 표시
레이블이 [ MyBatis ]인 게시물을 표시합니다. 모든 게시물 표시

2015년 8월 18일 화요일

[ ORACLE ] 저장형 함수를 사용할 때 튜닝방법

-- 1. 수행쿼리 SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM(GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME
;

-- 2. ’GET_AVG_STOCK’ Stored_FunctionCREATE OR REPLACE FUNCTION GET_AVG_STOCK(p_start_date IN DATE,
                                         p_end_date   IN DATE,
                                         p_product_cd IN VARCHAR2) RETURN  NUMBER 
AS
    v_ret_val NUMBER;
    
    BEGIN
        SELECT SUM(STOCK_QTY) / (V_START_DATE . V_END_DATE))
        INTO   v_ret_val
        FROM   PROD_STOCK
        WHERE  PRODUCT_CD = p_product_cd
        AND    STOCK_DATE BETWEEN p_start_date 
                          AND     p_end_date;                          
    EXCEPTION 
        WHEN OTHERS THEN
            v_ret_val := 0;
    END GET_AVG_STOCK;
    
    RETURN RET_VAL;
END GET_AVG_STOCK;


대략 1,000만건의 데이터가 들어있는 PRODUCT 테이블에서 CATEGORY_CD가 20인 10만건의 데이터를 GROUP BY하여 SUM값을 가져올 때 내부적으로 GET_AVG_STOCK 펑션을 10만번 Call하기 때문에 현재 응답속도가 느린데 이를 어떻게 개선할까?

정답부터 말하면 Function을 스칼라 서브쿼리로 하는 것입니다. ( Oracle 10g 이상 )
1. - Function을 Scalar Subquery로 사용하면 동일한 OUTPUT을 One-Buffer가 아닌 Multi-Buffer에 저장함.

2. - Oracle 10.2 버전부터 사용 가능 
   - Function을 Deterministic 으로 선언하면 같은 INPUT값에 대해서는 OUTPUT도 동일한 결과로 인식하여 FUNCTION Call을 안함 

3. - Scalar Subquery를 이용하거나, Deterministc형태로 함수를 생성해준다면, 사용자 정의 함수에 대한 입력값이 적을 시에 아주 뛰어난 효율을 보이게 된다.

* 여기서 중요하게 봐야할 포인트가 바로 스칼라 서브쿼리나 DETERMINISTIC Function 모두 Multi-Buffer에 저장을 하여 같은 값들에 대해서는 ’Function Call’을 안한다는 것입니다.

9i까지만 해도 실행계획에서 FILTER처리가 되면 One-Buffer 효과를 볼 수 있어서 재사용을 할 수 있었는데 10g R2에서는 One-Buffer를 지원안하고 Multi-Buffer를 지원하는 것으로 확인되었습니다.

’Oracle 10g’에서 새롭게 바뀐점은 바로 ’Fast Dual’ Optimizer Plan을 사용한다는 것입니다


SELECT TEST INTO FROM DUAL;
DUAL은 호출될 때마다 새로운 논리적 I/O(Buffer I/O)를 생성합니다. 이 기능은 매우 유용한게 활용욉니다. DUAL은 자주 사용되지만 오라클 코드는 DUAL을 특수한 형태의 테이블로서 취급하며, 일반적인 튜닝 방법은 적용할 수 없다는 문제가 있습니다.
Oracle Database 10g 에서라면 이에 대한 걱정을 할 필요가 없습니다. DUAL이 특수한 테이블이기 때문에, 논리적 I/O를 나타내는 consistent gets의 값도 줄이들며, optimization plan도 다른 형태로 나타납니다.



















Oracle 9i에서 사용되는 DUAL의 FAST TABLE SCAN 대신, 10g 에서는 FAST DUAL optimization plan을 사용하고 있다는 점을 주목하시기 바랍니다. 이러한 기능 개선을 통해 DUAL 테이블을 자주 사용하는 애플리케이션의 연속적인 읽기 작업 성능이 대폭적으로 향상되었습니다.
-- 1. 수정 전SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM(GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME

-- 2. 수정 후(스칼라 서브쿼리 적용)SELECT PRODUCT_CD,
       PRODUCT_NAME,
       SUM((SELECT GET_AVG_STOCK(PRODUCT_CD, :B1, :B2)
            FROM   DUAL)) AVG_STOCK
FROM   PRODUCT
WHERE  CATEGORY_CD = ’20’
GROUP BY PRODUCT_CD,
         PRODUCT_NAME
;

Execution Plan
--------------------------------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS  
   1    0   FAST DUAL    2    0   SORT (GROUP BY)
   3    2     TABLE ACCESS (BY INDEX ROWID) OF ’PRODUCT’ (TABLE)  
   4    3       INDEX (RANGE SCAN) OF ’PRODUCT_N1’ (INDEX)    

1) 패키지의 OUTPUT내용을 Multi-Buffer에 저장하여 같은값들에 대해서는 ’Function Call’을 안함
2) ’Function’에 대해서 ’FAST DUAL’로 연산하기 때문에 빠른 응답속도를 보임

실제 ’Function’에서 내부적으로 돌아가는 부분을 Trace보면 다음과 같습니다.
-- 1. 수정 전Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute 100000   18.000       35.000          0          0          0          0
Fetch   100000   98.000      195.000       2317    8295211         40        500------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total   400001  116.000      230.000       2317    8295211         40        500

-- 2. 수정 후Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.000          0          0          0          0
Execute   1000    4.000        5.000          0          0          0          0
Fetch     1000   17.000       18.000        250     480000          0        500------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total     2001   21.000       23.000        250     480000          0        500

여기선 가상으로 Count를 1000으로 잡았는데요. 실제 업무에서도 같은 값이 계속 발생하면 위에서 보신것처럼 Count를 줄여서 응답시간을 획기적으로 줄일 수 있습니다.

2014년 6월 10일 화요일

[MyBatis] MyBatis 동적 SQL

동적 SQL

MyBatis 의 가장 강력한 기능 중 하나는 동적 SQL 기능이다. JDBC 나 다른 유사한 프레임워크를 사용해본 경험이 있다면, 동적으로 SQL 을 구성하는 것이 얼마나 힘든 작업인지 이해할 것이다. 간혹 공백이나 콤마를 붙이는 것을 잊어본 적도 있을 것이다. 동적 SQL 은 그만큼 어려운 것이다.
동적 SQL 을 사용하는 것은 결코 파티가 될 수 없을 것이다. MyBatis 는 강력한 동적 SQL 언어로 이 상황은 개선한다.
동적 SQL 요소들은 JSTL 이나 XML 기반의 텍스트 프로세서를 사용해 본 사람에게는 친숙할 것이다. MyBatis 의 이전 버전에서는, 알고 이해해야 할 요소가 많았다. MyBatis 3 에서는 이를 크게 개선했고 실제 사용해야 할 요소가 반 이하로 줄었다. MyBatis 다른 요소의 사용을 최대한 제거하기 위해 OGNL 기반의 표현식을 가져왔다.
  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

동적 SQL 에서 가장 공통적으로 사용되는 것으로 where 의 일부로 포함될 수 있다. 예를 들면:
<select id="findActiveBlogWithTitleLike" 
     resultType="Blog">
  SELECT * FROM BLOG 
  WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
</select>
이 구문은 선택적으로 문자열 검색 기능을 제공할 것이다. 만약에 title 값이 없다면, 모든 active 상태의 Blog 가 리턴될 것이다. 하지만 title 값이 있다면, 그 값과 비슷한 데이터를 찾게 될 것이다.
title 과 author 을 사용하여 검색하고 싶다면? 먼저, 의미가 좀더 잘 전달되도록 구문의 이름을 변경할 것이다. 그리고 다른 조건을 추가한다.
<select id="findActiveBlogLike" 
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

choose, when, otherwise

우리는 종종 적용 할 모든 조건을 원하는 대신에 한가지 경우만을 원할 수 있다. 자바에서는 switch 구문과 유사하며, MyBatis 에서는 choose 요소를 제공한다.
위 예제를 다시 사용해보자. 지금은 title 만으로 검색하고 author 가 있다면 그 값으로 검색된다. 둘다 제공하지 않는다면, featured 상태의 blog 가 리턴된다.
<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>

trim, where, set

앞서 예제는 악명높게 다양한 요소가 사용된 동적 SQL 이다. “if” 예제를 사용해보자.
<select id="findActiveBlogLike" 
     resultType="Blog">
  SELECT * FROM BLOG 
  WHERE 
  <if test="state != null">
    state = #{state}
  </if> 
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
어떤 조건에도 해당되지 않는다면 어떤 일이 벌어질까? 아마도 다음과 같은 SQL 이 만들어질 것이다.
SELECT * FROM BLOG 
WHERE
아마도 이건 실패할 것이다. 두번째 조건에만 해당된다면 무슨 일이 벌어질까? 아마도 다음과 같은 SQL 이 만들어질 것이다.
SELECT * FROM BLOG 
WHERE 
AND title like ‘someTitle’
이것도 아마 실패할 것이다. 이 문제는 조건만 가지고는 해결되지 않았다. 이렇게 작성했다면, 다시는 이렇게 작성하지 않게 될 것이다.
실패하지 않기 위해서 조금 수정해야 한다. 조금 수정하면 아마도 다음과 같을 것이다:
<select id="findActiveBlogLike" 
     resultType="Blog">
  SELECT * FROM BLOG 
  <where> 
    <if test="state != null">
         state = #{state}
    </if> 
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
where 요소는 태그에 의해 컨텐츠가 리턴되면 단순히 “WHERE” 만을 추가한다. 게다가, 컨텐츠가 “AND” 나 “OR” 로 시작한다면, 그 “AND” 나 “OR”를 지워버린다.
만약에 where 요소가 기대한 것처럼 작동하지 않는다면, trim 요소를 사용자 정의할 수도 있다. 예를 들어, 다음은 where 요소에 대한 trim 기능과 동일하다.:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ... 
</trim>
override 속성은 오버라이드하는 텍스트의 목록을 제한한다. 결과는 override 속성에 명시된 것들을 지우고 with 속성에 명시된 것을 추가한다.
다음 예제는 동적인 update 구문의 유사한 경우이다. set 요소는 update 하고자 하는 칼럼을 동적으로 포함시키기 위해 사용될 수 있다. 예를 들어:
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>
여기서 set 요소는 동적으로 SET 키워드를 붙히고, 필요없는 콤마를 제거한다.
아마도 trim 요소로 처리한다면 아래와 같을 것이다.
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
이 경우, 접두사는 추가하고, 접미사를 오버라이딩 한다.

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” 를 키로 사용한다.
XML 설정 파일과 XML 매핑 파일에 대해서는 이 정도에서 정리하고, 다음 섹션에서는 Java API 에 대해 좀더 상세하게 살펴볼 것이다.

bind

bind 엘리먼트는 OGNL표현을 사용해서 변수를 만든 뒤 컨텍스트에 바인딩한다. 예를들면
<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

Multi-db vendor support

"_databaseId" 변수로 설정된 databaseIdProvider가 동적인 코드에도 사용가능하다면, 데이터베이스 제품별로 서로다른 구문을 사용할 수 있다. 다음의 예제를 보라:
<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>

Pluggable Scripting Languages For Dynamic SQL

마이바티스 3.2부터는 플러그인 형태로 스크립트 언어를 사용할 수 있다. 그래서 언어 드라이버를 장착하고 동적 SQL쿼리를 작성할때 그 언어를 사용할 수 있다.
두개의 내장된 언어가 있다.
  • xml
  • raw
xml 언어는 설정하지 않을때 기본으로 사용하는 값이다. xml을 사용하면 이전에 다룬 모든 동적태그를 실행할 수 있다.
raw 언어는 사실 기능이 조금 부족하다. raw설정을 사용하면 마이바티스는 파라미터를 치환해서 데이터베이스 드라이버에 구문을 전달한다. 짐작하는 것처럼 raw 언어는 xml 언어보다 조금더 빠르다.
다음처럼 lang 속성을 추가해서 구문에서 사용할 언어를 명시할 수 있다.
<select id="selectBlog" lang="raw">
  SELECT * FROM BLOG
</select>
또는 매퍼를 사용하는 경우라면 @Lang 애노테이션을 사용한다.
public interface Mapper {
  @Lang(RawLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List<Blog> selectBlog();
}
다음의 인터페이스를 구현해서 자신만의 언어 드라이버를 구현할 수도 있다.
public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

2014년 2월 17일 월요일

[MyBatis] MyBatis insert 시 selectKey 사용하여 pk 등록하기

Batis 에서 insert 시 selectKey 사용하여 pk 등록하기 

1
2
3
4
5
6
<insert id="writeBoard" parameterClass="java.util.Map">      
  <selectKey keyProperty="no" resultClass="java.lang.Integer" type="pre"
    select seq_board.nextval from dual 
  </selectKey
  insert into board (no, title, content) values(#{no},#{title},#{content}) 
</insert>

[MyBatis] MyBatis 에서 #{value} 와 ${value} 차이

1.#{value} 
변수 이고 실제 쿼리에서 '(호따움표)가 붙습니다. 
PreparedStatement 에서 ? 에 들어갈 값입니다. 

ex) 
select * from board where id=#{id} 
이렇게 되어 있다면 
myBatis 내부적으론 
1
2
3
4
5
String sql = "select * from board where id = ?"
PreparedStatement  ps = ....
.
.
ps.setString(1,id)




2.${value} 
상수 이고 '(호따움표)가 붙지 않습니다. (sql 인젝션 위험이 있음) 
sql 문장을 만듭니다. 
동적 테이블,컬럼에도 사용됩니다. 
${myColumn} = #{value}이런식도 가능합니다. 

select * from board where id='${id}' 
이렇게 되어 있다면 
myBatis 내부적으론 

1
2
3
String sql = "select * from board where id = 'aaa'"//sql자체를 만듬
PreparedStatement ..
.


[출처:JAVA Project - Spring 강좌]