すべての String プロパティをローカライズする必要があるアプリケーションを作成しています。つまり、利用可能なロケールごとに異なる値を格納する必要があります。簡単な解決策は Map を使用することです。これは Hibernate で簡単にマップできますが、Java プログラマーには適していません。
public class Product {
private Map<Locale, String> description;
private Map<Locale, String> note;
そこで、ロケールごとに異なる文字列を保持できる LocalString オブジェクトを実装しました。
public class LocalString {
private Map<Locale, String> localStrings;
ドメイン オブジェクトは次のようになります。
public class Product {
private LocalString description;
private LocalString note;
これらのオブジェクトを Hibernate アノテーションで最適にマッピングするにはどうすればよいですか?
LocalString をコンポーネントとして使用すると、最適なマッピングが行われると思います。
@Embeddable
public class LocalString {
private Map<Locale, String> localStrings;
@ElementCollection
public Map<Locale, String> getLocalStrings() {
return localStrings;
}
...
@Entity
public class Product {
private Long id;
private LocalString description;
private LocalString note;
@Embedded
public LocalString getDescription() {
return description;
}
hbm2ddl ant タスクは、"Products" テーブルと、キーと値の列を含む "Products_localStrings" テーブルの 2 つのテーブルを作成します。2 番目のプロパティのゲッターを追加すると、すべてが壊れます。
@Embedded
public LocalString getNote() {
return note;
}
2 番目のプロパティはスキーマに表示されません。@AttributesOverride タグを使用して 2 つの列に異なる名前を定義しようとしましたが、生成されたスキーマは正しくありません。
@Embedded
@AttributeOverrides({
@AttributeOverride(name="localStrings", column=@Column(name="description"))
})
public LocalString getDescription() {
return description;
}
@Embedded
@AttributeOverrides({
@AttributeOverride(name="localStrings", column=@Column(name="note"))
})
public LocalString getNote() {
return note;
}
生成されたスキーマでは、キー列が消えており、主キーに「説明」が使用されていますが、これは正しくありません。
create table Product_localStrings (Product_id bigint not null, note varchar(255), description varchar(255), primary key (Product_id, description));
これを修正する方法はありますか?
LocalString をエンティティとして使用して、埋め込みコンポーネントを使用しないほうがよいでしょうか?
代替デザインはありますか?
ありがとうございました。
編集
xml マッピングを試してみたところ、適切なスキーマを取得できましたが、休止状態が 1 つではなく 2 つの挿入を生成するため、挿入は主キー違反で失敗します
<hibernate-mapping>
<class name="com.yr.babka37.demo.entity.Libro" table="LIBRO">
<id name="id" type="java.lang.Long">
<column name="ID" />
<generator class="org.hibernate.id.enhanced.SequenceStyleGenerator"/>
</id>
<property name="titolo" type="java.lang.String">
<column name="TITOLO" />
</property>
<component name="descrizioni" class="com.yr.babka37.entity.LocalString">
<map name="localStrings" table="libro_strings" lazy="true" access="field">
<key>
<column name="ID" />
</key>
<map-key type="java.lang.String"></map-key>
<element type="java.lang.String">
<column name="descrizione" />
</element>
</map>
</component>
<component name="giudizi" class="com.yr.babka37.entity.LocalString">
<map name="localStrings" table="libro_strings" lazy="true" access="field">
<key>
<column name="ID" />
</key>
<map-key type="java.lang.String"></map-key>
<element type="java.lang.String">
<column name="giudizio" />
</element>
</map>
</component>
</class>
</hibernate-mapping>
スキーマは
create table LIBRO (ID bigint not null auto_increment, TITOLO varchar(255), primary key (ID));
create table libro_strings (ID bigint not null, descrizione varchar(255), idx varchar(255) not null, giudizio varchar(255), primary key (ID, idx));
alter table libro_strings add index FKF576CAC5BCDBA0A4 (ID), add constraint FKF576CAC5BCDBA0A4 foreign key (ID) references LIBRO (ID);
ログ:
DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, descrizione) values (?, ?, ?)
DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, giudizio) values (?, ?, ?)
WARN o.h.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
ERROR o.h.util.JDBCExceptionReporter - Duplicate entry '5-ita_ITA' for key 'PRIMARY'
次のような単一の挿入のみを生成するように休止状態に指示するにはどうすればよいですか?
insert into libro_strings (ID, idx, descrizione, giudizio) values (?, ?, ?, ?)
2011.Apr.05 編集
2 つの問題に遭遇するまで、しばらく (@ElementCollection で注釈を付けて) Map ソリューションを使用してきました。
- 現在の Criteria API は埋め込みコレクションでは機能しません: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3646
- 現在の Hibernate Search (Lucene) API は、埋め込みコレクションでも機能しません: http://opensource.atlassian.com/projects/hibernate/browse/HSEARCH-566
Criteria の代わりに HQL を使用したり、独自の FieldBridge を定義して Lucene でマップを処理したりするなど、多くの回避策があることは知っていますが、回避策は好きではありません。次の問題が発生するまで機能します。だから私は今このアプローチに従っています:
ロケールと値を保持するクラス "LocalString" を定義します (ロケールは実際には ISO3 コードです)。
@MappedSuperclass
public class LocalString {
private long id;
private String localeCode;
private String value;
次に、ローカライズしたいプロパティごとに、空の LocalString のサブクラスを定義します。
@Entity
public class ProductName extends LocalString {
// Just a placeholder to name the table
}
これで、Product オブジェクトで使用できます。
public class Product {
private Map<String, ProductName> names;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name="Product_id")
@MapKey(name="localeCode")
protected Map<String, ProductName> getNames() {
return names;
}
このアプローチでは、ローカライズする必要があるプロパティごとに空のクラスを作成する必要があります。これは、そのプロパティの一意のテーブルを作成するために必要です。利点は、条件と検索を制限なく使用できることです。