Hibernate maps with discrimination column or class name as key
I've been pulling my hair out over this for the best part of a day now,
and simply can't find any answers to this problem.
I've got a PostgreSQL schems that looks like this:
+---------+ 1-n +-------------+ 1-1 +------+
| Product |-------->| ProductSpec |-------->| Spec |
+---------+ +-------------+ +------+
This represents a one to many relationship between a Product and its list
of Specifications (The reason I don't just use a foreign key in the
specifications table into the products table is because specifications can
belong to things that aren't in the product inheritance tree, those links
are represented by other intersection tables).
Each Specification is a subclass of a Specification class (Weight, Length,
NumberOfThings, and so on), with the name of the subclass in question
being stored in the Spec table. Each product has a collection of
specifications, but each subclass of specification can only appear once. A
product can only have one weight (though if you need a weight for the
actual product, and a shipping weight for the courier to calculate
shipping charges, you can simply subclass ActualWeight and ShippingWeight
from the Weight specification).
Using the simplest case, a Set in the Product class, I'm able to construct
the object graph correctly from a Hibernate query of the products table. I
want to use a Map instead, however, so I can address specific
specifications directly. The plan was to use the class name as the key,
but I'm having serious issues trying to get it to work. I'm unable to
figure out how to use the Java class name as the key, and trying to use
the class name as stored in the database as the map key is proving
problematic.
As currently implemented, I'm able to query the specifications, and the
products individually (if I comment out the code implementing the mapping
between products and specifications). I can also query the products with
the specifications embedded if I use a set, but if I use a map with the
MapKey set to be the specifications class name, I get an exception.
Sep 01, 2013 1:25:55 AM org.hibernate.util.JDBCExceptionReporter
logExceptions WARNING: SQL Error: 0, SQLState: 42P01 Sep 01, 2013 1:25:55
AM org.hibernate.util.JDBCExceptionReporter logExceptions SEVERE: ERROR:
relation "specifications" does not exist Position: 424
I've annotated my (cut down) classes as follows. The product class:
@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;
}
}
And the Specification class:
@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;
}
}
The DB schema looks like this:
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)
)
The query that Hibernate generates is listed below (I've not trimmed this
the way I have the classes in case there's something in the unabridged
query that is an issue). One obvious problem is that it's trying to query
the specifications table without inserting the schema name.
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=?
The problem is in the sub-query, which isn't getting a schema prepended to
the specifications table name.
If anyone knows how to either get the query to be correct, or of using the
class name directly as the Java map key, I'd appreciate being told.
No comments:
Post a Comment