MyBatisを学び始めると、比較的早い段階で出てくるのが #{} と ${} です。
見た目はかなり似ています。
でも、実際には動きがまったく同じではありません。
ここをふんわり理解したまま使ってしまうと、
「とりあえず動いたからOK」で進んでしまいやすいです。
ただ、この2つは安全性にも関わる大事なポイントなので、最初に整理しておいたほうが安心です。
MyBatis公式では、#{} は PreparedStatement のパラメータとして扱われ、通常はこちらが safer / faster / almost always preferred と説明されています。一方で ${} は文字列をそのままSQLへ埋め込む仕組みとして説明されています。
この記事では、難しく広げすぎず、まず安全に使い分けられるようになることを目標に整理していきます。
MyBatisの #{} と ${} は何が違うのか
ここでは、まず2つの違いをシンプルに整理します。
#{} は値を安全に渡すための書き方
#{} は、SQLの中へ値を安全に渡すための書き方です。
たとえば、IDでユーザーを検索するなら、次のように書きます。
<select id="findById" resultType="User">
SELECT
id,
name,
email
FROM users
WHERE id = #{id}
</select>MyBatis公式では、#{id} のような書き方は PreparedStatement のパラメータとして扱われ、JDBCでいう ? に近いものだと説明されています。つまり、SQL文の形は固定したまま、値だけを後から渡すイメージです。
最初のうちは、#{} を見たら
「値を渡している」
と考えておけば大丈夫です。
${} は文字列をそのまま埋め込む書き方
${} は、値を安全に渡すというより、文字列をそのままSQLへ差し込む書き方です。
たとえば、次のようなイメージです。
<select id="findAllUsers" resultType="User">
SELECT
id,
name,
email
FROM users
ORDER BY ${columnName}
</select>MyBatis公式でも、${} は unmodified string を直接 inject するための仕組みであり、文字列は MyBatis によって modify or escape されないと説明されています。
つまり、${} は便利ではありますが、#{} と同じ感覚で使うものではありません。
まずは #{} を基本に考えてよい理由
初学者のうちは、まず
「迷ったら #{}」
でほぼ問題ありません。
MyBatis公式でも、#{} は safer / faster / almost always preferred と説明されています。検索条件の値や更新値のように、普通に入力値を渡したい場面では、基本的にこちらを使う前提で考えて大丈夫です。
逆に ${} は、少し特殊な用途で使うものです。
そのため、最初から両方を同じ重さで覚えようとしないほうが理解しやすいです。
どう使い分ければよいのか
ここでは、実際にどんな場面で使い分けるのかを整理します。
検索条件の値には #{} を使う
まず、検索条件として値を渡す場面では #{} を使います。
たとえば、名前で検索するなら次のように書きます。
<select id="findByName" resultType="User">
SELECT
id,
name,
email
FROM users
WHERE name = #{name}
</select>メールアドレス検索でも、ステータス検索でも、更新値でも同じです。
<update id="updateStatus">
UPDATE users
SET status = #{status}
WHERE id = #{id}
</update>こういう場面では、${} を使う理由は基本的にありません。
むしろ、普通の値を渡すだけなら #{} を使うのが自然です。
カラム名や並び順の切り替えで ${} が使われることがある
${} が使われる代表例は、SQLの構造そのものを切り替えたいときです。
MyBatis公式でも、文字列置換の例として ORDER BY ${columnName} が紹介されています。また、テーブル名やカラム名のような metadata が動的な場合に useful だと説明されています。
たとえば、並び順の列を切り替えたいときです。
<select id="findAllUsers" resultType="User">
SELECT
id,
name,
email
FROM users
ORDER BY ${orderBy}
</select>ここで ORDER BY #{orderBy} と書いても、列名としては扱えません。#{} はあくまで値を渡すためのものだからです。
つまり、
- 値を渡したいなら
#{} - SQLの一部として文字列を差し込みたいなら
${}
という違いがあります。
${} を使うときに気を付けたいこと
${} は便利ですが、そのまま文字列を埋め込むので扱いは慎重にしたほうがよいです。
たとえば、次のような書き方は危険です。
<select id="findUsers" resultType="User">
SELECT
id,
name,
email
FROM users
ORDER BY ${inputValue}
</select>もし inputValue に想定外の文字列が入ると、SQLの形そのものが変わってしまいます。
MyBatis公式でも ${} は文字列をそのまま差し込むだけで、escape されないと説明されています。
なので ${} を使うときは、
何でも自由に入れてよい値を渡すのではなく、使ってよい候補をアプリ側で制限する
という考え方が大切です。
たとえば、並び順を切り替えるなら、リクエスト値をそのまま渡すのではなく、Java側で安全な候補へ変換してから使うほうが安心です。
public String resolveOrderBy(String sortKey) {
return switch (sortKey) {
case "name" -> "name";
case "createdAt" -> "created_at";
default -> "id";
};
}<select id="findAllUsers" resultType="User">
SELECT
id,
name,
email
FROM users
ORDER BY ${orderBy}
</select>このように、自由入力をそのまま渡さない形にしておくと、かなり安全になります。
初学者が最初に覚えておきたいポイント
ここでは、最初の段階で特に大事なことを整理します。
迷ったら #{} を使う
これはかなり大事です。
検索条件、更新値、ID指定など、普通の入力値を扱う場面では、まず #{} を選べば大きく外しません。
MyBatis公式でも、通常はこちらが almost always preferred と説明されています。
初学者のうちは、${} を使う理由が明確に説明できないなら、たいてい #{} で考えたほうがよいです。
${} ${}にユーザー入力をそのまま入れない
これも最初に強く意識しておきたいです。
${} は、そのまま文字列を埋め込みます。
だから、フォーム入力やクエリパラメータの値をそのまま入れるのは避けたほうが安全です。
特に、並び順、テーブル名、カラム名の切り替えのような場面は、${} を使いたくなりやすいです。
でもそのときも、入力値をそのまま採用しないことが大切です。
やるべきことは、次の流れです。
- 受け取った値をそのまま使わない
- 許可した候補に変換する
- 変換後の安全な文字列だけ
${}に渡す
この順番を守るだけでも、かなり事故を防ぎやすくなります。
安全性と読みやすさを優先する
MyBatisは柔軟なので、やろうと思えばかなりいろいろ書けます。
でも、最初のうちは柔軟さよりも安全性と読みやすさを優先したほうがうまくいきます。
たとえば、
- 検索値は
#{}にする ${}は必要な場面だけにする${}を使うときは候補を絞る- なんとなく動く書き方より、あとで見てもわかる書き方を選ぶ
こういう考え方で十分です。
このテーマは、細かい仕様を全部覚えるより、
危ない使い方を避けながら、普段は #{} を選べるようになること
のほうが大事です。
まとめ
MyBatisの #{} と ${} は、見た目は似ていますが役割が違います。
#{} は PreparedStatement のパラメータとして扱われ、通常はこちらが safer / faster / almost always preferred と公式で説明されています。
一方で ${} は文字列をそのままSQLへ埋め込む仕組みで、escape されません。
最初は、次のように覚えておけば十分です。
- 検索条件や更新値には
#{}を使う - SQLの構造を切り替える必要があるときだけ
${}を検討する ${}にユーザー入力をそのまま渡さない- 迷ったら
#{}を選ぶ
この4つを押さえておくだけで、かなり安全に書けるようになります。


コメント