アイデンティティ - equals/hashCode

@Entity Beanにはequals()hashCode()を実装しないでください(代わりにEbean拡張に任せてください)。

Ebeanは、equals()hashCode()の最適な実装でエンティティBeanを拡張します。私たちはそれよりも優れた実装を試みずに、Ebeanに任せるべきです。

toString() - ゲッターの回避

toString()でゲッターメソッドの使用を避けてください。デバッガーを使用する際に、不意の遅延ロードの呼び出しを回避したいのです。つまり、デバッガーでエンティティBeanを検査すると、エンティティBeanのtoString()メソッドが暗黙的に呼び出されます。デバッガーでステップ実行する際に異なる動作をさせたくありません。EbeanはtoString()メソッドを拡張しませんが、toString()の実装がゲッターメソッドを使用している場合、それらのメソッドが遅延ロードを呼び出す可能性があり、一般的にそれを避けたいのです。

Kotlin データクラス

equals/hashCodeの実装が望ましくないため、@Entity BeanにKotlinデータクラスを使用しないでください。代わりに、エンティティBeanには通常のKotlinクラスを使用してください。

@EmbeddedId BeanにはKotlinデータクラスを使用してください。

SetよりListを優先

@OneToManyおよび@ManyToManyコレクションでは、SetよりもListの使用を優先します。Setを使用すると、equals()hashCode()が暗黙的に呼び出されます。一般的に、エンティティBeanがId値を持つまでこれらのメソッドを呼び出さない方が望ましいです。

@JoinColumn

必要がない限り@JoinColumnまたは@JoinTableを使用しないでください。命名規則によって、外部キー列と結合テーブルの良い名前が提供されるので、これらのアノテーションは、既存の外部キーなどがあり、命名規則と一致しない場合にのみ使用してください。

@Column(name=...)

データベースの列名をマップするために@Column(name=...)を使用しないでください。代わりに命名規則を使用してください。明示的な@Column(name=...)は、何らかの理由で命名規則と一致しない場合にのみ指定してください。

@Column(name="when_activated")    // This is redundant, just adds "annotation noise"
OffsetDateTime whenActivated;
@Column(name="when_activated")    // This is redundant, just adds "annotation noise"
val whenActivated: OffsetDateTime? = null

@MappedSuperclassを使用

共通のプロパティを保持するために@MappedSuperclassを活用してください。共通のマッピングスーパークラスには次のものがあるかもしれません。

...
@MappedSuperclass
public abstract class BaseDomain extends Model {

  @Id
  long id;

  @Version
  long version;

  @WhenCreated
  Instant whenCreated;

  @WhenModified
  Instant whenModified;

  // getters and setters
  ...
}
...
@MappedSuperclass
open class BaseDomain : Model() {

  @Id
  var id: Long = 0

  @Version
  var version: Long = 0

  @WhenModified
  lateinit var whenModified: Instant

  @WhenCreated
  lateinit var whenCreated: Instant

}

DDL生成

Ebeanを使用して、DBマイグレーションを含むすべてのDDLを生成します。つまり、DDLの前方生成を優先します。これにより、テストとDBマイグレーションのためにすべてのDDLが「同期」され、複数のデータベースプラットフォームのサポートや、データベースプラットフォーム間(例:MySqlからPostgres)の移行が容易になります。

手作業でDDLを作成すると、モデルと実際のデータベーススキーマの間に差異が発生する可能性が高まり、テストが難しくなる可能性があります。

NOT NULL制約の使用を促進

モデルをできる限りNOT NULLにします。可能であれば、DB列にNOT NULL制約を持たせることを優先します。必要な3値ロジックの量を減らし、「より厳格な」モデルにします。

コンストラクタの使用

コンストラクタを使用して、nullableでないプロパティ(Kotlin)を強制したり、nullableでないプロパティ(Java)を促進したりします。

たとえば、顧客が常に名前を持つ必要がある場合は、名前プロパティを受け取るコンストラクタを定義します。

@Entity
public class Customer extends BaseModel {

  @NotNull @Length(100)
  private String name;

  ...

  public Customer(String name) {
    this.name = name;
  }

