【Java】iBATISでオブジェクトの配列を持つクラスを利用すると重くなるケースとその回避方法について
おはようございます。
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_CD | COMPANY_NAME |
| 00001 | 会社1 |
| 00002 | 会社2 |
従業員テーブル
| COMPANY_CD | EMP_CD | EMP_NAME | AGE |
| 00001 | 000001 | 社員1 | 25 |
| 00001 | 000002 | 社員2 | 33 |
| 00001 | 000003 | 社員3 | 26 |
| 00002 | 000001 | 社員4 | 43 |
| 00002 | 000002 | 社員5 | 30 |
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対多のテーブルに対してもマッピングを行えるが、
会社データを取得した後に、従業員データを会社毎に取得しにいってしまうため
データ量が増えると途端に遅くなってしまう。
処理フローのイメージは次の通り。
- 会社データ取得「selectCompanyList」
- 会社コード毎に「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 くらいの改善がみられました。
最初から遅い遅いとは思いつつ、一つしか方法がないと思い込んでいたのが間違いでした。
もっと早く気づいていればよかった。。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません