Enterprise Java Development@TOPIC@
Advantages
Simplest to implement
Single table to administer
Performs better than other inheritance strategies
No complex joins
Disadvantages
Unused fields when sub-types have unique properties
Sub-type columns must be nullable
Harder to enforce constraints within database
SQL "check" constraint can help
check(TYPE != 'BREAD_TYPE' or (BAKEDON is not null and SLICES is not null)) check(TYPE != 'Soup' or (SOUPTYPE is not null and EXPIRATION is not null)
Not normalized
More suitable for hierarchies with sub-types that...
Differ primarily in behavior only
Do not have unique data requirements
Figure 63.2. Single Table Example Database Schema
create table ORMINH_PRODUCT (
PTYPE varchar(32) not null,
id bigint generated by default as identity,
cost double not null,
expiration date,
SOUPTYPE varchar(16),
bakedOn date,
slices integer,
primary key (id)
)Single table
No joins
Unused columns
Figure 63.3. Single Table Example Java Mapping (Parent Class)
@Entity @Table(name="ORMINH_PRODUCT")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="PTYPE", //column in root table indicating type
discriminatorType=DiscriminatorType.STRING,//data type of column
length=32) //length of discriminator string
public abstract class Product {
@Id @GeneratedValue
private long id;
private double cost;
...
@Transient
public abstract String getName();
Parent defines default mapping for all derived types
Figure 63.4. Single Table Example Java Mapping (Annotated Derived)
@Entity
@DiscriminatorValue("BREAD_TYPE") //value placed in root table to indicate type
public class Bread extends Product {
private int slices;
@Temporal(TemporalType.DATE)
private Date bakedOn;
...
@Transient
public String getName() { return "Bread"; }
Supplies type-specific column value
Figure 63.5. Single Table Example Java Mapping (Default Derived)
@Entity
public class Soup extends Product {
public enum SoupType {
UNKNOWN("Unknown"),
CHICKEN_NOODLE("Chicken Noodle"),
NEW_ENGLAND_CLAM_CHOWDER("New England Clam Chowder"),
TOMATO("Tomato");
private String text;
private SoupType(String text) { this.text = text; }
public String text() { return text; }
};
@Enumerated(EnumType.STRING)
@Column(name="SOUPTYPE", length=16)
private SoupType type = SoupType.UNKNOWN;
@Temporal(TemporalType.DATE)
private Date expiration;
...
@Transient
public String getName() { return type.text() + "Soup"; }
Accepts default type-specific column value
Figure 63.6. Single Table Example Usage (Persist)
ejava.examples.orm.inheritance.annotated.Soup soup = new Soup();
soup.setCost(2.12);
final long lifetime = 365L*24*60*60*1000;
soup.setExpiration(new Date(System.currentTimeMillis() + lifetime));
soup.setSoupType(Soup.SoupType.CHICKEN_NOODLE);
em.persist(soup);
ejava.examples.orm.inheritance.annotated.Bread bread = new Bread();
bread.setBakedOn(new Date());
bread.setCost(2.25);
bread.setSlices(24);
em.persist(bread);
Hibernate:
insert into ORMINH_PRODUCT (id, cost, expiration, SOUPTYPE, PTYPE)
values (null, ?, ?, ?, 'Soup')
Hibernate:
insert into ORMINH_PRODUCT(id, cost, bakedOn, slices, PTYPE)
values (null, ?, ?, ?, 'BREAD_TYPE')Two rows inserted into single base table with discriminator type supplied
Figure 63.7. Single Table Example Usage (Get Entities)
List<Product> products = em.createQuery(
"select p from Product p", Product.class)
.getResultList();
assertTrue("unexpected number of products:" + products.size(),
products.size() == 2);
for(Product p: products) {
log.info("product found:" + p);
}
Hibernate:
select
product0_.id as id2_9_,
product0_.cost as cost3_9_,
product0_.expiration as expirati4_9_,
product0_.SOUPTYPE as SOUPTYPE5_9_,
product0_.bakedOn as bakedOn6_9_,
product0_.slices as slices7_9_,
product0_.PTYPE as PTYPE1_9_
from
ORMINH_PRODUCT product0_
-product found:Soup, id=1, cost=2.12, type=CHICKEN_NOODLE, expiration=2014-10-05
-product found:Bread, id=2, cost=2.25, slices=24, baked=2013-10-05Single table queried for objects
Figure 63.8. Single Table Example Usage (Verify DB Schema)
//query specific tables for columns
int rows = em.createNativeQuery(
"select ID, TYPE, COST, SOUPTYPE, EXPIRATION, BAKEDON, SLICES " +
" from ORMINH_PRODUCT")
.getResultList().size();
assertEquals("unexpected number of product rows:" + rows, 2, rows);
select * from ORMINH_PRODUCT PTYPE ID COST BAKEDON SLICES EXPIRATION SOUPTYPE ---------- -- ---- ---------- ------ ---------- -------------- Soup 1 2.12 (null) (null) 2007-10-08 CHICKEN_NOODLE BREAD_TYPE 2 2.25 2006-10-08 24 (null) (null)
Single table "sparsely" filled based on type
@DiscriminatorColumn defines column to hold type-specific value
name (default="DTYPE")Column name
discriminatorTypeSTRING (defaults to Entity.name)Only portable technique when accepting default
CHARVendor-specific value when accepting default
INTEGERVendor-specific value when accepting default
columnDefinitionDatabase-specific definition for when generating schema
lengthSize of STRING