  // getters and setters

}
...
@Entity
class Customer(name : String) : BaseModel() {

  @Length(100)
  var name: String = name    // Ebean knows this is Kotlin non-nullable type

}

これで、新しい顧客を作成するときは、名前を指定して作成する必要があります。

Kotlinでは、namenon-nullable型にする必要があります。EbeanはKotlinのnon-nullable型をデータベースの観点からNOT NULLとして扱い、より厳格なモデルを提供します。

ゲッターとセッター

Ebeanは拡張によって独自のアクセサメソッドを追加するため、ゲッターとセッターは必要ありません。

必要に応じて、ゲッターとセッターメソッドを省略できます。セッターメソッドは、必要に応じてthisを返す流れるようなスタイルに従うことができます。

ビルダーパターン

特定のエンティティに対して追加のビルダークラスを生成する(これにより、エンティティのすべてのプロパティが複製され、保守が難しくなる)のではなく、ビルダーパターンを使用したい場合は、エンティティBeanの「セッター」メソッドに流れるようなスタイルを使用し、thisを返すというより簡単なアプローチがあります。

@Entity
public class Customer extends BaseModel {

  @NotNull @Length(100)
  private String name;

  private String notes;

  private int level;
  ...

  public Customer(String name) {
    this.name = name;
  }

  // accessors

  public Customer setNotes(String notes) { // fluid style
    this.notes = notes;
    return this;
  }

  public Customer setLevel(int level) {  // fluid style
    this.level = level;
    return this;
  }
  ...
}

// using fluid style

Customer customer =
  new Customer("Roberto")
    .setNotes("An example")
    .setLevel(42);

一括更新クエリ

適切な場合は、一括更新ステートメントと削除ステートメントの使用を優先します。Beanをフェッチして反復処理して削除したり、反復処理してすべて同じ方法で更新したりすることを避けてください。

参照Bean

id値がある場合は、データベースをクエリする場合を除き、idで検索するクエリを実行するのではなく、参照Beanを使用します。

つまり、挿入と更新の場合、@Id値がある場合は、データベースに対して追加のクエリを実行するのではなく、外部キー値>に参照Beanを使用できます。

これはしないでください - 追加のデータベースクエリを実行するため

Order order = new Order()
order.setCustomer(database.find(Customer.class, 42)); // extra db query for customer
...
database.save(order);
val order = Order()
order.setCustomer(database.find(Customer.class, 42));
...
database.save(order)

代わりにこちらを実行してください - 参照Beanを使用

Order order = new Order()
order.setCustomer(database.getReference(Customer.class, 42)); // no extra db query
...
database.save(order);
val order = Order()
order.setCustomer(database.getReference(Customer.class, 42));
...
database.save(order)

エンティティBeanの命名

厳密な「ベストプラクティス」というよりも「Robの好み」として、エンティティBeanにDプレフィックス(ハンガリー記法)を付けることがよくあります。

  • Customerの代わりにDCustomer
  • Productの代わりにDProduct
  • Orderの代わりにDOrder

エンティティBeanは一般的に内部とみなされ、公開されません。エンティティBeanの名前は、パブリックAPIで使用したい型/名と一致/衝突することが多く、パブリックに公開されたAPI型と内部エンティティBeanとの間でマッピングすることがよくあります。

エンティティBeanにDプレフィックス(DomainのD)を付けると、一般的に

  • パブリックAPI型との名前の衝突を回避
  • マッパーで完全なパッケージ修飾型を使用する必要を回避
  • コードが内部エンティティBean(永続化ドメインオブジェクト)を使用している場合に、よりわかりやすくなる

一般的に、エンティティBeanはdomainパッケージにあります。Beanは、一般的に@OneToMany@ManyToOneなどを介して相互に関連付けられているため、それらを全体的なモデルとしてまとめておきたいと考えています。

データベース設計の考え方

エンティティBeanを構築/モデリングする際、「データベース設計の考え方」に強く賛成します。これは、主に正規化と優れたデータベース設計原則に焦点を当て、その結果、ORMやデータベースとの対話に使用している永続化レイヤーに関係なく、長期にわたって良好で持続可能なデータベース設計になることを意味します。

長期的な設計をしてください。