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-05
Single 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
discriminatorType
STRING (defaults to Entity.name)
Only portable technique when accepting default
CHAR
Vendor-specific value when accepting default
INTEGER
Vendor-specific value when accepting default
columnDefinition
Database-specific definition for when generating schema
length
Size of STRING