Hibernate.orgCommunity Documentation
永続クラスはビジネス上の問題のエンティティ(例えば、 E コマースアプリケーションの顧客や注文) を実装するアプリケーションのクラスです。永続クラスのすべてのインスタンスが永続状態であると見なされるわけではありません。インスタンスは逆に一時的(transient)であったり、分離状態(detached)であったりするかもしれません。
Plain Old Java Object (POJO)プログラミングモデルとしても知られるいくつかの単純なルールに従うなら、 Hibernate は最もよく働きます。しかしこれらのルールは難しいものではありません。実際 Hibernate3 は永続オブジェクトの性質にほとんど何の前提も置いていません。ドメインモデルは他の方法で表現することもできます。例えば Map
インスタンスのツリーを使う方法があります。
Most Java applications require a persistent class representing felines. For example:
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);
}
}
The four main rules of persistent classes are explored in more detail in the following sections.
Cat
には引数のないコンストラクタがあります。 Hibernate が Constructor.newInstance()
を使って永続クラスのインスタンス化を行えるように、すべての永続クラスにはデフォルトコンストラクタ (public でなくても構いません) がなければなりません。 Hibernate の実行時プロキシ生成のために、少なくとも package の可視性を持つデフォルトコンストラクタを強くお勧めします。
Cat
has a property called id
. This property maps to the primary key column of a database table. The property might have been called anything, and its type might have been any primitive type, any primitive "wrapper" type, java.lang.String
or java.util.Date
. If your legacy database table has composite keys, you can use a user-defined class with properties of these types (see the section on composite identifiers later in the chapter.)
識別子プロパティは厳密にはオプションです。これを省略して、 Hibernate に内部的にオブジェクトの識別子を追跡させることは可能です。しかしお勧めはしません。
実際に、識別子プロパティを宣言するクラスだけが利用可能な機能がいくつかあります:
Transitive reattachment for detached objects (cascade update or cascade merge) - see 「連鎖的な永続化」
Session.saveOrUpdate()
Session.merge()
永続クラスには、一貫した名前の識別子プロパティを定義することをお勧めします。さらに null 値を取れる(つまりプリミティブではない)型を使った方がよいでしょう。
Hibernate の中心的な特徴である プロキシ は、永続クラスが final でないこと、またはメソッドを全部 public で宣言しているインターフェースが実装されているかに依存しています。
Hibernate でインターフェースを実装していない final
クラスを永続化することはできますが、遅延関連フェッチに対してプロキシを使うことはできなくなります。これはパフォーマンスチューニングへの選択肢を狭めることになります。
final ではないクラスで public final
メソッドを定義することも避けるべきです。 public final
メソッドを持つクラスを使いたければ、 lazy="false"
と設定して明示的にプロキシを無効にしなければなりません。
Cat
declares accessor methods for all its persistent fields. Many other ORM tools directly persist instance variables. It is better to provide an indirection between the relational schema and internal data structures of the class. By default, Hibernate persists JavaBeans style properties and recognizes method names of the form getFoo
, isFoo
and setFoo
. If required, you can switch to direct field access for particular properties.
プロパティは public で宣言する必要は ありません 。 Hibernate はデフォルトで、 protected
もしくは private
の get / set のペアを持つプロパティを永続化することができます。
サブクラスも1番目と2番目のルールを守らなければなりません。サブクラスはスーパークラス Cat
から識別子プロパティを継承します。
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}
以下の条件の場合、 equals()
と hashCode()
メソッドをオーバーライドしなければなりません、
永続クラスのインスタンスを Set
に置く場合。 (これは多値の関連を表現するおすすめの方法です) そして同時に
分離インスタンスをセッションへ再追加する場合。
Hibernate は、永続 ID (データベースの行)と、特定のセッションスコープ内に限定ですが Java ID とが等価であることを保証します。ですから異なるセッションで検索したインスタンスを組み合わせる場合、 Set
に意味のあるセマンティクスを持たせようと思っているならすぐに equals()
と hashCode()
を実装しなければなりません。
最も明白な方法は、両方のオブジェクトの識別子の値の比較によって equals()
と hashCode()
を実装する方法です。値が同じなら、両者はデータベースの同じ行でなければならないため等しくなります。 (両者が Set
に追加されても、 Set
には1個の要素しかないことになります) 残念なことに、生成された識別子にはこのアプローチを使うことができません。 Hibernate は永続化されたオブジェクトへ識別子の値を代入するだけであり、新しく作成されたインスタンスはどのような識別子の値も持っていません。さらに、インスタンスがセーブされておらず、現在 Set
の中にあれば、セーブするとオブジェクトへ識別子の値を代入することになります。もし equals()
と hashCode()
が識別子の値に基づいているなら、ハッシュコードが変更されると Set
の規約が破られます。この問題についての完全な議論は、 Hibernate のウェブサイトを見てください。これは Hibernate の問題ではなく、オブジェクトの同一性と等価性についての、通常の Java のセマンティクスであることに注意してください。
ビジネスキーの等価性 を使って、 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;
}
}
A business key does not have to be as solid as a database primary key candidate (see 「オブジェクト識別子を考える」). Immutable or unique properties are usually good candidates for a business key.
The following features are currently considered experimental and may change in the near future.
永続エンティティは、必ずしも実行時に POJO クラスや JavaBean オブジェクトで表現する必要はありません。 Hibernate は(実行時に Map
の Map
を使う)動的モデルと、 DOM4J ツリーとしてのエンティティの表現もサポートします。このアプローチを使うと永続クラスを書かず、マッピングファイルだけを書くことになります。
By default, Hibernate works in normal POJO mode. You can set a default entity representation mode for a particular SessionFactory
using the default_entity_mode
configuration option (see 表3.3「Hibernate 設定プロパティ」).
以下の例では Map
を使った表現を紹介します。まずマッピングファイルで、クラス名の代わりに(またはそれに加えて) entity-name
を定義しなければなりません:
<hibernate-mapping>
<class entity-name="Customer">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping
>
関連がターゲットのクラス名を使って定義していたとしても、関連のターゲット型も POJO ではなく動的なエンティティでも構わないことに注意してください。
SessionFactory
に対してデフォルトのエンティティモードを dynamic-map
に設定した後、実行時に Map
の Map
を使うことができます:
Session s = openSession();
Transaction tx = s.beginTransaction();
// Create a customer
Map david = new HashMap();
david.put("name", "David");
// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Link both
david.put("organization", foobar);
// Save both
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
動的なマッピングの利点は、エンティティクラスの実装を必要としないため、プロトタイピングに要するターンアラウンドタイムが早いということです。しかしコンパイル時の型チェックがないので、実行時に非常に多くの例外処理を扱わなければならないでしょう。 Hibernate マッピングのおかげで、データベーススキーマは容易に正規化でき、健全になり、後で適切なドメインモデルの実装を追加することが可能になります。
エンティティ表現モードは Session
ごとに設定することも可能です。
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continue on pojoSession
EntityMode
を使った getSession()
の呼び出しは SessionFactory
ではなく Session
APIにあることに注意してください。その方法では、新しい Session
は、ベースとなる JDBC コネクション、トランザクション、その他のコンテキスト情報を共有します。これは2番目の Session
では flush()
と close()
を呼ぶ必要がないということ、そのためトランザクションとコネクションの管理を1番目の作業単位(Unit of Work)に任せることができるということです。
More information about the XML representation capabilities can be found in 19章XML マッピング.
org.hibernate.tuple.Tuplizer
とそのサブインターフェースは、表現の org.hibernate.EntityMode
を利用して、データ断片のある表現の管理に責任を持ちます。与えられたデータ断片をデータ構造として考えるなら、 Tuplizer はそのようなデータ構造をどのように作成するかを知り、そのようなデータ構造からどのように値を抽出し、注入するかを知っています。例えば POJO エンティティモードでは、対応する Tuplizer はコンストラクタを通して、 POJO をどのように作成するか、定義されたプロパティアクセサを使い、 POJO プロパティにどのようにアクセスするかを知ります。
Tuplizer には二つのハイレベルの型があります。それらは、org.hibernate.tuple.entity.EntityTuplizer
と org.hibernate.tuple.component.ComponentTuplizer
インターフェースで表現されます。 EntityTuplizer
は上で述べたようなエンティティに関する契約の管理に責任を持ちます。一方、 ComponentTuplizer
はコンポーネントに関する契約の管理に責任を持ちます。
ユーザーは独自の Tuplizer に差し替えることも可能です。おそらく dynamic-map entity-mode の際に java.util.HashMap
を使うのではなく、 java.util.Map
の実装が必要でしょう。もしくは、おそらくデフォルトのものではなく、別のプロキシ生成戦略の定義が必要でしょう。両者とも、カスタムの Tuplizer 実装を定義することで達成されます。 Tuplizer の定義は、管理しようとするエンティティやコンポーネントのマッピングに結び付けられます。顧客エンティティの例は以下になります:
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl
extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
// override the buildInstantiator() method to plug in our custom map...
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// override the generateMap() method to return our custom map...
protected final Map generateMap() {
return new CustomMap();
}
}
}
The org.hibernate.EntityNameResolver
interface is a contract for resolving the entity name of a given entity instance. The interface defines a single method resolveEntityName
which is passed the entity instance and is expected to return the appropriate entity name (null is allowed and would indicate that the resolver does not know how to resolve the entity name of the given entity instance). Generally speaking, an org.hibernate.EntityNameResolver
is going to be most useful in the case of dynamic models. One example might be using proxied interfaces as your domain model. The hibernate test suite has an example of this exact style of usage under the org.hibernate.test.dynamicentity.tuplizer2. Here is some of the code from that package for illustration.
/**
* A very trivial JDK Proxy InvocationHandler implementation where we proxy an interface as
* the domain model and simply store persistent state in an internal Map. This is an extremely
* trivial example meant only for illustration.
*/
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}
public String getEntityName() {
return entityName;
}
public HashMap getData() {
return data;
}
}
/**
*
*/
public class ProxyHelper {
public static String extractEntityName(Object object) {
// Our custom java.lang.reflect.Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}
// various other utility methods ....
}
/**
* The EntityNameResolver implementation.
* IMPL NOTE : An EntityNameResolver really defines a strategy for how entity names should be
* resolved. Since this particular impl can handle resolution for all of our entities we want to
* take advantage of the fact that SessionFactoryImpl keeps these in a Set so that we only ever
* have one instance registered. Why? Well, when it comes time to resolve an entity name,
* Hibernate must iterate over all the registered resolvers. So keeping that number down
* helps that process be as speedy as possible. Hence the equals and hashCode impls
*/
public class MyEntityNameResolver implements EntityNameResolver {
public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();
public String resolveEntityName(Object entity) {
return ProxyHelper.extractEntityName( entity );
}
public boolean equals(Object obj) {
return getClass().equals( obj.getClass() );
}
public int hashCode() {
return getClass().hashCode();
}
}
public class MyEntityTuplizer extends PojoEntityTuplizer {
public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
public EntityNameResolver[] getEntityNameResolvers() {
return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}
public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}
...
}
In order to register an org.hibernate.EntityNameResolver
users must either:
Implement a custom Tuplizer, implementing the getEntityNameResolvers
method.
Register it with the org.hibernate.impl.SessionFactoryImpl
(which is the implementation class for org.hibernate.SessionFactory
) using the registerEntityNameResolver
method.
製作著作 © 2004 Red Hat, Inc.