DBFlute  外だしsql使用せず、テーブル結合方法

# はじめに
皆さん、はじめまして。本連載を担当する呉（WU）と申します。

代表的な3つのO/Rマッピングツール（iBATIS、Torque、Hibernate）がありますが、
今回はO/RマッピングとしてのDBFluteを紹介させていただきます。
※O/Rマッピングとは、「オブジェクト」と「リレーショナルデータベース」をマッピング（対応付け）することです。

なぜ、DBFluteの外だしSQLの利用をなるべく避けたい？
以下2点の理由：
①SQL文がソースコードの様々な場所に記述されていると、修正漏れや修正間違いといった問題を常に意識しながら開発しなければなりません。
②O/Rマッピングは実装時のデータベース操作にかかわる煩雑な作業を軽減し、「ミスマッチ」を解決してくれます。
# 課題背景
DBFluteのホームページがありましたが、知識がバラバラに点在し、現場の仕事を利用するためには、設定ファイル（additionalforeignkey.dfprop）とJavaソースの書き方を整理しなければならないです。
#テーブル結合方法説明
この記事ではDBFlute-1.0.5NとJAVA7を使っています。
### DBFluteの基本仕組み
DBFluteのO/Rマッピングの大まかな仕組み
（１）DBFluteにおける自動生成
     DBFluteは、自動生成されたクラスがあって初めて動作するO/Rマッピングです。
（２）JAVA実装
　   Behavior 全てのDBアクセスの処理を司る
　   ConditionBean 検索条件を組み立てる
詳細は[実装マニュアル](http://dbflute.seasar.org/ja/manual/function/ormapper/index.html)をご参照ください。
### ２つ以上のテーブル間が外部キーも紐付けていない、業務上が結合したい場合には以下の方法で対応します。

例：
####  利用テーブル
| TableA            | TableB        | TableC           |
|:-----------------:|:-------------:|:------------------:|
| A_column_1        |  B_column_1   |  C_column_1     |
| A_column_2        |  B_column_1   |  C_column_1     |

※上記はTableAとTableBとTableCがすべて外部キーが紐付けていないことが想定する。       
　下記の設定ファイルはdbflute/XXX/dfprop/additionalforeignkey.dfpropを指すこと。

---------------------------------------
1、期待実行結果SQL（TableAとTableB結合）：  
SELECT *
FROM TableA A
LEFT JOIN TableB B
ON A.A_column_1 = B.B_column_1  

設定ファイル：  
 ; FK_TableA_TO_TableB_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableB
   ; localColumnName = A_column_1 ; foreignColumnName = B_column_1
   ; fixedSuffix = ByID
 }

JAVA:
//TableA情報の取得
```TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();```  

---------------------------------------

2、期待実行結果SQL（TableA・TableBとTableB・TableC結合）：  
SELECT * 
FROM TableA A
INNER JOIN TableB B
ON A.A_column_1 = B.B_column_1
INNER JOIN TableC C
ON B.B_column_1 = C.C_column_1

設定ファイル：  
 ; FK_TableB_TO_TableC_BY_ID = map:{
   ; localTableName  = TableB  ; foreignTableName  = TableC
   ; localColumnName = B_column_1 ; foreignColumnName = C_column_1
   ; fixedSuffix = ById
 }

 ; FK_TableA_TO_TableB_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableB
   ; localColumnName = A_column_1 ; foreignColumnName = B_column_1
   ; fixedSuffix = ById
 }

JAVA:
//TableA情報の取得
```TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableBById().join();
cb.setupSelect_TableBById().withTableCBId();
cb.setupSelect_TableBById().withTableCBId().join();```

---------------------------------------
3、期待実行結果SQL（TableA・TableBとTableB・TableCとTableA・TableC結合）：

SELECT * 
FROM TableA A
LEFT JOIN TableB B
ON A.A_column_1 = B.B_column_1
LEFT JOIN TableC C
ON B.B_column_1 = C.C_column_1
AND A.A_column_2 = C.C_column_2

設定ファイル：  
 ; FK_TableA_TO_TableB_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableB
   ; localColumnName = A_column_1 ; foreignColumnName = B_column_1
   ; fixedSuffix = ById
 }

 ; FK_TableA_TO_TableC_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableC
   ; localColumnName = A_column_2 ; foreignColumnName = C_column_2
   ; fixedCondition = `$$foreignAlias$$.C_column_1 = $$over($localTable.tableBById)$$.B_column_1`
   ; fixedSuffix = ById
 }

JAVA：
//TableA情報の取得
```TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableCById();```

---------------------------------------
4、実行結果SQL(fixedConditionで相関サブクエリ)：

SELECT * 
FROM TableA A
INNER JOIN TableB B
ON A.A_column_1 = B.B_column_1
INNER JOIN TableC C
ON B.B_column_1 = C.C_column_1
AND A.A_column_2 = C.C_column_2
INNER JOIN 
	(SELECT TC.C_column_1 
	  		,MAX(CAST(TC.C_column_2 AS INT)) AS VER 
 			FROM TableC TC
			GROUP BY
			TC.C_column_1
			TC.C_column_2
		) C_0 
ON C_0.C_column_1 = C.C_column_1


設定ファイル：

 ; FK_TableA_TO_TableB_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableB
   ; localColumnName = A_column_1 ; foreignColumnName = B_column_1
   ; fixedSuffix = ById
 }

 ; FK_TableA_TO_TableC_BY_ID = map:{
   ; localTableName  = TableA  ; foreignTableName  = TableC
   ; localColumnName = A_column_2 ; foreignColumnName = C_column_2
   ; fixedCondition = `$$foreignAlias$$.C_column_1 = $$over($localTable.tableBById)$$.B_column_1`
	INNER JOIN 
		(SELECT TC.C_column_1 
	  		,MAX(CAST(TC.C_column_2 AS INT)) AS VER 
 			FROM TableC TC
			GROUP BY
			TC.C_column_1
			TC.C_column_2
		) C_0 
	ON C_0.C_column_1 = `$$foreignAlias$$.C_column_1`
   ; fixedSuffix = ById
 }


JAVA：

//TableA情報の取得
```TableACB cb = tableABhv.newMyConditionBean();
cb.setupSelect_TableBById();
cb.setupSelect_TableBById().join();
cb.setupSelect_TableCById();
cb.setupSelect_TableCById().join();```

---------------------------------------
#### 設定ファイルの用語説明

上記に表したマックは以下の意味合いで、理解した上で活用していくと思います。

`$$foreignAlias$$`：参照先テーブルのエリアス
`$$localAlias$$`：参照元テーブルのエリアス
`$$over([テーブル名].[リレーション名])$$`：別リレーションのテーブルのエリアス
`$$over($localTable.[リレーション名])$$`:LocalテーブルのForeignテーブル
`$$over($foreignTable.[リレーション名])$$`:ForeignテーブルのForeignテーブル

---------------------------------------
# まとめ
BDfluteの設定ファイルとJAVAソースをペアーで組み合わせると、外だしsql使用せず、すべてのテーブル結合が可能です。
今回の説明は現場よく利用している複数テーブルの結合する場合に対応方法ですが、実際にはまたいろいろ細かいのノウハウと思っていますが、次回はJAVA側の実装するノウハウを説明します。

---------------------------------------
# 参考資料
~現場指向O/Rマッパー~
http://dbflute.seasar.org/

additionalForeignKeyMapとは？
http://dbflute.seasar.org/ja/manual/reference/dfprop/additionalforeignkey/