DBアクセスを定番化しよう DBFlute入門

第7回 応用編「エスケープ付き曖昧検索」

この記事を読むのに必要な時間:およそ 4 分

それでは,本題のエスケープ処理です。

このLikeSearchOptionにescapeBy[エスケープ文字]()というメソッドが幾つか存在します。 それを呼び出すことでエスケープ処理が可能になります。

リスト7 ConditionBeanによるエスケープ付き曖昧検索

/**
 * 会員名称に'100%ジュース_テ'が含まれる会員を検索。
 * 
 * @throws Exception
 */
public void test_ConditionBean_query_LikeSearch_likePrefix_escapeByPipeLine_Tx() throws Exception {
    // ## Arrange ##
    String expectedMemberName = "果汁100%ジュース_テスト";
    String keyword = "100%ジュース_テ";

    // escape処理の必要な会員がいなかったので,ここで一時的に登録
    Member escapeMember = new Member();
    escapeMember.setMemberName(expectedMemberName);
    escapeMember.setMemberAccount("temporaryAccount");
    escapeMember.classifyMemberStatusCodeFormalized();
    memberBhv.insert(escapeMember);

    // escape処理をしない場合にHITする会員も登録
    Member nonEscapeOnlyMember = new Member();
    nonEscapeOnlyMember.setMemberName("果汁100パーセントジュースAテスト");
    nonEscapeOnlyMember.setMemberAccount("temporaryAccount2");
    nonEscapeOnlyMember.classifyMemberStatusCodeFormalized();
    memberBhv.insert(nonEscapeOnlyMember);

    // 一時的に登録した会員が想定しているものかどうかをチェック
    MemberCB checkCB = new MemberCB();
    checkCB.query().setMemberName_LikeSearch(keyword, new LikeSearchOption().likeContain());
    assertEquals("escapeなしで2件ともHITすること", 2, memberBhv.selectList(checkCB).size());

    MemberCB cb = new MemberCB();
    LikeSearchOption option = new LikeSearchOption().likeContain().escapeByPipeLine(); 
    cb.query().setMemberName_LikeSearch(keyword, option); 

    // ## Act ##
    List<Member> memberList = memberBhv.selectList(cb);

    // ## Assert ##
    assertNotNull(memberList);
    assertEquals(1, memberList.size());// このキーワードにHITする人は1人しかいない
    Member actualMember = memberList.get(0);
    log.debug(actualMember);
    assertEquals(expectedMemberName, actualMember.getMemberName());
}

リスト8 ConditionBeanによるエスケープ付き曖昧検索-SQL

select ...
  from MEMBER dflocal 
 where dflocal.MEMBER_NAME like '%100|%ジュース|_テ%' escape '|'

見事に'%'や'_'がエスケープされています。 このエスケープ処理を手動で行い場合に一番間違え易く忘れ易いのが, ⁠条件値にエスケープ文字を埋め込む作業」です。本当のワイルドカードを 付ける前に文字列置換でエスケープ文字を埋め込む必要があるのですが, DBFluteはこれを内部的に自動で行います。よって,非常に「安全に⁠⁠ エスケープ付き曖昧検索を実現することが可能です。

どのエスケープ文字でエスケープするかは,ご利用のデータベースが採用している エスケープ文字を選んで下さい。(データベース固有の仕様をご確認下さい)

リスト9 選択できるエスケープ文字の種類

new LikeSearchOption().escapeByAtMark();    // '@'でエスケープ
new LikeSearchOption().escapeByBackSlash(); // '\'でエスケープ
new LikeSearchOption().escapeByPipeLine();  // '|'でエスケープ
new LikeSearchOption().escapeBySlash();     // '/'でエスケープ

条件値にエスケープ文字が格納されていたらどうしよう!?と考える必要はありません。

条件値内のエスケープ文字も内部的に自動でエスケープされます。

リスト10 ConditionBeanでエスケープ文字をエスケープ-SQL

-- '100|ジュース'という条件値だった場合
 where dflocal.MEMBER_NAME like '%100||ジュース%' escape '|'

OutsideSql(外だしSQL)のLikeSearch

それでは,今度はOutsideSql(外だしSQL)におけるLikeSearchを見て行きましょう。 こちらに関しては,やろうと思えばエスケープ文字の指定をSQLにベタに 書くことはとても簡単です。SQLに「escape '|'」と自分で書いてしまえば 良いのです。しかし,条件値のエスケープ文字の埋め込み処理は自前で やる必要がありますし,また,エスケープ文字の指定がSQLとプログラムで 冗長化します。

DBFluteでは,ParameterBeanにLikeSearchOptionを 指定することでConditionBeanのときのようなエスケープ処理の自動化を提供しています。

ParameterBeanの宣言時に,曖昧検索対象のプロパティに「:like」というオプションを 付けてあげて,Sql2Entityを実行します。 すると,ParameterBeanのSetterメソッドにて,LikeSearchOptionが指定できるようになります。

リスト11 ParameterBeanでLikeSearchOption指定-SQL

-- !MemberWithMaxPurchasePricePmb!
-- !!Integer memberId!!
-- !!String memberName:like!! 

リスト12 ParameterBeanのSetterでLikeSearchOption

LikeSearchOption option = new LikeSearchOption().likeContain().escapeByPipeLine();
pmb.setMemberName_LikeSearch("100%ジュース", option); 

リスト13 LikeSearchOption利用時の外だしSQLでの条件設定-SQL

MEMBER_NAME like /*pmb.memberName*/'%テスト値%'

リスト14 LikeSearchOption利用時の実行後SQL

MEMBER_NAME like '%100|%ジュース%' escape '|'

まとめ

エスケープ付き曖昧検索を徹底してみてみました。 実務でぜひ利用してみて下さい。

次回は,とうとう「ページング検索」をみていきます!

著者プロフィール

久保雅彦(くぼまさひこ)

DBFluteメインコミッタ。主にオープン系の開発に従事。DB設計・DB周りの実装などを担当することが多い。