Hibernate映射以区分列或类名为键



我一天中大部分时间都在为这个问题绞尽脑汁,根本找不到这个问题的答案。

我有一个PostgreSQL方案,看起来像这样:

+---------+1-n+-------------+1-1+------+|产品|-------->|ProductSpec |------->|Spec|+---------++-------------++------+

这代表了产品及其规范列表之间的一对多关系(我之所以不在规范表中使用外键到产品表中,是因为规范可能属于产品继承树中没有的东西,这些链接由其他交叉表表示)。

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

使用最简单的情况,Product类中的Set,我能够从products表的Hibernate查询中正确地构建对象图。然而,我想使用Map,这样我就可以直接处理特定的规范。计划是使用类名作为密钥,但我在尝试使用它时遇到了严重的问题。我不知道如何使用Java类名作为键,并且尝试使用存储在数据库中的类名作为映射键是有问题的。

正如目前实现的那样,我能够单独查询规范和产品(如果我注释掉实现产品和规范之间映射的代码)。如果我使用一个集合,我也可以查询嵌入了规范的产品,但如果我使用MapKey集合作为规范类名的映射,我会得到一个异常。

2013年9月1日凌晨1:25:55 org.hibernate.util.JDBCException ReporterlogExceptions警告:SQL错误:0,SQL状态:42P01 2013年9月1日凌晨1:25:55 org.hibernate.util.JDBCExceptionReporter日志异常严重:错误:关系"规范"不存在位置: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生成的查询如下所示(我没有按照类的方式对其进行修剪,以防未桥接的查询中出现问题)。一个明显的问题是,它试图在不插入模式名称的情况下查询规范表。

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

必须对一个集合进行迭代以获得单独的规范记录似乎是直接访问这些记录的一种非常糟糕的方式。

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

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

严重:非法访问加载集合

您可以使用规范的内部映射,而不用使用映射来干扰数据库。不要在Hibernate使用的getter或setter中初始化映射,但如果您的瞬态映射已经初始化,请检查您的getter(例如getActualWeight)。如果没有,请迭代规范一次并构建映射。顺便说一句,如果没有太多的规范,每次迭代应该不会太痛苦。

最新更新