2010-12-30 13 views
14

ハイバネートプロジェクトでは、エンティティはjava beansパターンを使用してコード化されています。私たちのコードには、誰かがミューテータを設定するのを忘れていて、NOT NULLフィールドのために例外が発生する場所がかなりあります。不変のJPAエンティティを持つことができますか?

ビルダーを使用してエンティティを構築しているか、不変にしている人はいますか?

私は、Java Beanパターンのスタイルではない有効なパターンを見つけようとしています。あなたの豆は不変作る場合

おかげ

答えて

14

は、あなたは、フィールドレベルのアクセスを使用する必要があり、徹底的にhere説明したように、これは問題の独自のセットが付属しています。私たちが取ったアプローチは、ビルダー/工場に必要性などのルールを適用/検証させることです。

+0

にIDを追加しますエンティティは、まだミューテータを持っていますか?あなたはちょっと精巧にできますか? –

+3

これは私たちが行うことです(これは、すべてのアプリケーションでグローバルに適用されるわけではありません)。クラスPersonを持っているとします。次に、Personオブジェクトを作成するが、それをReadablePersonにキャストするPersonFactoryBuilderを作成します。 ReadablePersonは "get"メソッドを公開するだけなので、不変にします。 DBからフェッチしている間、私たちはPersonRepository(DAOのような)を持ち、DBからその人をフェッチし、意図(更新または表示)に基づいてReadablePersonまたはPerson(変更可能)をキャストまたは返す。このデザインはjoda-timeライブラリからインスピレーションを受けています。 http://joda-time.sourceforge.net –

+1

インターフェイスへのキャスティング!抽象化のためのhooray。それはいい考えです。ありがとう。 – b3bop

8

私たちのプロジェクトでは、バニラビルダーアプローチ(@効果的なJavaを参照)を使用しています。あなたは時々、私は戻って、すべてのデータをコピーしますどのnew Builder(Person person)を紹介するお勧めしますので、あなたは、ビルダーとそれを変異させることができ、エンティティを変異させたい場合は

@Entity 
public class Person { 
    public static class Builder { 
     private String firstName; 
     private String lastName; 
     private PhoneNumber phone; 

     public Builder() {} 

     public Builder withFullName(String fullName) { 
      Preconditions.notNull(fullName); 
      String[] split = fullName.split(" "); 
      if (split == null || split.length != 2) { 
       throw new IllegalArgumentException("Full name should contain First name and Last name. Full name: " + fullName); 
      } 
      this.firstName = split[0]; 
      this.lastName = split[1]; 
      return this; 
     } 

     public Builder withPhone(String phone) { 
      // valueOf does validation 
      this.phone = PhoneNumber.valueOf(phone); 
      return this; 
     } 

     public Person build() { 
      return new Person(this); 
     } 
    } 

    //@Columns 
    private long id;//@Id 
    private String firstName; 
    private String lastName; 
    private String phoneNumber; 

    // hibernate requires default constructor 
    private Person() {} 

    private Person(Builder builder) { 
     this.firstName = Preconditions.notNull(builder.firstName); 
     this.lastName = Preconditions.notNull(builder.lastName); 
     this.phoneNumber = builder.phone != null ? builder.phone : null; 
    } 

    //Getters 
    @Nonnull 
    public String getFirstName() { return firstName;} 
    @Nonnull 
    public String getLastName() { return lastName;} 
    @Nullable 
    public String getPhoneName() { return phone;} 
    public long getId() { return id;} 
} 

次の例を考えてみましょう。もちろん、新しいエンティティが生成されるため、古いものは読み取り専用のままです。

Person.Builder personBuilder = new Person.Builder(); 
Person person = personBuilder.withFullName("Vadim Kirilchuk").withPhone("12345678").build(); 

Person modified = new Person.Builder(person).withPhone("987654321").build(); 

はまた、この例では、人は100%で不変ではありません(とすることはできません)ということに注意することが重要であるクラス:

(変異を有する)の使用は、同じくらい簡単です、すべての最初のidはjpaで設定されるため、実行時に遅延接続がフェッチされる可能性があります(必須のデフォルトコンストラクタのためにフィールドを最後にできないためです).(後者の点はマルチスレッド環境でも問題になります。 #build()の直後に別のスレッドに渡されたエンティティは、完全に構築されたオブジェクトを見ることが保証されていないため、あらゆる種類のエラーにつながる可能性があります。

JPA 2.1仕様、セクション「2.1エンティティクラス」、言う:

ませ方法やエンティティクラスの永続的なインスタンス変数が最終 ないかもしれません。

もう一つの同様のアプローチは:http://vlkan.com/blog/post/2015/03/21/immutable-persistence/

私の場合、私はちょうど、あなたが構築するビルダーを使用している代わりにドラフトの上にサービスを構築するビルダー..そう

関連する問題