/** 顧客メールアドレス */
@Embeddable
public class CustomerMail extends ValueObject<ContractNo> {
@Column(name = "customerMail", nullable = false, length = 50)
private final String value;
//コンストラクタ
public CustomerMail(final String value) {
this.value = value;
//@column設定による値評価関数
validate();
//文字列評価関数
Validator.Mail.is(value);
}
}
-
クラス名称はフル名を指定すべき
-
Mailという短縮名ではなく、CustomerMailと名前空間的なprefixを付加した方がよい
-
インスタンスフィールド名の@Column, nameアトリビュートもフル名
-
DBカラム名となるため重複しないように
-
@Columnのnameアトリビュートの設定は必須
-
VOクラス名をリファクタリングで変更してもDBカラム名への影響がない
-
インスタンスフィールド名はvalueで統一する
-
Repo等でフィールド名の指定が自然となり、またフィールド名を意識させないため
return Contract.find("lower(mail.value) like ?", strMail.toLowerCase()).first();
*Hibernateはオブジェクト評価できるので、通常はできるだけVOレベルで評価する
return Contract.find("mail", new CustomerMail(strMail).first();
-
validate()関数にて、@Column設定値を元に値評価する
-
new Valid(value).notNull().length(10) などと@Column設定値を意識する必要がない
-
Validatorクラスに汎用的な文字列評価を登録済み
-
メールアドレス形式、電話番号、郵便番号など
-
Enumは@Enumeratedを利用しない方がよいかも
-
@Enumeratedにはnameアトリビュートがない
-
インスタンスフィールド名がDBカラム名となりリファクタ時のインパクトが大きい
-
アクセサはEnumで行い、フィールドはVOとする形の方がよさそう
/** 受注エンティティ */
@Entity
public class Order extends EntityModels<Order> {
/** 顧客メールアドレス */
@Embedded
private final CustomerMail mail;
//アクセサも短縮名
public CustomerMail mail(){
return mail;
}
-
インスタンスフィールド名は短縮名
-
Customerクラスという名前空間が存在するので、短縮名の方が自然
-
アクセサ名も同様
-
@AttributeOverrideでの上書きはお勧めできない
-
validate()評価が正しく処理されない
-
同じメールという値オブジェクトでまとめたい気になるが、異なる振る舞いは定義できない
/** 顧客エンティティ */
@Entity
public class Customer extends EntityModels<Customer> {
/** 顧客メールアドレス */
@Embedded
private final CustomerMail mail;
/** 担当メールアドレス */
@Embedded
@AttributeOverride(name = "employeeMail", column = @Column(nullable = false))
private final CustomerMail empMail;
VOをそのままエンティティに追加すると、フィールドを多く持つエンティティはコード量が大量になり可読性が下がる。一定のくくりでVOをグルーピングする。
/** 顧客情報VOグループ */
@Embeddable
public class Customer extends ValueObject<Customer> {
/** 顧客名 */
@Embedded
private final CustomerName name;
/** 顧客メールアドレス */
@Embedded
private final CustomerMail mail;
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
/** 受注エンティティ */
@Entity
public class Order extends EntityModels<Order> {
/** 顧客情報 */
@Embedded
private final Customer customer;
- VOグループもVOの一種なので、Immutable属性を持つ
- つまり変更タイミングが同じものをグルーピングする(更新せず新たに生成する)
- 変更タイミングが異なるVOをグルーピングしたいのであれば、エンティティ分離する
論理的な意味合いでフィールドを切り出したければ、サブエンティティ化する
/** 顧客エンティティ */
@Entity
//各受注エンティティに対して1つの顧客エンティティしか関連付かない
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"orderId"})})
class Customer extends EntityModels<Customer> {
/** 受注 */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "orderId")
@ForeignKey(name = "order")
private final Order order;
/** 顧客名 */
@Embedded
private final CustomerName name;
/** 顧客メール */
@Embedded
private CustomerMail mail;
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
/** 受注エンティティ */
@Entity
public class Order extends EntityModels<Order> {
/** 受注番号 */
@Embedded
private final OrderNo no;
/** 顧客情報 */
@OneToOne(mappedBy = "order", fetch = FetchType.LAZY)
private Customer customer;
//ファクトリ
public Order factory(OrderNo no, CustomerName name, CustomerMail mail){
Order order = Order(no).save();
new Customer(order, name, mail).save();
}
//コンストラクタ
private Order(OrderNo no){
this.no = no;
}
-
エンティティで切り出せば、論理的な意味合いでは同じだが、変更タイミングが異なる物も包含できる
-
顧客名は変更されないが、顧客メールは変更されるなど
-
エンティティ側での生成時にサブエンティティを処理しないといけないため、ファクトリを使うべき
-
この例だと受注エンティティを作成したあとに、顧客サブエンティティを生成する必要がある