1

私は一日のほとんどの間、これについて頭を悩ませてきましたが、この問題に対する答えを見つけることができません.

次のような 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: ローディングコレクションへの不正アクセス

4

1 に答える 1

2

仕様の内部マップを使用でき、マップでデータベースを煩わせることはありません。Hibernate が使用するゲッターまたはセッターでマップを初期化しないでください。ただしgetActualWeight、一時マップが既に初期化されている場合は、ゲッター (例: ) をチェックインしてください。そうでない場合は、仕様を 1 回繰り返してマップを作成します。ところで、仕様が多すぎない場合は、毎回反復してもそれほど害はありません。

于 2013-09-01T16:35:35.507 に答える