🐤
MyBatisでforeachの中でbindを使う時の挙動
問題
以下のようなテーブルがあるときに
CREATE TABLE hogeschema.hoge_piyo (
hoge TEXT,
piyo TEXT,
PRIMARY KEY (hoge, piyo)
);
MyBatisを使ってSQLを実行する
<insert id="テストinsert1">
<foreach collection="hogeリスト" item="hoge">
<bind name="piyo" value="piyoマップ.get(hoge)"/>
INSERT INTO "hogeschema"."hoge_piyo" (
"hoge",
"piyo"
) VALUES
(
#{hoge.value},
#{piyo.value}
)
;
</foreach>
</insert>
JavaからMyBatisに渡したパラメータはこちら
List<Hoge型> list = new ArrayList<>();
list.add(testHoge1);
list.add(testHoge2);
list.add(testHoge3);
Hogeリスト型 hogeリスト = new Hogeリスト型(list); // ファーストクラスコレクション
HashMap<Hoge型, Piyo型> map = new HashMap<>();
map.put(testHoge1, new Piyo型("testPiyo1"));
map.put(testHoge2, new Piyo型("testPiyo2"));
map.put(testHoge3, new Piyo型("testPiyo3"));
Piyoマップ型 piyoマップ = new Piyoマップ型(map); // ファーストクラスコレクション
実行後の想定結果はテーブルのレコードが以下になる想定だったが
hoge | piyo |
---|---|
testHoge1 | testPiyo1 |
testHoge2 | testPiyo2 |
testHoge3 | testPiyo3 |
実際はhogeとpiyoが対応せず、piyoの最後の要素が全てのhogeに対応してしまった
hoge | piyo |
---|---|
testHoge1 | testPiyo3 |
testHoge2 | testPiyo3 |
testHoge3 | testPiyo3 |
結論
foreachの中でbindしたくなった時は、bindを利用せずMyBatisに渡すパラメータをまとめてしまうのが良い
<foreach collection="hogepiyoリスト" item="hogepiyo">
INSERT INTO "hogeschema"."hoge_piyo" (
"hoge",
"piyo"
) VALUES
(
#{hogepiyo.hoge.value},
#{hogepiyo.piyo.value}
)
;
</foreach>
パラメータをまとめるだけのHogePiyo型を作り
public record HogePiyo型(Hoge型 hoge, Piyo型 piyo) {
}
HogePiyo型のリストにまとめた型をMyBatisに渡す
List<Hoge型> list = new ArrayList<>();
list.add(testHoge1);
list.add(testHoge2);
list.add(testHoge3);
Hogeリスト型 hogeリスト = new Hogeリスト型(list); // ファーストクラスコレクション
HashMap<Hoge型, Piyo型> map = new HashMap<>();
map.put(testHoge1, new Piyo型("testPiyo1"));
map.put(testHoge2, new Piyo型("testPiyo2"));
map.put(testHoge3, new Piyo型("testPiyo3"));
Piyoマップ型 piyoマップ = new Piyoマップ型(map); // ファーストクラスコレクション
List<HogePiyo型> list = new ArrayList<>();
for (Hoge型 hoge : hogeリスト()) {
Piyo型 piyo = piyoマップ().get(hoge);
list.add(new HogePiyo型(hoge, piyo));
}
HogePiyoリスト型 HogePiyoリスト = new HogePiyoリスト型(list)
やったこと
'#{}'をつかったバインド変数ではなく'${}'を利用してみた
<insert id="テストinsert2">
<foreach collection="hogeリスト" item="hoge">
<bind name="piyo" value="piyoマップ.get(hoge)"/>
INSERT INTO "hogeschema"."hoge_piyo" (
"hoge",
"piyo"
) VALUES
(
'${hoge.value}', -- ここを#から$に変えた。hogeは変えなくても良さそう。
'${piyo.value}' -- ここを#から$に変えた
)
;
</foreach>
</insert>
これによってテーブルは以下の期待値通りになったが、置換変数はユーザーからの入力の場合SQLインジェクションを許してしまう。
hoge | piyo |
---|---|
testHoge1 | testPiyo1 |
testHoge2 | testPiyo2 |
testHoge3 | testPiyo3 |
bindのvalueで呼び出す処理にエスケープ処理を実装すれば良いが、自前で書くのは危険と考えJavaでのエスケープ処理を探した。
PreparedStatementを見つけたが、これはコネクションを使わなければならないため、エスケープをしたいだけだがコネクションを使うことをしたくなかった。
他に良い方法を探し中…
動作確認用のリポジトリ
Discussion