JPA實體繼承實體的對映策略
阿新 • • 發佈:2019-02-09
注:這裡所說的實體指的是@Entity註解的類
繼承對映使用@Inheritance來註解,它的strategy屬性的取值由列舉InheritanceType來定義(包括SINGLE_TABLE、TABLE_PER_CLASS、JOINED,分別對應三種繼承策略)。@Inheritance註解只能作用於繼承結構的超類上。如果不指定繼承策略,預設使用SINGLE_TABLE。
JPA提供了三種繼承對映策略:
1、一個類繼承結構一個表的策略。這是繼承對映的預設策略。即如果實體類B繼承實體類A,實體類C也繼承自實體A,那麼只會對映成一個表,這個表中包括了實體類A、B、C中所有的欄位,JPA使用一個叫做“discriminator列”來區分某一行資料是應該對映成哪個實體。註解為:@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
2、聯合子類策略。這種情況下子類的欄位被對映到各自的表中,這些欄位包括父類中的欄位,並執行一個join操作來例項化子類。註解為:@Inheritance(strategy = InheritanceType.JOINED)
3、每個具體的類一個表的策略。註解為:@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
一、一個類繼承結構一個表的策略
這種策略中,一個繼承結構中的所有類都被對映到一個表中。該表中有一列被當作“discriminator列”,即使用該列來識別某行資料屬於某個指定的子類例項。
這種對映策略對實體和涉及類繼承結構的查詢的多型系統提供了很好的支援。但缺點是要求與子類的指定狀態對應的列可以為空。
例項如下:
其中,超類的@DiscriminatorColumn註解可以省略,預設的“discriminator列”名為DTYPE,預設型別為STRING。package com.mikan; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.Table; @Entity @Table(name = "EMP") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "emp_type") public class Employee implements Serializable { private static final long serialVersionUID = -7674269980281525370L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) protected Integer empId; @Column protected String name; // getter/setter方法 } package com.mikan; import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @Entity @DiscriminatorValue("FT") public class FullTimeEmployee extends Employee { private static final long serialVersionUID = 9115429216382631425L; @Column private Double salary; // getter/setter方法 } package com.mikan; import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @Entity @DiscriminatorValue("PT") public class PartTimeEmployee extends Employee { private static final long serialVersionUID = -6122347374515830424L; @Column(name = "hourly_wage") private Float hourlyWage; // getter/setter方法 }
@DiscriminatorColumn註解只能使用在超類上,不能使用到具體的子類上。discriminatorType的值由DiscriminatorType列舉定義,包括STRING、CHAR、INTEGER。如果指定了discriminatorType,那麼子類上@ DiscriminatorValue註解的值也應該是相應型別。
@DiscriminatorValue註解只能使用在具體的實體子類上。同樣@DiscriminatorValue註解也可以省略,預設使用類名作為值。
上面的例子中,只會生成一個表,包含了欄位emp_type、empId、name、salary、hourly_wage。當儲存FullTimeEmployee時,emp_type的值為“FT”, 當儲存PartTimeEmployee時,emp_type的值為“PT”。
二、聯合子類策略
這種策略超類會被對映成一個單獨的表,每個子類也會對映成一個單獨的表。子類對應的表中只包括自身屬性對應的欄位,預設情況下使用主鍵作為超類對應的表的外來鍵。
這種策略對於實體間的多型關係提供了很好的支援。但缺點是例項化子類例項時需要一個或多個表的關聯操作。在深層次的繼承結構中,這會導致效能很低。例項如下:
這會對映成三個具體的表,分別是,Employee對應EMP表,欄位包括empId、name;FullTimeEmployee對應FT_EMP表,欄位包括empId、salary;PartTimeEmployee對應PT_EMP表,欄位包括empId、hourly_wage。其中,表FT_EMP和PT_EMP中的empId作為表EMP的外來鍵,同是它也是主鍵。預設情況下,使用超類的主鍵作為子類的主鍵和外來鍵。當然,可以通過@PrimaryKeyJoinColumn註解來自己指定外來鍵的名稱,如FullTimeEmployee使用@PrimaryKeyJoinColumn(name = "FT_EMPID")註解,那麼該子類實體的欄位為FT_EMPID、name,FT_EMPID作為表FT_TIME的主鍵,同時它也是EMP表的外來鍵。package com.mikan; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.Table; @Entity @Table(name = "EMP") @Inheritance(strategy = InheritanceType.JOINED) public class Employee implements Serializable { private static final long serialVersionUID = -7674269980281525370L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) protected Integer empId; @Column protected String name; // getter/setter方法 } package com.mikan; import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name = "FT_EMP") public class FullTimeEmployee extends Employee { private static final long serialVersionUID = 9115429216382631425L; @Column private Double salary; // getter/setter方法 } package com.mikan; import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name = "PT_EMP") public class PartTimeEmployee extends Employee { private static final long serialVersionUID = -6122347374515830424L; @Column(name = "hourly_wage") private Float hourlyWage; // getter/setter方法 }
子類實體每儲存一條資料,會在EMP表中插入一條記錄,如FT_EMP表插入一條資料,會先在EMP表中插入name,並生成empId,再在FT_EMP表中插入empId和salary。PT_EMP同理。
不管超類是抽象類還是具體類,都會生成對應的表。
三、每個具體的類一個表的策略
這種對映策略每個類都會對映成一個單獨的表,類的所有屬性,包括繼承的屬性都會對映成表的列。
這種對映策略的缺點是:對多型關係的支援有限,當查詢涉及到類繼承結構時通常需要發起SQL UNION查詢。例項如下:
package com.mikan;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "EMP")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Employee implements Serializable {
private static final long serialVersionUID = -7674269980281525370L;
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
protected Integer empId;
@Column
protected String name;
// getter/setter方法
}
package com.mikan;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "FT_EMP")
public class FullTimeEmployee extends Employee {
private static final long serialVersionUID = 9115429216382631425L;
@Column
private Double salary;
// getter/setter方法
}
package com.mikan;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "PT_EMP")
public class PartTimeEmployee extends Employee {
private static final long serialVersionUID = -6122347374515830424L;
@Column(name = "hourly_wage")
private Float hourlyWage;
// getter/setter方法
}
這會對映成三個具體的表,分別是,Employee對應EMP表,欄位包括empId、name;FullTimeEmployee對應FT_EMP表,欄位包括empId、salary;PartTimeEmployee對應PT_EMP表,欄位包括empId、hourly_wage。其中,表FT_EMP和PT_EMP中的empId和EMP表的empId沒有任何關係。子類實體每儲存一條資料,EMP表中不會插入記錄。而且主鍵的生成策略不能使用GenerationType.AUTO或GenerationType.IDENTITY,否則會出現異常:
org.hibernate.MappingException: Cannot use identity column key generation with <union-subclass> mapping for: com.mikan.PartTimeEmployee
因為TABLE_PER_CLASS策略每個表都是單獨的,沒有並且各表的主鍵沒有任何關係,所以不能使用GenerationType.AUTO或GenerationType.IDENTITY主鍵生成策略,可以使用GenerationType.TABLE。
如果超類是抽象類,那麼不會生成對應的表。如果超類是具體的類,那麼會生成對應的表。以上例項使用JPA的hibernate實現測試通過。