はじめに
今回からは、
エスケープ付き曖昧検索
エスケープ付き曖昧検索とは?
まずは基本的な概念から説明します。
曖昧検索は、
-- 「'100%ジュース'を含むもの」
where xxx like '%100%ジュース%'
なにがまずいかというと、
SQLでは、
-- 「'100%ジュース'を含むもの」
where xxx like '%100|%ジュース%' escape '|'
こうすることで、
-- 「'aaa|bbb'を含むもの」
where xxx like '%aaa||bbb%' escape '|'
この処理は、
「第3回 ConditionBeanで色々な条件組み立て」
ConditionBeanのLikeSearch
ConditionBeanのLikeSearchの基本的な使い方を説明します。
query()メソッドの後、
/**
* 会員名称が'ス'で始まる会員を検索。
*
* @throws Exception
*/
public void test_ConditionBean_query_LikeSearch_likePrefix_Tx() throws Exception {
// ## Arrange ##
String prefix = "ス";
MemberCB cb = new MemberCB();
LikeSearchOption option = new LikeSearchOption().likePrefix();
cb.query().setMemberName_LikeSearch(prefix, option);
// これと同じ
// cb.query().setMemberName_PrefixSearch(prefix);
// ## Act ##
List<Member> memberList = memberBhv.selectList(cb);
// ## Assert ##
assertNotNull(memberList);
assertNotSame(0, memberList.size());
for (Member member : memberList) {
log.debug("memberName=" + member.getMemberName());
if (!member.getMemberName().startsWith(prefix)) {
fail();
}
}
}
select ...
from MEMBER dflocal
where dflocal.MEMBER_NAME like 'ス%'
likePrefix()の部分をlikeContain()やlikeSuffix()にすることで中間一致や後方一致に することも可能です。
new LikeSearchOption().likePrefix(); // 前方一致
new LikeSearchOption().likeContain(); // 中間一致
new LikeSearchOption().likeSuffix(); // 後方一致
それでは、
このLikeSearchOptionにescapeBy[エスケープ文字]()というメソッドが幾つか存在します。 それを呼び出すことでエスケープ処理が可能になります。
/**
* 会員名称に'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());
}
select ...
from MEMBER dflocal
where dflocal.MEMBER_NAME like '%100|%ジュース|_テ%' escape '|'
見事に'%'や'_'がエスケープされています。
このエスケープ処理を手動で行い場合に一番間違え易く忘れ易いのが、
どのエスケープ文字でエスケープするかは、
new LikeSearchOption().escapeByAtMark(); // '@'でエスケープ
new LikeSearchOption().escapeByBackSlash(); // '\'でエスケープ
new LikeSearchOption().escapeByPipeLine(); // '|'でエスケープ
new LikeSearchOption().escapeBySlash(); // '/'でエスケープ
条件値にエスケープ文字が格納されていたらどうしよう!?
条件値内のエスケープ文字も内部的に自動でエスケープされます。
-- '100|ジュース'という条件値だった場合
where dflocal.MEMBER_NAME like '%100||ジュース%' escape '|'
OutsideSql(外だしSQL)のLikeSearch
それでは、
DBFluteでは、
ParameterBeanの宣言時に、
-- !MemberWithMaxPurchasePricePmb!
-- !!Integer memberId!!
-- !!String memberName:like!!
LikeSearchOption option = new LikeSearchOption().likeContain().escapeByPipeLine();
pmb.setMemberName_LikeSearch("100%ジュース", option);
MEMBER_NAME like /*pmb.memberName*/'%テスト値%'
MEMBER_NAME like '%100|%ジュース%' escape '|'
まとめ
エスケープ付き曖昧検索を徹底してみてみました。 実務でぜひ利用してみて下さい。
次回は、