1

在一天的大部分时间里,我一直在为此烦恼,根本找不到这个问题的任何答案。

我有一个 PostgreSQL 模式,看起来像这样:

 +---------+ 1-n +-------------+ 1-1 +------+
 | 产品 |-------->| 产品规格 |-------->| 规格 |
 +---------+ +-------------+ +------+

这表示产品与其规格列表之间的一对多关系(我不只是在产品表中使用规格表中的外键的原因是因为规格可以属于不在产品继承中的事物树,这些链接由其他交集表表示)。

每个 Specification 都是 Specification 类(Weight、Length、NumberOfThings 等)的子类,相关子类的名称存储在 Spec 表中。每个产品都有一组规格,但规格的每个子类只能出现一次。一个产品只能有一个重量(尽管如果您需要实际产品的重量,以及快递员计算运费的运输重量,您可以简单地从重量规范中继承 ActualWeight 和 ShippingWeight)。

使用最简单的情况,即 Product 类中的 Set,我能够从产品表的 Hibernate 查询中正确构造对象图。但是,我想改用 Map,这样我就可以直接解决特定的规范。计划是使用类名作为键,但我在试图让它工作时遇到了严重的问题。我无法弄清楚如何使用 Java 类名作为键,并且尝试使用存储在数据库中的类名作为映射键被证明是有问题的。

按照目前的实现,我可以单独查询规格和产品(如果我注释掉实现产品和规格之间映射的代码)。如果我使用集合,我也可以查询嵌入了规范的产品,但是如果我使用 MapKey 设置为规范类名称的地图,则会出现异常。

2013 年 9 月 1 日上午 1:25:55 org.hibernate.util.JDBCExceptionReporter logExceptions 警告:SQL 错误:0,SQLState:42P01 2013 年 9 月 1 日上午 1:25:55 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;
    }
}

数据库架构如下所示:

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 生成的查询(如果未删节的查询中有问题,我没有按照我拥有类的方式进行修剪)。一个明显的问题是它试图在不插入模式名称的情况下查询规范表。

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 隐藏了规范存储在集合中的事实,并为每个单独的规范提供了 getter 和 setter。

如果我将规范设置为一组,我必须像这样实现 getter 和 setter:

@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;
}

必须遍历一组以获取单个规范记录似乎是直接访问这些记录的一种非常糟糕的方式。

我确实尝试在内部维护一个哈希图,并让规范的 getter 和 setter 接受并返回集合,并在 getter 和 setter 中进行转换。这样我只需要接受一次迭代规范的冲击。

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 抛出异常。

严重:非法访问加载集合

4

1 回答 1

2

您可以使用内部规格图,而不必用地图打扰数据库。不要在 Hibernate 使用的 getter 或 setter 中初始化映射,但getActualWeight如果您的瞬态映射已经初始化,请检查您的 getter(例如)。如果没有,请迭代一次规范并构建地图。顺便说一句,如果没有太多规范,那么每次迭代应该不会造成太大的伤害。

于 2013-09-01T16:35:35.507 回答