【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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | packagejp.co.doradora.dto; publicclassCompany{ privateStringcompanyCd; privateStringcompanyName; privateList<Emp>empList; publicStringgetCompanyCd(){ returncompanyCd; } publicvoidsetCompanyCd(StringcompanyCd){ this.companyCd=compnayCd; } publicStringgetCompanyName(){ returncompanyName; } publicvoidsetCompanyName(StringcompanyName){ this.companyName=companyName; } publicList<Emp>getEmpList(){ returnempList; } publicvoidsetEmpList(List<Emp>empList){ this.empList=empList } } |
Emp.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | packagejp.co.doradora.dto; publicclassEmp{ privateStringcompanyCd; privateStringempCd; privateStringempName; privateintage; publicStringgetCompanyCd(){ returncompanyCd; } publicvoidsetCompanyCd(StringcompanyCd){ this.companyCd=companyCd; } publicStringgetEmpCd(){ returnempCd; } publicvoidsetEmpCd(StringempCd){ this.empCd=empCd; } publicStringgetEmpName(){ returnempName; } publicvoidsetEmpName(StringempName){ this.empName= } publicintgetAge(){ returnage; } publicvoidsetAge(intage){ this.age=age; } } |
SqlMap
sample.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?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 くらいの改善がみられました。
最初から遅い遅いとは思いつつ、一つしか方法がないと思い込んでいたのが間違いでした。
もっと早く気づいていればよかった。。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません