selectEntity(cb)

概要

基本概念

ConditionBean で null を戻す可能性のある一件検索 をします。検索結果が存在する場合としない場合で業務的な処理を分岐させる必要があるような場合に利用します。

検索結果が存在しなかったら例外になってもいい(条件付与のバグ、データ不整合、すれ違いによる削除など)、という場合は、このメソッドではなく selectEntityWithDeletedCheck() を利用して下さい。

会話上では、せれくとえんてぃてぃ と表現します。

実装方法

実装の流れ

Behaviorの selectEntity() を呼び出し、ConditionBeanを指定します。

e.g. selectEntity()の実装手順 (Eclipseでコード補完) {MEMBER} @Java
memberBhv.selE // .selE と打って enter
--
// セミコロン ';' を打って ctrl(or command) + 2 そして L
memberBhv.selectEntity(cb);
--
Member member = memberBhv.selectEntity(cb);
if (member != null) { 
    ... // 存在した場合の処理
} else {
    ... // 存在しなかった場合の処理
}

Java8 なら OptionalEntity

OptionalEntityの使い方

Java8であれば(1.0.xでもisCompatibleBeforeJava8をfalseにしていれば)、 selectEntity()の戻り値は OptionalEntity となります。Java8で導入された Optional を Entity に適用したものです。

DBFlute独自のクラスではありますが、Optionalと同じメソッド持ち、Entityならではの便利なメソッドが追加されています。 また、わかりやすい例外ハンドリングのために、NoSuchElementException の代わりに EntityAlreadyDeletedException が発生します。

スタイルごとにうまく使い分けるとよいでしょう。

一発処理でOKスタイル
その場で利用しておしまいなケース
なんども登場スタイル
そのあと何度も利用するようなケース
みんなで勢ぞろいスタイル
入り乱れて利用するようなケース
トラディショナルスタイル
いざとなったら or 迷って手が止まるくらいだったら
スタイル入りみだれスタイル
その名の通り (selectEntity()だとあまりないかも...主に関連テーブルにて)
e.g. OptionalEntityの使い方 @Java
// 戻り値は OptionalEntity だが、単純に受け取るのではなく...
OptionalEntity<Member> entity = memberBhv.selectEntity(cb); // 一件検索
...

// - - - - - - - - - - - - - - - - - - 一発処理でOKスタイル!
// (その場で利用しておしまいなケース)
//

// 業務的に必ず存在するなら (なければ例外)
memberBhv.selectEntity(cb -> cb.acceptPK(1)).alwaysPresent(member -> {
    ... = member.getMemberId();
    ... = member.getMemberName();
});

// 存在しないこともあるなら (なければ素通り)
memberBhv.selectEntity(cb -> cb.acceptPK(1)).ifPresent(member -> {
    ... = member.getMemberId();
    ... = member.getMemberName();
});

// 存在しないこともあって、"あり、なし" で分岐したいなら
memberBhv.selectEntity(cb -> cb.acceptPK(1)).ifPresent(member -> {
    ... = member.getMemberId();
    ... = member.getMemberName();
}).orElse(() -> {
    // 存在しないときの処理をする
});

// ちなみに、複数条件のConditionBeanと合わせるとこんなイメージ
memberBhv.selectEntity(cb -> { // 名前が 'S' で始める一番若い会員
    cb.query().setMemberName_LikeSearch("S", op -> op.likePrefix());
    cb.query().addOrderBy_Birthdate_Desc().withNullsLast();
    cb.fetchFirst(1);
}).alwaysPresent(member -> { // 最後の括弧の後からドット(.)と打って続ける
    ... = member.getMemberId();
    ... = member.getMemberName();
});

// そのまま他のBeanに詰め替えて戻すなら
return memberBhv.selectEntity(cb -> cb.acceptPK(1)).map(member -> {
    return new MemberWebBean(member);
}); // map()の戻り値は、OptionalThing<MemberWebBean>
...

// - - - - - - - - - - - - - - - - - - なんども登場スタイル!
// (そのあと何度も利用するようなケース)
//

// 業務的に必ず存在して、そのあと何度も登場するなら (なければ例外)
Member member = memberBhv.selectEntity(cb -> ...).get();
... = member.getMemberId();
...
if (...) {
    ... = member.getMemberName();
}
...
seaLogic.land(member.getBirthdate());
if (member.isMemberStatusCodeFormalized()) {
    ...
} else {
    for (...) {
        ... = member.getMemberAccount();
    }
}
...

