【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 くらいの改善がみられました。
最初から遅い遅いとは思いつつ、一つしか方法がないと思い込んでいたのが間違いでした。
もっと早く気づいていればよかった。。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません