私は一日のほとんどの間、これについて頭を悩ませてきましたが、この問題に対する答えを見つけることができません.
次のような PostgreSQL スキーマがあります。
+---------+ 1-n +-------------+ 1-1 +------+ | | 製品 |-------->| 製品仕様 |-------->| 仕様 | +---------+ +-------------+ +------+
これは、製品とその仕様のリストとの間の 1 対多の関係を表します (仕様テーブルの外部キーを製品テーブルに使用しない理由は、仕様が製品の継承にないものに属する可能性があるためです)ツリー、これらのリンクは他の交差テーブルで表されます)。
各仕様は仕様クラス (Weight、Length、NumberOfThings など) のサブクラスであり、問題のサブクラスの名前は Spec テーブルに格納されます。各製品には仕様のコレクションがありますが、仕様の各サブクラスは一度しか表示できません。製品には 1 つの重量のみを指定できます (ただし、実際の製品の重量と宅配業者が送料を計算するための出荷重量が必要な場合は、Weight 仕様から ActualWeight と ShippingWeight を単純にサブクラス化できます)。
最も単純なケースである Product クラスの Set を使用すると、製品テーブルの Hibernate クエリからオブジェクト グラフを正しく構築できます。ただし、代わりに Map を使用したいので、特定の仕様に直接対処できます。計画はクラス名をキーとして使用することでしたが、それを機能させるために深刻な問題が発生しています。Java クラス名をキーとして使用する方法がわかりません。データベースに格納されているクラス名をマップ キーとして使用しようとすると問題が発生します。
現在実装されているように、仕様と製品を個別に照会できます (製品と仕様の間のマッピングを実装するコードをコメントアウトした場合)。セットを使えばスペックが埋め込まれた商品も検索できるのですが、MapKeyをスペックのクラス名に設定したマップを使うと例外が出てしまいます。
2013 年 9 月 1 日 1:25:55 AM org.hibernate.util.JDBCExceptionReporter logExceptions 警告: SQL エラー: 0、SQLState: 42P01 2013 年 9 月 1 日 1:25:55 AM org.hibernate.util.JDBCExceptionReporter logExceptions 重大: エラー:関係「仕様」は存在しません 位置: 424
次のように、クラスに注釈を付けました(切り詰めました)。製品クラス:
@Entity
@Table (
name="products",
schema="sellable"
)
public abstract class Product extends Sellable {
private Map <String, Specification> specifications = new HashMap <> ();
@OneToMany (fetch = FetchType.EAGER)
@Cascade (CascadeType.SAVE_UPDATE)
@JoinTable (
schema = "sellable",
name = "productspecifications",
joinColumns = {@JoinColumn (name = "sll_id")},
inverseJoinColumns = {@JoinColumn (name = "spc_id")})
@MapKey (name = "className")
private Map <String, Specification> getSpecifications () {
return this.specifications;
}
private Product setSpecifications (Map <String, Specification> specs) {
this.specifications = specs;
return this;
}
}
そして仕様クラス:
@Entity
@Table (
name="specifications",
schema="sellable",
uniqueConstraints = @UniqueConstraint (columnNames="spc_id")
)
@Inheritance (strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn (name = "spc_classname", discriminatorType=DiscriminatorType.STRING)
public abstract class Specification implements Serializable {
private Integer specId = null;
private String className = null;
@Id
@Column (name="spc_id", unique=true, nullable=false)
@SequenceGenerator (name = "specifications_spc_id_seq", sequenceName = "sellable.specifications_spc_id_seq", allocationSize = 1)
@GeneratedValue (strategy = GenerationType.SEQUENCE, generator = "specifications_spc_id_seq")
public Integer getSpecId () {
return this.specId;
}
private Specification setSpecId (Integer specId) {
this.specId = specId;
return this;
}
@Column (name="spc_classname", insertable = false, updatable = false, nullable = false)
public String getClassName () {
return this.className;
}
private void setClassName (String className) {
this.className = className;
}
}
DB スキーマは次のようになります。
CREATE TABLE sellable.sellables
(
sll_id serial NOT NULL, -- Sellable ID
sll_date_created timestamp with time zone NOT NULL DEFAULT now(), -- Date the item was created
sll_date_updated timestamp with time zone NOT NULL DEFAULT now(), -- Date the item was last updated
sll_title character varying(255) NOT NULL, -- Title of the item
sll_desc text NOT NULL, -- Textual description of the item
CONSTRAINT sellables_pkey PRIMARY KEY (sll_id)
)
CREATE TABLE sellable.products
(
sll_id integer NOT NULL, -- Sellable ID
mfr_id integer NOT NULL, -- ID of the product manufacturer
CONSTRAINT products_pkey PRIMARY KEY (sll_id),
CONSTRAINT products_mfr_id_fkey FOREIGN KEY (mfr_id)
REFERENCES sellable.manufacturers (mfr_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT products_sll_id_fkey FOREIGN KEY (sll_id)
REFERENCES sellable.sellables (sll_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE sellable.specifications
(
spc_id serial NOT NULL, -- Specification ID
spc_classname character varying(127) NOT NULL, -- Specification subclass
CONSTRAINT specifications_pkey PRIMARY KEY (spc_id)
)
CREATE TABLE sellable.productspecifications
(
ps_id serial NOT NULL, -- Primary key
sll_id integer NOT NULL, -- Product the specification is linked to
spc_id integer NOT NULL, -- Specification the product is associated with
CONSTRAINT productspecifications_pkey PRIMARY KEY (ps_id),
CONSTRAINT productspecifications_sll_id_fkey FOREIGN KEY (sll_id)
REFERENCES sellable.products (sll_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT productspecifications_spc_id_fkey FOREIGN KEY (spc_id)
REFERENCES sellable.specifications (spc_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT productspecifications_spc_id_key UNIQUE (spc_id)
)
Hibernate が生成するクエリを以下に示します (要約されていないクエリに問題がある場合に備えて、クラスの方法でこれをトリミングしていません)。明らかな問題の 1 つは、スキーマ名を挿入せずに仕様テーブルをクエリしようとしていることです。
select
bicycle0_.sll_id as sll1_0_3_,
bicycle0_2_.sll_date_created as sll2_0_3_,
bicycle0_2_.sll_date_updated as sll3_0_3_,
bicycle0_2_.sll_desc as sll4_0_3_,
bicycle0_2_.sll_title as sll5_0_3_,
bicycle0_1_.mfr_id as mfr2_1_3_,
bicycle0_.btp_id as btp2_2_3_,
manufactur1_.mfr_id as mfr1_4_0_,
manufactur1_.mfr_name as mfr2_4_0_,
specificat2_.sll_id as sll1_5_,
specificat3_.spc_id as spc2_5_,
(select
a9.spc_classname
from
specifications a9
where
a9.spc_id=specificat2_.spc_id) as formula0_5_,
specificat3_.spc_id as spc2_5_1_,
specificat3_.spc_classname as spc1_5_1_,
specificat3_1_.dec_value as dec1_6_1_,
specificat3_2_.bol_value as bol1_7_1_,
specificat3_3_.int_value as int1_8_1_,
specificat3_4_.str_value as str1_9_1_,
bicycletyp4_.btp_id as btp1_3_2_,
bicycletyp4_.btp_name as btp2_3_2_
from
sellable.bicycles bicycle0_
inner join
sellable.products bicycle0_1_
on bicycle0_.sll_id=bicycle0_1_.sll_id
inner join
sellable.sellables bicycle0_2_
on bicycle0_.sll_id=bicycle0_2_.sll_id
left outer join
sellable.manufacturers manufactur1_
on bicycle0_1_.mfr_id=manufactur1_.mfr_id
left outer join
sellable.productspecifications specificat2_
on bicycle0_.sll_id=specificat2_.sll_id
left outer join
sellable.specifications specificat3_
on specificat2_.spc_id=specificat3_.spc_id
left outer join
sellable.specdecimalvalues specificat3_1_
on specificat3_.spc_id=specificat3_1_.spc_id
left outer join
sellable.specbooleanvalues specificat3_2_
on specificat3_.spc_id=specificat3_2_.spc_id
left outer join
sellable.specintegervalues specificat3_3_
on specificat3_.spc_id=specificat3_3_.spc_id
left outer join
sellable.specstringvalues specificat3_4_
on specificat3_.spc_id=specificat3_4_.spc_id
left outer join
sellable.bicycletypes bicycletyp4_
on bicycle0_.btp_id=bicycletyp4_.btp_id
where
bicycle0_.sll_id=?
問題はサブクエリにあり、仕様テーブル名の前にスキーマが追加されていません。
正しいクエリを取得する方法、またはクラス名を Java マップ キーとして直接使用する方法を誰かが知っている場合は、教えていただければ幸いです。
編集:セットの代わりにマップを使用したい理由は、仕様コレクション内のアイテムに直接対処したいからです。セットを使用すると、Hibernate によって生成されたクエリは機能しますが、要素にアクセスするためのインデックスがありません。Product オブジェクトの API は、仕様がコレクションに格納されているという事実を隠し、個々の仕様ごとにゲッターとセッターを提供します。
仕様をセットにすると、次のようにゲッターとセッターを実装する必要があります。
@Transient
public BigDecimal getActualWeight () {
BigDecimal found = null;
for (Specification spec : this.specifications) {
if (spec instanceof ActualWeightSpec) {
found = ((ActualWeightSpec) spec).getValue ();
break;
}
}
return found;
}
public Product setActualWeight (Number value) {
ActualWeightSpec newWeight = new ActualWeightSpec ();
newWeight.setValue (value);
for (Specification spec : this.specifications) {
if (spec instanceof ActualWeightSpec) {
((ActualWeightSpec) spec).setValue (value);
return this;
}
}
this.specifications.add (newWeight);
return this;
}
個々の仕様レコードを取得するためにセットを反復処理する必要があるのは、これらのレコードに直接アクセスする方法としては非常に悪い方法のようです。
内部でハッシュマップを維持し、仕様のゲッターとセッターがセットを受け入れ、ゲッターとセッターで変換が行われるセットを返すようにしました。そうすれば、仕様を 1 回繰り返すだけで済みます。
private Product setSpecifications (Set <Specification> specs) {
HashMap <String, Specification> specsMap = new HashMap <> ();
for (Specification spec : specs) {
specsMap.put(spec.getClassName (), spec);
}
this.specifications = specsMap;
return this;
}
これも機能せず、Hibernate が例外をスローしました。
SEVERE: ローディングコレクションへの不正アクセス