ドキュメント意訳メモ:4.永続クラス

Persistent classes are classes in an application that implement the entities of the business problem (e.g. Customer and Order in an E-commerce application). Not all Hibernate works best if these classes follow some simple rules, also known as the Plain Old Java Object (POJO) programming model. However, none of these rules are hard requirements. Indeed, Hibernate3 assumes very little about the nature of your persistent objects. You may express a domain model in other ways: using trees of Map instances, for example.

Hibernateはそれらのクラスが以下に示すいくつかのシンプルなルールに従っている場合に最も良く機能します。これらのルールはPlain Old Java Object(POJO)プログラミングモデルとしても知られていますが、どれも難しい制約ではありません。それどころか、Hibernate3はあなたの永続オブジェクトの普段の書き方にはほとんど干渉しません。以下に示すのとは別なやり方でドメインモデルを定義することもできます−例えばMapインスタンスツリーを使ってもかまいません。

4.1.POJOの例

Most Java applications require a persistent class representing felines.

猫科の動物を表すJavaオブジェクトを書いてみます。

package eg;
import java.util.Set;
import java.util.Date;

public class Cat {
    private Long id; // identifier

    private Date birthdate;
    private Color color;
    private char sex;
    private float weight;
    private int litterId;

    private Cat mother;
    private Set kittens = new HashSet();

    private void setId(Long id) {
        this.id=id;
    }
    public Long getId() {
        return id;
    }

    void setBirthdate(Date date) {
        birthdate = date;
    }
    public Date getBirthdate() {
        return birthdate;
    }

    void setWeight(float weight) {
        this.weight = weight;
    }
    public float getWeight() {
        return weight;
    }

    public Color getColor() {
        return color;
    }
    void setColor(Color color) {
        this.color = color;
    }

    void setSex(char sex) {
        this.sex=sex;
    }
    public char getSex() {
        return sex;
    }

    void setLitterId(int id) {
        this.litterId = id;
    }
    public int getLitterId() {
        return litterId;
    }

    void setMother(Cat mother) {
        this.mother = mother;
    }
    public Cat getMother() {
        return mother;
    }
    void setKittens(Set kittens) {
        this.kittens = kittens;
    }
    public Set getKittens() {
        return kittens;
    }
    
    // addKitten not needed by Hibernate
    public void addKitten(Cat kitten) {
    	kitten.setMother(this);
	kitten.setLitterId( kittens.size() ); 
        kittens.add(kitten);
    }
}

この中には以下に示す4つのルールが含まれています:

4.1.1.引数なしのコンストラクタを実装する

Cat has a no-argument constructor. All persistent classes must have a default constructor (which may be non-public) so that Hibernate can instantiate them using Constructor.newInstance(). We strongly recommend having a default constructor with at least package visibility for runtime proxy generation in Hibernate.

Catは引数なしのコンストラクタを持っています。全ての永続クラスはデフォルトコンストラクタ(publicでなくても構いません)を持たなくてはなりません。
これによりHibernateConstructor.newInstance()を使って永続クラスのインスタンスを生成します。Hibernateの代理生成機能のために最低でもパッケージレベルの可視性を持つコンストラクタを定義しておくことを強くお勧めします。

4.2. 継承の実装

A subclass must also observe the first and second rules. It inherits its identifier property from the superclass, Cat.

サブクラスもまた最初と二番目のルールに従っていなければなりません。識別属性はスーパークラスであるCatから継承されます。

package eg;

public class DomesticCat extends Cat {
        private String name;

        public String getName() {
                return name;
        }
        protected void setName(String name) {
                this.name=name;
        }
}

4.3. equals()メソッドとhashCode()メソッドの実装

You have to override the equals() and hashCode() methods if you

    • intend to put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and
    • intend to use reattachment of detached instances

Hibernate guarantees equivalence of persistent identity (database row) and Java identity only inside a particular session scope. So as soon as we mix instances retrieved in different sessions, we must implement equals() and hashCode() if we wish to have meaningful semantics for Sets.

もしあなたが以下の両方をするつもりであるのなら、equals()hashCode()をオーバーライドしなければなりません:

Hibernateはデータベースの行の識別子と単一のセッションスコープの中にのみ存在するJavaオブジェクトの識別子が同値であることを保証するため、異なるセッションにおいて取得したインスタンスを混在させる場合、Set内に意味的に正しく保持するためにequals()hashCode()を実装しなければなりません。

The most obvious way is to implement equals()/hashCode() by comparing the identifier value of both objects. If the value is the same, both must be the same database row, they are therefore equal (if both are added to a Set, we will only have one element in the Set). Unfortunately, we can't use that approach with generated identifiers! Hibernate will only assign identifier values to objects that are persistent, a newly created instance will not have any identifier value! Furthermore, if an instance is unsaved and currently in a Set, saving it will assign an identifier value to the object. If equals() and hashCode() are based on the identifier value, the hash code would change, breaking the contract of the Set. See the Hibernate website for a full discussion of this problem. Note that this is not a Hibernate issue, but normal Java semantics of object identity and equality.

最も簡単に思いつくequals()/hashCode()の実装は二つのオブジェクトの識別属性の値を比較することです。もし値が同じならば同じデータベースの行であり、それゆえ等しいということになります(両方がSetに追加された場合、そのSet内には1つの要素しか保持されません)。しかし残念ながらこのアプローチを生成された識別子に対して使うことはできないのです!Hibernateはpersistant状態にあるオブジェクトにのみ識別子を割り当てるため、新しく生成されたインスタンスは識別子の値を持たないからです!さらに、もしまだ保存されていないインスタンスSetに保持されていた場合、それを保存することによって識別子が発行されます。もしequals()/hashCode()の実装が識別子の値に基づくものであった場合、これによりハッシュコードの値が変わり、Setの契約を満たすことができなくなります。この問題に関してはHibernateのウェブサイトの議論を見てください。この問題はHibernateに由来するものではなく、Javaオブジェクトの識別と意味的同値性に関する普遍的な問題なのです。

We recommend implementing equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key, a key that would identify our instance in the real world (a natural candidate key):

我々はビジネスキーの比較によってequals()/hashCode()を実装することをおすすめします。ビジネスキーの比較というのはequals()メソッドがビジネスキー、すなわち現実世界での識別子となる属性だけを比較するということです:

public class Cat {

    ...
    public boolean equals(Object other) {
        if (this == other) return true;
        if ( !(other instanceof Cat) ) return false;

        final Cat cat = (Cat) other;

        if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
        if ( !cat.getMother().equals( getMother() ) ) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getMother().hashCode();
        result = 29 * result + getLitterId();
        return result;
    }

}

Note that a business key does not have to be as solid as a database primary key candidate (see Section 11.1.3, “Considering object identity”). Immutable or unique properties are usually good candidates for a business key.

ビジネスキーはプライマリキーとなれるほど厳密なものでなくても構いません(11.1.3 "オブジェクトの識別子を考える"を参照)。ビジネスキーの候補としては不変もしくはユニークな属性が適しています。



...書きかけ。