// 存在しないこともあって、そのあと何度も登場するなら
// (Optionalのメソッドを二回以上呼ぶなら、Optionalのまま変数で受けとる)
OptionalEntity<Member> entity = memberBhv.selectEntity(cb -> ...);
entity.ifPresent(member -> {
    ... = member.getMemberId()
});
...
seaLogic.land(...);
entity.ifPresent(member -> {
    ... = member.getBirthdate();
}.orElse(() -> {
    ...
});
...

// - - - - - - - - - - - - - - - - - - みんなで勢ぞろいスタイル!
// (入り乱れて利用するようなケース)
//

// 業務的に必ず存在するものは、get()で取っちゃって
// 存在しないこともあるものは、Optionalのまま取っちゃって
Member member = memberBhv.selectEntity(cb -> ...).get();
Product product = productBhv.selectEntity(cb -> ...).get();
OptionalEntity<MemberAddress> optAddress = memberAddressBhv.sel...;

// いりみだれー
LocalDate birthdate = member.getBirthdate();
String productName = product.getProductName();
if (birthdate != null && product.isProductStatusCodeOnSale()) {
    optAddress.ifPresent(address -> {
        seaLogic.land(productName, address);
    }.orElse(() -> {
        seaLogic.iks(birthdate, productName);
    }
} else {
    optAddress.ifPresent(address -> {
        seaLogic.amphi(productName, address);
    }
    seaLogic.miraco(member.getFormalizedDatetime());
}
...

// - - - - - - - - - - - - - - - - - - トラディショナルスタイル!
// (いざとなったら or 迷って手が止まるくらいだったら)
//

// 存在しないこともあって、"あり、なし" で分岐したり
// (Optionalのメソッドを二回以上呼ぶならやっぱり変数で受け取る)
OptionalEntity<Member> entity = memberBhv.selectEntity(cb -> ...);
if (entity.isPresent()) {
    Member member = entity.get();
    ... = member.getMemberId();
    ... = member.getMemberName();
} else {
    // 存在しないときの処理をここで
}

// 業務的に必ず存在して、単に中のEntityを取り出すなら (なければ例外)
// (他のメソッドにがそのままEntity渡す必要があるときなど)
Member member = entity.get();
seaLogic.land(member); // Entityで受け取って、nullを受け付けないなら

// 存在しないかもしれなくって、単に中のEntityを取り出すなら (なければnull)
// (他のメソッドにnullありのEntity渡す必要があるとき)
Member member = entity.orElse(null);
seaLogic.land(member); // Entityで受け取って、null分岐を中でやっているなら

OptionalEntityの変数名は?

OptionalEntityの変数名に迷います。

ctrl+2->Lで補完すると、そのまま entity となります。 さすがにアバウト過ぎるということで optMember とかにするかもしれません。optMember の方がユニーク性が高くなり、関連テーブルの OptionalEntity が出てきたときに区別がしやすくなりますが、補完では全く支援されないのでわりと面倒かもしれません。 毎度毎度、区別の必要になるわけでもないのでなおさらなので、とりあえずは entity でいいのかもしれません。必要になったら、変数名を選択して ctrl+1->Enter で Rename すればいいかと。

ちなみに、これは基点テーブルでの話です。関連テーブルでは少しニュアンスが違うでしょう。

すぐに使って終わりなら一発処理でOKスタイル

そもそも Optional を使うときは、あまり変数に受け取られなくてもいいように実装する方がよいでしょう。 一発処理でおしまいなら、selectEntity()の戻り値でそのままコールバックスタイルに実装した方が世話ないです。 これを 一発処理でOKスタイル と呼びます。

e.g. 一発処理でOKスタイルなら @Java
// 業務的に必ず存在することが前提で、一発処理でOKスタイル (なければ例外)
memberBhv.selectEntity(cb).alwaysPresent(member -> {
    ... = member.getMemberId();
    ... = member.getMemberName();
});

// 存在しないこともあって、一発処理でOKスタイル (なければ素通り)
memberBhv.selectEntity(cb).ifPresent(member -> {
    ... = member.getMemberId();
    ... = member.getMemberName();
});

ただ、なんども登場スタイルみんなで勢ぞろいスタイル においては、業務的に存在するものは普通に get() してしまえばいいですが、存在しないこともあるものは Optional のまま受け取る必要がありそうです。みんなで勢ぞろいするときは optMember など区別できる名前にしないとですね。

これは関連テーブルでも同じようなことが言えます。

selectEntityWithDeletedCheck()もあるよ

ただ、絶対に存在することが前提の検索であれば、今まで通り selectEntityWithDeletedCheck(cb) を使うこともできます。その方が Optional のことは考えずに済みますし、可読性としても絶対に存在することが前提であることが一目瞭然で読み手にすぐに伝わります。

selectEntityWithDeletedCheck() をうまく活用していきましょう。

e.g. selectEntityWithDeletedCheck()を使ったら @Java
// 業務的に必ず存在するなら (なければ例外)
Member member = memberBhv.selectEntityWithDeletedCheck(cb); // 一件検索

// ここに来たときは絶対に存在している
... = member.getMemberId();
... = member.getMemberName();

リレーションもOptional

基点テーブルだけでなく、関連テーブルもOptionalとなります。

DBFlute on Java8

"DBFlute on Java8" ページ作りました。

メソッド仕様

引数

該当のBehaviorに対応するテーブルの ConditionBean となります。

ConditionBeanが必ず存在することが前提であるため、nullを指定した場合は例外発生します。 検索条件がない検索をする場合でも、必ずConditionBeanのインスタンスが必要です。

戻り値

該当のBehaviorに対応するテーブルの Entity となります。

検索結果が一件もない場合は、null となります。

例外

絞り込み条件が存在しなかった場合
org.seasar.dbflute.exception.SelectEntityConditionNotFoundException
この例外は、指定された ConditionBean に絞り込み条件が存在しない場合に例外となります。Query で絞り込み条件が設定されている、もしくは FetchFirst で 1 が設定されている必要があります。
"条件忘れ" や "外部から渡されたデータが予期せぬ null" の場合に、全件検索を未然に防ぎます。 たとえテスト環境だとしても、例えば100万件以上のテーブルに全件検索してしまうと処理がなかなか戻ってきませんので、 事前のチェックで比較的早い段階で例外になり検知することができます。
検索結果が複数件の場合
org.seasar.dbflute.exception.EntityDuplicatedException
この例外は、複数件(二件以上)の場合に発生します。一件検索は、必ず結果が一件になる絞り込み条件を前提としています。 その際、全てのデータを取得はせず、二件目がフェッチされた瞬間に例外になるため、比較的早い段階で例外になり検知することができます。

オーバーライド

selectList() と同じような要領となります。