【Java】iBATISでオブジェクトの配列を持つクラスを利用すると重くなるケースとその回避方法について

2017年7月9日Java,開発

おはようございます。

Java のシステムでiBATISを利用している場合に、どうにもパフォーマンスが悪いということで調べてみたら
iBATIS を利用した 1対多 のテーブルをマッピングしている箇所がまずかったのがわかりました。。

1対多のテーブルをマッピングするのに、普通にやると多側の取得に N回 のクエリ実行が必要となります。

実はこれがものすごい遅い・・・

スポンサーリンク

iBATISとは

wikiより抜粋

iBATIS は、SQLクエリを POJO (Plain Old Java Object) にマッピングする永続性フレームワークである。SQLクエリはXMLファイルに置くことで一旦アプリケーションと分離される。検索結果のオブジェクトのマッピングは自動的か半自動的に行う。

iBATIS の基本となる考え方は、SQLクエリをXMLファイルに置くことで、関係データベースにアクセスする際に必要となる大量のJavaコードを大幅に減らすことである。

所謂 O/Rマッパーというやつですね。
便利なのですが、使い方を誤ると思わぬデメリットを被る可能性もあります。

サンプル

例えば、
次のようなテーブルを DTO(Data Transfer Object)クラスにマッピングするとした場合。

テーブル

会社テーブル

COMPANY_CDCOMPANY_NAME
00001会社1
00002会社2

従業員テーブル

COMPANY_CDEMP_CDEMP_NAMEAGE
00001000001社員125
00001000002社員233
00001000003社員326
00002000001社員443
00002000002社員530

DTOクラス

Company.java

package jp.co.doradora.dto;
public class Company {
  private String companyCd;
  private String companyName;
  private List<Emp> empList;
  
  public String getCompanyCd() {
    return companyCd;
  }
  public void setCompanyCd(String companyCd) {
    this.companyCd = compnayCd;
  }
  
  public String getCompanyName() {
    return companyName;
  }
  public void setCompanyName(String companyName) {
    this.companyName = companyName;
  }
  
  public List<Emp> getEmpList() {
    return empList;
  }
  public void setEmpList(List<Emp> empList) {
    this.empList = empList
  }
}

Emp.java

package jp.co.doradora.dto;
public class Emp {
  private String companyCd;
  private String empCd;
  private String empName;
  private int age;
  
  public String getCompanyCd() {
    return companyCd;
  }
  public void setCompanyCd(String companyCd) {
    this.companyCd = companyCd;
  }
  
  public String getEmpCd() {
    return empCd;
  }
  public void setEmpCd(String empCd) {
    this.empCd = empCd;
  }
  
  public String getEmpName() {
    return empName;
  }
  public void setEmpName(String empName) {
    this.empName = 
  }
  
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
}

 

SqlMap

sample.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
    <sqlMap namespace="Sample">
      <!-- 別名 -->
      <typeAlias alias="Company" type="jp.co.doradora.dto.Company"/>
      <typeAlias alias="Emp" type="jp.co.doradora.dto.Emp"/>
        
      <!-- 会社DTOにマッピング -->
      <resultMap id="CompanyResultMap" class="Company">
        <result column="COMPNAY_CD" property="companyCd"/>
        <result column="COMPNAY_NAME" property="companyName"/>
        <result property="empList" select="Sample.selectEmpList" 
          column="{companyCd = COMPANY_CD}"/>
      </resultMap>
    
      <!-- 従業員DTOにマッピング -->
      <resultMap id="EmpResultMap" class="Emp">
        <result column="COMPANY_CD" property="companyCd"/>
        <result column="EMP_CD" property="empCd"/>    
        <result column="EMP_NAME" property="empName"/>
        <result column="AGE" property="age"/>
      </resultMap>
      
      <!-- 会社取得クエリ -->
      <select id="selectCompanyList" resultMap="CompanyResultMap">
        SELECT
          COMPANY_CD
          , COMPANY_NAME
        FROM
          COMPANY
        ORDER BY
          COMPANY_CD
      </select>  
      
      <!-- 従業員取得クエリ -->
      <select id="selectEmpList" resultMap="">
        SELECT
          EMP_CD
          , EMP_NAME
          , AGE
        FROM
          EMP
        WHERE
          COMPANY_CD = #companyCd:CHAR#
        ORDER BY
          EMP_CD
      </select>
      
    </sqlMap>

こうすることで、1対多のテーブルに対してもマッピングを行えるが、
会社データを取得した後に、従業員データを会社毎に取得しにいってしまうため
データ量が増えると途端に遅くなってしまう。

処理フローのイメージは次の通り。

  1. 会社データ取得「selectCompanyList」
  2. 会社コード毎に「selectEmpList」を呼び出し、
    Companyクラスの empList に結果をマッピングする

結果的に、従業員数 × 1 (会社データ取得)のSQLが発行されることとなる。
1回のSQLがたとえ 0.0001 秒しか掛からなかったとしても
件数が増えれば増えるほど遅くなるのは明白ですよね。

改善方法

データを一括で取得してから、
結果を iBATIS の GroupBy で指定した列によって
グルーピングするという方法がある。

SqlMap

sample.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
    <sqlMap namespace="Sample">
      <!-- 別名 -->
      <typeAlias alias="Company" type="jp.co.doradora.dto.Company"/>
      <typeAlias alias="Emp" type="jp.co.doradora.dto.Emp"/>
        
      <!-- 従業員DTOにマッピング -->
      <resultMap id="EmpResultMap" class="Emp">
        <result column="COMPANY_CD" property="companyCd"/>
        <result column="EMP_CD" property="empCd"/>    
        <result column="EMP_NAME" property="empName"/>
        <result column="AGE" property="age"/>
      </resultMap>
      
      <!-- 会社DTOにマッピング -->
      <resultMap id="CompanyResultMap" class="Company" groupBy="companyCd, empCd" >
        <result column="COMPNAY_CD" property="companyCd"/>
        <result column="COMPNAY_NAME" property="companyName"/>
        <result property="empList" resultMap="EmpResultMap" />
      </resultMap>
     
      <!-- 会社、従業員取得クエリ -->
      <select id="selectCompanyEmpList" resultMap="CompanyResultMap">
        SELECT
          C.COMPANY_CD
          , C.COMPANY_NAME
          , E.EMP_CD
          , E.EMP_NAME
          , E.AGE
        FROM
          COMPANY C
          LEFT OUTER JOIN EMP E ON (
            C.COMPANY_CD = E.COMPANY_CD
          )
        ORDER BY
          C.COMPANY_CD
          , E.EMP_CD
      </select>  
      
    </sqlMap>

まとめ

プログラムやシステムにもよるところだと思いますが、
私が経験した感じだと 1/5 から 1/10 くらいの改善がみられました。

最初から遅い遅いとは思いつつ、一つしか方法がないと思い込んでいたのが間違いでした。

もっと早く気づいていればよかった。。

ではでは。

 

スポンサーリンク


関連するコンテンツ

2017年7月9日Java,開発iBATIS,Java,プログラミング

Posted by doradora