package com.example.dbflute.basic.dbflute.howto;
import java.util.List;
import javax.annotation.Resource;
import org.seasar.dbflute.cbean.ListResultBean;
import org.seasar.dbflute.exception.SelectEntityConditionNotFoundException;
import com.example.dbflute.basic.dbflute.cbean.MemberCB;
import com.example.dbflute.basic.dbflute.exbhv.MemberBhv;
import com.example.dbflute.basic.dbflute.exentity.Member;
import com.example.dbflute.basic.dbflute.exentity.MemberStatus;
import com.example.dbflute.basic.dbflute.exentity.MemberWithdrawal;
import com.example.dbflute.basic.unit.UnitContainerTestCase;
/**
* ConditionBeanの初級編Example実装。
*
* ターゲットは以下の通り:
* o とりあえずDBFluteのDBアクセスのやり方について知りたい方
* o DBFluteで開発するけど今まで全く使ったことのない方
*
* コンテンツは以下の通り:
* o ConditionBeanを使った基本的な検索: selectList().
* o many-to-one(FK先)を結合して取得する検索: setupSelect_Xxx().
* o one-to-oneを結合して取得する検索: setupSelect_Xxx().
* o Query-Equal条件: setXxx_Equal().
* o 二つ以上の条件を指定: setXxx_Equal(), setXxx_Equal().
* o 条件にnullを指定: setXxx_Equal(null).
* o 条件に空文字を設定: setXxx_Equal("").
* o 同じ条件を別の値で二回設定(Override): setXxx_Equal(3), setXxx_Equal(4).
* o 同じ条件を同じ値で二回設定(Warn): setXxx_Equal(3), setXxx_Equal(3).
* o 親テーブルの条件で絞り込み検索: queryXxx().setXxx_Equal().
* o 昇順ソートを指定: addOrderBy_Xxx_Asc().
* o 降順ソートを指定: addOrderBy_Xxx_Desc().
* o 複数条件ソートを指定: addOrderBy_Xxx_Asc().addOrderBy_Xxx_Asc().
* o 親テーブルのカラムでソート: queryXxx().addOrderBy_Xxx_Asc().
*
* @author jflute
* @since 0.7.3 (2008/06/01 Sunday)
*/
public class ConditionBeanBasicTest extends UnitContainerTestCase {
// ===================================================================================
// Attribute
// =========
/** The behavior of Member. (Injection Object) */
@Resource
protected MemberBhv memberBhv;
// [Description]
// A. Seasar-2.4の場合はプロパティ名が「クラス名に先頭を小文字にしたもの」であること。
// B. Spring-2.5の場合は型でインジェクションされる。
// ===================================================================================
// Basic
// =====
/**
* o ConditionBeanを使った基本的な検索: selectList().
*
* 【実装手順】
* A. 基点テーブルのConditionBeanを生成
* B. 取得したい関連テーブルを指定
* C. 絞り込み条件・ソート条件を設定
* D. Behaviorのメソッドを呼ぶ
*
* 【特徴】
* ConditionBeanは、目的ベースにSQLを組み立てるオブジェクトである。
* A. 「取得したいテーブル何か?」
* B. 「取得したい関連テーブルは何か?」
* C. 「どんな絞込みをしたいか?ソートをしたいか?」
* D. 「一件検索なのか?リスト検索なのか?」
* などの「目的」を指定することで、SQLを安全に実行することが可能である。
*
*/
public void test_basic() {
// ## Arrange ##
// = = = = = = = = = = = = = = = = =
// A. 基点テーブルのConditionBeanを生成
// -> select句, from句)
// = = = = = = = = = = = = = = = = =
MemberCB cb = new MemberCB();// 基点テーブルは「会員」
// = = = = = = = = = = = = = = = =
// B. 取得したい関連テーブルを指定
// -> select句, from句, join句)
// = = = = = = = = = = = = = = = =
cb.setupSelect_MemberStatus();// 「会員ステータス」を結合してSelect句に展開
// = = = = = = = = = = = = = = = = = = = = = =
// C. 絞り込み条件・ソート条件を設定
// -> where句, order-by句(, from句, join句)
// = = = = = = = = = = = = = = = = = = = = = =
cb.query().setMemberName_PrefixSearch("S"); // 会員名が'S'で始まること
cb.query().addOrderBy_Birthdate_Desc(); // 会員の生年月日の降順で並べる
cb.query().addOrderBy_MemberId_Asc(); // 生年月日が同じ場合は会員IDの昇順
// ## Act ##
// = = = = = = = = = = = = =
// D. Behaviorのメソッドを呼ぶ
// = = = = = = = = = = = = =
List memberList = memberBhv.selectList(cb);// リスト検索
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.toString());
assertTrue(member.getMemberName().startsWith("S"));
}
// [SQL]
// select ...
// from MEMBER member
// left outer join MEMBER_STATUS status
// on member.MEMBER_STATUS_CODE = status.MEMBER_STATUS_CODE
// where member.MEMBER_NAME like 'S%'
// order by member.BIRTHDATE desc, member.MEMBER_ID asc
}
// ===================================================================================
// SetupSelect
// ===========
/**
* o many-to-one(FK先)を結合して取得する検索: setupSelect_Xxx().
* 「会員」の親テーブルである「会員ステータス」を結合して取得。
* many-to-one(FK先)のテーブルに対するsetupSelect_Xxx()メソッドが
* それぞれ自動生成されているので、取得したいものを指定する。
* NotNull制約のFKであれば、理論的にnullが戻ることはありえない。
*/
public void test_setupSelect_Foreign() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.setupSelect_MemberStatus();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
MemberStatus memberStatus = member.getMemberStatus();
assertNotNull("NotNull制約のFKなのでnullはありえない", memberStatus);
log(member.getMemberName() + ", " + memberStatus.getMemberStatusName());
}
// [SQL]
// select ...
// from MEMBER member
// left outer join MEMBER_STATUS status
// on member.MEMBER_STATUS_CODE = status.MEMBER_STATUS_CODE
// [Description]
// A. setupSelectは「結合先テーブルのカラムをSelect句に並べてEntityにマッピングすること」まで含む。
// B. 結合自体は必ずleft outer joinにて実現される。
// C. NotNull制約のあるFKの場合は理論的にnullはありえない。
// -> NullableなFKであればnullが戻る可能性がある。
}
/**
* o one-to-oneを結合して取得する検索: setupSelect_Xxx().
* 「会員」の1:1の関係にある「会員退会情報」を結合して取得。
* 子テーブルの基点テーブルに対するFKカラムが制約的にユニーク(PK or UQ)であれば、
* one-to-oneのテーブルに対するsetupSelect_Xxx()メソッドが
* それぞれ自動生成されているので、取得したいものを指定する。
* 結合先テーブルに該当のデータが無い場合はnullが戻る。
*/
public void test_setupSelect_AsOne() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.setupSelect_MemberWithdrawalAsOne();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
boolean existsMemberWithdrawal = false;
for (Member member : memberList) {
log("[MEMBER]: " + member.getMemberName());
MemberWithdrawal memberWithdrawalAsOne = member.getMemberWithdrawalAsOne();// *Point!
if (memberWithdrawalAsOne != null) {// {1 : 0...1}の関連なのでnullチェック
log(" [MEMBER_WITHDRAWAL]: " + memberWithdrawalAsOne);
existsMemberWithdrawal = true;
}
}
assertTrue(existsMemberWithdrawal);
// [SQL]
// select ...
// from MEMBER member
// left outer join MEMBER_WITHDRAWAL withdrawal
// on member.MEMBER_ID = withdrawal.MEMBER_ID
// [Description]
// A. setupSelectは「結合先テーブルのカラムをSelect句に並べてEntityにマッピングすること」まで含む。
// B. 結合自体は必ずleft outer joinにて実現される。
// C. 結合先テーブルに該当のデータが無い場合はnullが戻る。
}
// /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 子テーブル(Referrer)の取得に関しては、BehaviorMiddleTestのLoadReferrerにて
// - - - - - - - - - -/
// ===================================================================================
// Query
// =====
// -----------------------------------------------------
// Equal
// -----
/**
* o Query-Equal条件: setXxx_Equal().
* 会員ID「3」の会員を検索。
*/
public void test_query_Equal() {
// ## Arrange ##
Integer expectedMemberId = 3;
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(expectedMemberId);// *Point!
// ## Act ##
Member member = memberBhv.selectEntityWithDeletedCheck(cb);
// ## Assert ##
assertNotNull(member);
assertEquals(expectedMemberId, member.getMemberId());
}
// /= = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// 以下、query().setXxx_Yyy()における共通の仕様を説明するExample実装
// = = = = = = = = = =/
/**
* o 二つ以上の条件を指定: setXxx_Equal(), setXxx_Equal().
* 会員ID「1」、かつ、会員アカウント「Stojkovic」の会員を検索。
* 全て「And条件」として連結される。
*/
public void test_query_Equal_TwoOrMoreCondition() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(1);// *Point!
cb.query().setMemberAccount_Equal("Pixy");// *Point!
// ## Act ##
Member member = memberBhv.selectEntityWithDeletedCheck(cb);
// ## Assert ##
assertNotNull(member);
assertEquals((Integer) 1, member.getMemberId());
assertEquals("Pixy", member.getMemberAccount());
}
/**
* o 条件にnullを設定: setXxx_Equal(null).
* 会員IDにnullを設定。
* その条件指定は無効となる。
*/
public void test_query_Equal_ArgumentNull() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(null);// *Point!
// ## Act & Assert ##
try {
memberBhv.selectEntityWithDeletedCheck(cb);
fail();
} catch (SelectEntityConditionNotFoundException e) {
// OK
log(e.getMessage());
}
// [Description]
// A. nullのものを検索したい場合は、setXxx_IsNull()を利用。
}
/**
* o 条件に空文字を設定: setXxx_Equal("").
* 会員名称に空文字を設定。
* その条件指定は無効となる。
*/
public void test_query_Equal_ArgumentEmptyString() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().setMemberName_Equal("");// *Point!
// ## Act ##
int count = memberBhv.selectCount(cb);
// ## Assert ##
assertEquals("条件なしの件数と同じであること", memberBhv.selectCount(new MemberCB()), count);
// [Description]
// A. 空文字で検索したい場合は、setXxx_Equal_EmptyString()を利用。
// -> デフォルトでは生成されないので、ビルドプロパティに
// 「torque.isMakeConditionQueryEqualEmptyString = true」
// を追加して再自動生成。(但し、需要は少ないと思われる)
}
/**
* o 同じ条件を別の値で二回設定(Override): setXxx_Equal(3), setXxx_Equal(4).
* 会員ID「3」の設定をした後、会員ID「4」を設定。
* 後勝ちになる。
* 後勝ちにならず、複数の条件が展開されるものもある。ex) LikeSearchなど
*/
public void test_query_Equal_OverrideCondition() {
// ## Arrange ##
Integer beforeMemberId = 3;
Integer afterMemberId = 4;
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(beforeMemberId);
cb.query().setMemberId_Equal(afterMemberId);// *Point!
// ## Act ##
Member member = memberBhv.selectEntityWithDeletedCheck(cb);
// ## Assert ##
assertNotNull(member);
assertEquals("後に設定した値が有効になること", afterMemberId, member.getMemberId());
}
/**
* o 同じ条件を同じ値で二回設定(Warn): setXxx_Equal(3), setXxx_Equal(3).
* 会員ID「3」の設定をした後、会員ID「3」を設定。
* Warningが出る。
*/
public void test_query_Equal_AbsolutelySameCondition() {
// ## Arrange ##
Integer beforeMemberId = 3;
Integer afterMemberId = beforeMemberId;
MemberCB cb = new MemberCB();
cb.query().setMemberId_Equal(beforeMemberId);
cb.query().setMemberId_Equal(afterMemberId);// *Point!
// ## Act ##
Member member = memberBhv.selectEntityWithDeletedCheck(cb);
// ## Assert ##
assertNotNull(member);
assertEquals(beforeMemberId, member.getMemberId());
}
/**
* o 親テーブルの条件で絞り込み検索: queryXxx().setXxx_Equal().
* 関連する会員退会情報の退会理由コードが'PRD'の会員を検索。
*/
public void test_query_queryForeign_Equal() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().queryMemberWithdrawalAsOne().setWithdrawalReasonCode_Equal_Prd(); // *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.toString());
assertNull("条件(query)利用のみの結合である", member.getMemberWithdrawalAsOne());
}
// [SQL]
// select ...
// from MEMBER dfloc
// left outer join MEMBER_WITHDRAWAL dfrel_3 on dfloc.MEMBER_ID = dfrel_3.MEMBER_ID
// where dfrel_3.WITHDRAWAL_REASON_CODE = 'PRD'
// [Description]
// A. queryXxx()しても、結合先テーブルのデータを取得(setupSelect)するわけではない。
//
// 「結合先テーブルのデータを取得(setupSelect)」という目的と
// 「結合先テーブルの条件で絞り込み・ソート(queryXxx())」という目的を
// 明確に分けている。(予期せぬ無駄なメモリ展開(パフォーマンス劣化)を抑制するため)
// 「結合」はそれら目的のための「手段」であり、ConditionBeanが自動的に解決する。
//
// B. 結合前に結合先テーブルを絞り込む場合はOnClause(or InlineView)を利用。
// -> ConditionBeanPlatinumTestにて
}
// -----------------------------------------------------
// OrderBy
// -------
/**
* o 昇順ソートを指定: addOrderBy_Xxx_Asc().
* 会員アカウントの昇順で検索。
*/
public void test_query_addOrderBy_Asc() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().addOrderBy_MemberAccount_Asc();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.getMemberAccount());
}
}
/**
* o 降順ソートを指定: addOrderBy_Xxx_Desc().
* 会員アカウントの降順で検索。
*/
public void test_query_addOrderBy_Desc() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().addOrderBy_MemberAccount_Desc();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.getMemberAccount());
}
}
/**
* o 複数条件ソートを指定: addOrderBy_Xxx_Asc().addOrderBy_Xxx_Asc().
* 会員アカウントの昇順で検索。
*/
public void test_query_addOrderBy_Desc_addOrderBy_Asc() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().addOrderBy_Birthdate_Desc();// *Point!
cb.query().addOrderBy_MemberAccount_Desc();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.getBirthdate() + ", " + member.getMemberAccount());
}
}
/**
* o 親テーブルのカラムでソート: queryXxx().addOrderBy_Xxx_Asc().
* 関連する会員退会情報の退会理由コードが'PRD'の会員を検索。
*/
public void test_query_queryForeign_addOrderBy_Asc() {
// ## Arrange ##
MemberCB cb = new MemberCB();
cb.query().queryMemberStatus().addOrderBy_DisplayOrder_Asc();// *Point!
// ## Act ##
ListResultBean memberList = memberBhv.selectList(cb);
// ## Assert ##
assertFalse(memberList.isEmpty());
for (Member member : memberList) {
log(member.getMemberName() + ", " + member.getMemberStatusCode());
assertNull("ソート利用のみの結合である", member.getMemberStatus());
}
// [SQL]
// select ...
// from MEMBER dfloc
// left outer join MEMBER_STATUS dfrel_0 on dfloc.MEMBER_STATUS_CODE = dfrel_0.MEMBER_STATUS_CODE
// order by dfrel_0.DISPLAY_ORDER asc
// [Description]
// A. queryXxx()しても、結合先テーブルのデータを取得(setupSelect)するわけではない。
//
// 「結合先テーブルのデータを取得(setupSelect)」という目的と、
// 「結合先テーブルの条件で絞り込み・ソート(queryXxx())」という目的を明確に分けている。
// (予期せぬ無駄なメモリ展開(パフォーマンス劣化)を抑制するため)
// 「結合」はそれら目的のための「手段」であり、ConditionBeanが自動的に解決する。
}
}