# 高度な操作
# SQLファイルの解決ルール
# 複数フォルダの指定
sqlフォルダはクラスパスから参照することが出来れば複数指定することが出来ます。
src/test/resources
,src/main/resources
の順にクラスパスに指定されている場合
src
├─main
│ └─resources
│ └─sql
│ ├─department
│ │ ├─insert_department.sql
│ │ └─select_department.sql
│ └─employee
│ ├─insert_employee.sql
│ └─select_employee.sql
└─test
└─resources
└─sql
├─department
│ ├─update_department.sql
│ └─delete_department.sql
└─employee
├─update_employee.sql
└─select_employee.sql
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
WARNING
SQLファイルのパスが重複している場合、クラスパス上で先にあるフォルダのSQLファイルが使用されます。
上記のフォルダ構成の場合、src/main/resources/sql/employee/select_employee.sql
と src/test/resources/sql/employee/select_employee.sql
がともに employee/select_employee
として解決されますが、クラスパスとしてsrc/test/resources
が先に指定されているため、src/test/resources/sql/employee/select_employee.sql
が使用されます。
# jarファイルの指定
SQLファイルはjarの中にリソースとして含めることもできます。
その場合、リソースのルート直下のsqlフォルダをルートフォルダとした相対パスでSQLファイルを指定することができます。
SQLファイルのルートフォルダ(初期値:sql)は変更することができます。
変更方法の詳細は SQLファイルルートフォルダの設定 を参照してください。
# Dialectによるファイルパスの切り替え
NioSqlManagerImpl
をSqlManager
として指定した場合、Dialectによるファイルパスの切り替えが出来るようになります。
詳しくはDB種類毎のファイルパス切り替えを参照してください。
# PostgreSQLのトランザクション内SQLエラー対応
PostgreSQLでは、1つのトランザクション内でSQLエラーが発生した場合、後続するSQL文はすべて無条件でエラーとなります。
この状態はトランザクションに対してcommit
もしくはrollback
を実行するまで続きます。
WARNING
エラーが発生している状態でcommit
を実行しても実際にはrollback
されます
これはPostgreSQL固有の動作であり、通常は問題ない動作なのですが、テーブルロックエラーなどリトライ処理を行うケースで問題になります。
(SQLのリトライについてはSQL実行のリトライを参照)
uroboroSQLではリトライ指定のあるSQL実行、かつ、PostgreSQL(より正確にはDialect#isRollbackToSavepointBeforeRetry()
がtrue
の場合)の場合にsavepointを使った部分ロールバックを行うことで
この問題に対応しています。
具体的にはリトライ指定のあるSQL実行、かつ、PostgreSQLの場合はSQL実行の直前にリトライ用のsavepointを設定し、SQL実行が成功すればsavepointの解放、SQL実行が失敗した場合はリトライ用のsavepointまでロールバックを行います。
WARNING
リトライ指定のないSQL実行の場合はsavepointの設定は行われません。
リトライ指定のないSQLで上記と同様の動作を行う場合は以下のように実装してください。
agent.required(() -> { // トランザクション開始
agent.savepointScope(() -> {
// savepointScopeの開始
agent.update("example/insert_product")
.param("product_id", 1)
.count();
});
agent.savepointScope(() -> {
// 後続処理
int count = agent.update("department/insert_department")
.param("dept_no", 1)
.param("dept_name", "Sales")
.count();
・・・
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 更新処理の委譲(SqlContext#setUpdateDelegate())
Webアプリケーションを作成する場合、以下のような流れで画面からの登録処理を行うことがあります。
- データ検証(データの存在有無や重複チェック、データの整合性チェックなど)
- 登録、更新処理
その際、より厳密に一度 1. のデータ検証だけを実施し、問題がなければ再度1. と 2. を合わせて処理を行うことがあります。
この場合 1. のデータ検証だけを行うモードかどうかをフロントエンドから渡し、それによって 1. と 2. を実行するか分岐することになります。
- 個別処理での実装イメージ
if (request.isCheckMode()) {
// 1. データ検証(データ検証用のQuery発行)
validateData(request);
} else {
// 1. データ検証(データ検証用のQuery発行)
validateData(request);
// 2. 登録、更新処理
createData(request);
}
2
3
4
5
6
7
8
9
このような処理を個別実装で行うと実装漏れが起こりやすく、テストも大変になります。
このような場合に更新処理の委譲を利用することで、データ検証を行うモードの場合には更新処理をスキップする、といった動作を一律指定することが出来ます。
更新処理の委譲を利用する場合、SqlContextに委譲用のFunctionを指定します。
- setUpdateDelegate 実装例
SqlUpdate update = agent.update("example/update_product")
.set("product_name", "new_name")
.set("jan_code", "1234567890123")
.equal("product_id", 1);
SqlContext ctx = update.context();
ctx.setUpdateDelegate(context -> 2); // 更新の委譲処理。登録する Function は 引数として SqlContext を受取り、int(更新件数)を返却する
update.count(); // SQLは発行されず、代わりに委譲用のFunctionが実行され戻り値 2 が返る
2
3
4
5
6
7
SqlContext#setUpdateDelegate() は通常 自動パラメータバインド関数の設定 と合わせて利用します。
- SqlContextFactoryの設定例
SqlConfig config = UroboroSQL
.builder(...)
// SqlContextFactoryの設定
.setSqlContextFactory(new SqlContextFactoryImpl()
// update/batch/procedure用自動パラメータバインド関数の登録
.addUpdateAutoParameterBinder((ctx) -> {
if (チェックモードなら) {
ctx.setUpdateDelegate(context -> 1);
}
})
).build();
2
3
4
5
6
7
8
9
10
11
- setUpdateDelegateを利用した実装イメージ
// 1. データ検証(データ検証用のQuery発行)
validateData(request);
// 2. 登録、更新処理 (チェックモードの場合はsetUpdateDelegateにより更新SQLの発行が行われない)
createData(request);
2
3
4
# SQLカバレッジ ( uroborosql.sql.coverage
)
これまでアプリケーション上の条件分岐はカバレッジツールを利用して網羅率を確認することができました。
しかし、SQL文の条件分岐は実際にその分岐が通っているかどうかを確認する手段がなく、リリース後に初めて通った条件で不具合を発生させることがありました。
この問題を解決するためにuroboroSQLでは、SQL文の条件分岐を集計してカバレッジレポートを行う機能を提供します。
SQLカバレッジはuroboroSQLを利用するアプリケーションの起動時オプションに
-Duroborosql.sql.coverage=true
を追加することで有効になります。
SQLカバレッジを有効にするとアプリケーションが実行している間に実行されるSQLについて、カバレッジ情報が収集されます。
カバレッジ情報の収集結果は標準ではtarget/coverage/sql-cover.xml
に出力されます。
このファイルの場所や名前を変更したい場合は、起動時オプションに
-Duroborosql.sql.coverage.file=[出力ファイルパス]
を指定してください。
出力されたsql-cover.xml
をJenkinsのCobertura pluginなどのXMLレポートとして読み込むとSQLファイルのカバレッジレポートが参照できるようになります。
また0.2.0+より、uroboroSQLのみでHTMLレポートを出力することができるようになりました。
起動時オプションに
-Duroborosql.sql.coverage=jp.co.future.uroborosql.coverage.reports.html.HtmlReportCoverageHandler
を指定することで本機能を利用することができます。
カバレッジ情報はデフォルトではtarget/coverage/sql
フォルダ配下に出力されます。
出力先フォルダを変更した場合は、起動時オプションに
-Duroborosql.sql.coverage.dir=[出力フォルダパス]
を指定してください。
出力されたレポートのサンプルは下記を参照してください。
# サマリーページ
# 詳細ページ
# ログ出力
uroboroSQLではログ出力ライブラリとしてSLF4Jを使用しています。SLF4Jの詳細は公式のドキュメント (opens new window)を参照して下さい。
uroboroSQLで出力されるログ内容は以下表の通りです。
クラス名 | TRACE | DEBUG | INFO | WARN | ERROR | FATAL |
---|---|---|---|---|---|---|
AbstractAgent | 変換前SQL | 実行時SQL | - | - | - | - |
DebugSqlFilter | - | パラメーター/ 対象データ数/ 実行結果 | - | - | - | - |
IfNode | - | 評価式/ 判定結果/ パラメーター | - | - | - | - |
Parameter | - | パラメーターの設定 | - | サブパラメーター値にNULLを設定 | - | - |
SecretColumnSqlFilter | - | バッチ処理追加件数/ ストアドプロシージャ出力パラメーター | - | - | - | - |
SqlAgent | ステートメントのクローズ | 処理実行アナウンス/ リトライ実行アナウンス/ SQL実行時間 | - | - | エラーメッセージ | - |
SqlContext | - | バッチ処理追加件数/ ストアドプロシージャ出力パラメーター | - | - | - | - |
SqlContextFactory | - | 定数パラメーター | - | 定数名の重複 | エラーメッセージ | - |
SqlLoader | SQL定義ファイルの読み込み完了 | SQL定義ファイルの読み込み開始/読み込み中 | - | - | デフォルトファイルパスの設定/ デフォルト拡張子/ 空のSQLキャッシュの返却 | - |
# システムプロパティ
uroboroSQLではシステムプロパティを指定することで動作を変更することができます。
プロパティ名 | 説明 | 初期値 |
---|---|---|
uroborosql.sql.coverage | SQLカバレッジを出力するかどうかのフラグ。true の場合はSQLカバレッジを出力します。文字列として jp.co.future.uroborosql.coverage.CoverageHandler インタフェースの実装クラスが設定された場合はそのクラスを利用してカバレッジの収集を行います。 | なし |
uroborosql.sql.coverage.file | 指定されたPATH(ファイル)に SQLカバレッジのCobertura形式のxmlレポートを出力します。 | ./target/coverage/sql-cover.xml |
uroborosql.sql.coverage.dir | 指定されたPATH(フォルダ)にSQLカバレッジのHTMLレポートを出力します。 | ./target/coverage/sql |
uroborosql.entity.cache.size | Entityクラス情報のキャッシュサイズを指定します。 キャッシュサイズを超えるEntityクラスの読み込みがあった場合は古い情報から破棄されます。 | 30 |
uroborosql.use.qualified.table.name | DAOインタフェースで生成するSQLにスキーマ名で修飾したテーブル名を出力(true )するか、テーブル名のみを出力(false )するかを指定 | true |