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

第3回ConditionBeanで色々な条件組み立て

はじめに

前回はDBFluteの環境を構築し、実際にDBアクセスをしてみました。

今回はDBFluteのコア機能であるConditionBeanを実際に使ってみましょう。

お知らせ

この連載にて利用するExampleのソース(Eclipseプロジェクト)は、Subversionにて管理されており、誰もが参照することが可能です。

以下がSVNリポジトリのURLです。

H2データベースを組み込みで利用しているため、チェックアウトしてすぐに単体テストが実行できます。

また、ちょっとしたテーブル構造の確認などはDBFluteが生成したテーブル一覧HTMLにて確認できます

今回のConditionBeanの例題プログラムも全てこのプロジェクトにて実装されています。ぜひチェックアウトしてDBFluteをいじり倒して見て下さい!

ConditionBean

条件絞り込み

Equal

まず、基本中の基本「Equal」からやってみて、条件メソッドの根本的な仕様を探っていきましょう。

条件値は全てバインド変数としてハンドリングされます。条件の組み立てはConditionBeanのquery()メソッドを必ず利用します。

続いてメソッドを補完すると、set[カラム名]_[演算子]()というメソッドが利用できますリスト1⁠。

リスト1:Equal
/**
 * 会員ID「3」の会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_Equal(expectedMemberId);

    // ## Act ##
    final Member member = memberBhv.selectEntityWithDeletedCheck(cb);

    // ## Assert ##
    assertNotNull(member);
    assertEquals(expectedMemberId, member.getMemberId());
}

そして、複数条件を指定指定した場合は全てAND条件として設定されますリスト2⁠。ConditionBeanではOR条件はサポートしていません(特例を除き⁠⁠。代わりにUNION(後述)を利用します。

リスト2:複数条件の指定
/**
 * 会員ID「1」、かつ、会員アカウント「Stojkovic」の会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_TwoOrMoreCondition_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 1;
    final String expectedMemberAccount = "Stojkovic";
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_Equal(expectedMemberId);
    cb.query().setMemberAccount_Equal(expectedMemberAccount);

    // ## Act ##
    final Member member = memberBhv.selectEntityWithDeletedCheck(cb);

    // ## Assert ##
    assertNotNull(member);
    assertEquals(expectedMemberId, member.getMemberId());
    assertEquals(expectedMemberAccount, member.getMemberId());
}

もし、setMemberId_Equal()の引数にnullを入れた場合はどうなるでしょうか?

例えば、検索画面の条件入力項目において、ユーザが何も入力しなければnullもしくは空文字が来ることでしょう。そのような状況を考慮して、nullもしくは空文字が指定された場合はその条件は無効になりますリスト3、4⁠。

リスト3:条件引数にnullを指定
/**
 * 会員IDにnullを設定
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_ArgumentNull_Tx() throws Exception {
    // ## Arrange ##
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_Equal(null);

    // ## Act & Assert ##
    try {
        memberBhv.selectEntityWithDeletedCheck(cb);
        fail();
    } catch (EntityDuplicatedException e) {
        // OK
        log.debug(e.getMessage());
    }
}
リスト4:条件引数に空文字を指定
/**
 * 会員名に空文字を設定
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_ArgumentEmptyString_Tx() throws Exception {
    // ## Arrange ##
    final MemberCB cb = new MemberCB();
    cb.query().setMemberName_Equal("");

    // ## Act ##
    final int count = memberBhv.selectCount(cb);

    // ## Assert ##
    assertEquals("条件なしの件数と同じであること", memberBhv.getCountAll(), count);
}

よってConditionBeanでは、画面入力値の有無をif文で囲って条件付与を分岐させる必要はありませんリスト5⁠。この仕様は、他の条件メソッドにも当てはまります(GreaterEqualやLessThanなど⁠⁠。

リスト5:画面入力値を想定した実装
/**
 * 会員IDと会員名と会員ステータスを画面から入力されたと想定
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_SearchInputExample_Tx() throws Exception {
    // ## Arrange ##
    // これらは画面入力値を想定
    final Integer memberId = 3;
    final String memberName = null;
    final String memberStatusCode = "FML";

    final MemberCB cb = new MemberCB();

    // ここで特にif文でnullチェックをする必要はない。
    cb.query().setMemberId_Equal(memberId);
    cb.query().setMemberName_Equal(memberName);
    cb.query().setMemberStatusCode_Equal(memberStatusCode);

    // ## Act & NonAssert##
    // 会員ID「3」かつ会員ステータス「FML(正式会員)」という条件になる
    //   --> where MEMBER_ID = 3 and MEMBER_STATUS_CODE = 'FML'
    final List<Member> memberList = memberBhv.selectList(cb);
    log.debug(memberList);
}

また、同じカラムに対する同じ演算子の条件を再度設定した場合は、上書きになりますリスト6⁠。

リスト6:条件の上書き
/**
 * 会員ID「3」の設定をした後、会員ID「4」を設定
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_OverrideCondition_Tx() throws Exception {
    // ## Arrange ##
    final Integer beforeMemberId = 3;
    final Integer afterMemberId = 4;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_Equal(beforeMemberId);
    cb.query().setMemberId_Equal(afterMemberId);

    // ## Act ##
    final Member member = memberBhv.selectEntityWithDeletedCheck(cb);

    // ## Assert ##
    assertNotNull(member);
    assertEquals("後に設定した値が有効になること", afterMemberId, member.getMemberId());
}

しかし、この挙動は演算子によって変わります。LikeSearch(後述)は同じカラムに対する同じ演算子の条件をAND条件でつなげることに意味のあるため、これらの演算子は呼び出すたびにAND条件で条件が追加されます。

ただし、NotEqual(後述)もAND条件でつなげる意味があるのですが、NotInScope(後述)を利用することで代用できるためこちらは上書き仕様となっています。

では同じカラムに対する同じ演算子で「同じ値」の条件を設定した時が気になりますね。どうなるのでしょう?

明らかに意味が無いため、もちろん2つ条件を作ったりはせずに1つだけ条件が有効になりますリスト7⁠。この場合は、明らかなプログラミングミスになりますので、既に設定済みであることを示すWARNログが出力されますリスト8⁠。

リスト7:全く同じ条件の設定
/**
 * 会員ID「3」の設定をした後、会員ID「3」を設定
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_Equal_AbsolutelySameCondition_Tx() throws Exception {
    // ## Arrange ##
    final Integer beforeMemberId = 3;
    final Integer afterMemberId = beforeMemberId;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_Equal(beforeMemberId);
    cb.query().setMemberId_Equal(afterMemberId);

    // ## Act ##
    final Member member = memberBhv.selectEntityWithDeletedCheck(cb);

    // ## Assert ##
    assertNotNull(member);
    assertEquals(beforeMemberId, member.getMemberId());
}
リスト8:「全く同じ条件の設定」のWARNログ
The value has already registered at equal of MEMBER.memberId: value=3

NotEqual

では、じゃんじゃん他の演算子を見ていきましょう。

「NotEqual」ですリスト9⁠。SQLでは「where COLUMN_NAME != 3」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト9:NotEqual
/**
 * 会員ID「3」でない会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_NotEqual_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_NotEqual(expectedMemberId);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    final Member notContainsMember = new Member();
    notContainsMember.setMemberId(expectedMemberId);
    if (memberList.contains(notContainsMember)) {
        fail();
    }
}

GreaterThan

「GreaterThan」ですリスト10⁠。SQLでは「where COLUMN_NAME > 3」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト10:GreaterThan
/**
 * 会員ID「3」より大きい会員IDを持った会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_GreaterThan_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_GreaterThan(expectedMemberId);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        if (member.getMemberId() <= 3) {
            fail();
        }
    }
}

GreaterEqual

「GreaterEqual」ですリスト11⁠。SQLでは「where COLUMN_NAME >= 3」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト11:GreaterEqual
/**
 * 会員ID「3」以上の会員IDを持った会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_GreaterEqual_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_GreaterEqual(expectedMemberId);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        if (member.getMemberId() < 3) {
            fail();
        }
    }
}

LessThan

「LessThan」ですリスト12⁠。SQLでは「where COLUMN_NAME < 3」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト12:LessThan
/**
 * 会員ID「3」より小さい会員IDを持った会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_LessThan_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_LessThan(expectedMemberId);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        if (member.getMemberId() >= 3) {
            fail();
        }
    }
}

LessEqual

「LessEqual」ですリスト13⁠。SQLでは「where COLUMN_NAME <= 3」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト13:LessEqual
/**
 * 会員ID「3」以下の会員IDを持った会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_LessEqual_Tx() throws Exception {
    // ## Arrange ##
    final Integer expectedMemberId = 3;
    final MemberCB cb = new MemberCB();
    cb.query().setMemberId_LessEqual(expectedMemberId);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        if (member.getMemberId() > 3) {
            fail();
        }
    }
}

PrefixSearch

ちょっと毛並みが変わります。前方一致検索を意味する「PrefixSearch」ですリスト14⁠。

SQLでは「where COLUMN_NAME like 'xxx%'」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、最後に設定した条件が有効になります。

リスト14:PrefixSearch
/**
 * 会員名が「ス」で始まる会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_PrefixSearch_Tx() throws Exception {
    // ## Arrange ##
    final String expectedMemberNamePrefix = "ス";
    final MemberCB cb = new MemberCB();
    cb.query().setMemberName_PrefixSearch(expectedMemberNamePrefix);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        log.debug("memberName=" + member.getMemberName());
        if (!member.getMemberName().startsWith(expectedMemberNamePrefix)) {
            fail();
        }
    }
}

アプリケーションにおいて前方一致は一番よく利用される曖昧検索かと思われます。 そのため、このように明示的なメソッドとして用意されています。

特徴としては、ワイルドカード(%)はConditionBean内部にて解決されるため、プログラム側で明示的に付与する必要がないということが挙げられます。ログを見てみると、しっかりワイルドカードが付与されていることがわかりますリスト15⁠。

リスト15:「PrefixSearch」のログ一部抜粋
...from MEMBER  where MEMBER.MEMBER_NAME like 'ス%'

InScope

ある列に対して複数の値を条件に検索する「InScope」ですリスト16⁠。

SQLでは「where COLUMN_NAME in ('a', 'b')」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、指定した分条件がAND条件で追加されます。

リスト16:InScope
/**
 * 会員ID「3」・「6」・「7」の会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_InScope_Tx() throws Exception {
    // ## Arrange ##
    final MemberCB cb = new MemberCB();
    final List<Integer> expectedMemberIdList = new ArrayList<Integer>();
    expectedMemberIdList.add(3);
    expectedMemberIdList.add(6);
    expectedMemberIdList.add(7);
    cb.query().setMemberId_InScope(expectedMemberIdList);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        log.debug("memberId=" + member.getMemberId());
        if (!expectedMemberIdList.contains(member.getMemberId())) {
            fail();
        }
    }
}

NotInScope

「InScope」の否定版を意味する「NotInScope」ですリスト17⁠。

SQLでは「where COLUMN_NAME not in ('a', 'b')」という条件になります。同じカラムに対する同じ演算子の条件を複数指定すると、指定した分条件がAND条件で追加されます。

リスト17:NotInScope
/**
 * 会員ID「3」・「6」・「7」でない会員を検索
 * 
 * @throws Exception
 */
public void test_ConditionBean_Query_NotInScope_Tx() throws Exception {
    // ## Arrange ##
    final MemberCB cb = new MemberCB();
    final List<Integer> expectedMemberIdList = new ArrayList<Integer>();
    expectedMemberIdList.add(3);
    expectedMemberIdList.add(6);
    expectedMemberIdList.add(7);
    cb.query().setMemberId_NotInScope(expectedMemberIdList);

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

    // ## Assert ##
    assertNotNull(memberList);
    assertNotSame(0, memberList.size());
    for (final Member member : memberList) {
        log.debug("memberId=" + member.getMemberId());
        if (expectedMemberIdList.contains(member.getMemberId())) {
            fail();
        }
    }
}

次回

基本的な演算子は一通り登場しました。さらには応用的なものが他にもあるのですが、先に他のもっと基本的なところを見ていきたいと思います。

今回はソートや結合とかが全く出てきていません。気になっている方もいらっしゃるかと思いますので、次回はConditionBeanのソートや結合等の基本的な機能を見ていきます。

おすすめ記事

記事・ニュース一覧