自分がかかわっているシステム開発プロジェクトで、他のチームがこのことでえらく盛り上がっていたので、自分なりに調べた結果を残しておきたいと思います(´・ω・`)
前提
商品在庫を管理するシステム(Java + Doma2)を構築するにあたって、ユーザ入力値をもとに商品名を前方一致で検索したいという要件があったとします。
この要件を実現するため、とある開発者が実施したことは、まず以下のような2-way SQL (findLikeName.sql
)を用意することでした。
findLikeName.sql
SELECT *
FROM product
WHERE name LIKE /* name */'みかん%'
これを以下のような形で呼び出すわけですね。
String name = "りんご"; // ユーザの入力した値
name += "%";
List<Product> products = productDao.findLikeName(name);
doSomething(products);
この場合、以下のようなクエリが発行されるので、要件が実現できているように見えます。
SELECT *
FROM product
WHERE name LIKE 'りんご%'
問題点
たとえばユーザ入力値が%
だったとします。
String name = "%"; // ユーザの入力した値
name += "%";
List<Product> products = productDao.findLikeName(name);
doSomething(products);
この場合、以下のようなSQLが実行されます。
SELECT *
FROM product
WHERE name LIKE '%%'
このクエリはnameがnull以外の行にヒットしてしまいます。これでは%
で商品名を前方一致検索したいという要件にこたえられていないだけでなく、セキュリティ上の問題を発生させる可能性すらあります。
修正方法
この解決策としてはExpression language
の@prefix
を利用することです。
今回の場合はまずfindLikeName.sql
を以下の通りに修正します。
findLikeName.sql
SELECT *
FROM product
WHERE name LIKE /* @prefix(name) */'みかん%'
その後呼び出し方を以下のように修正します。
String name = "りんご"; // ユーザの入力した値
List<Product> products = productDao.findLikeName(name);
doSomething(products);
@prefix
は入力パラメタを前方一致検索用の文字列に変換してくれるだけでなく、%
や_
のような特別な意味を持つ文字をエスケープしてくれる関数です。そのため、パラメタとして%
が渡されたとしても安心ということですね。
環境情報 (pom.xmlから抜粋)
pom.xml
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.seasar.doma.boot</groupId>
<artifactId>doma-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.seasar.doma</groupId>
<artifactId>doma-processor</artifactId>
<version>2.30.0</version>
<scope>provided</scope>
</dependency>