Single-table 策略:這是繼承映射中的缺省策略,在不特別指明的情況下,,系統(tǒng)默認(rèn)就是采用這種映射策略進(jìn)行映射的,。這個策略的映射原則就是父類包括子類中新添加的屬性全部映射到一張數(shù)據(jù)庫表中,數(shù)據(jù)庫表中有一個自動生成的字段用來存儲區(qū)分不同的子類的信息,。
Joined-subclass 策略:這種映射策略中,,繼承關(guān)系中的每一個實(shí)體類,無論是具體類 (concrete entity) 或者抽象類 (abstract entity),,數(shù)據(jù)庫中都有一個單獨(dú)的表與他對應(yīng),。子實(shí)體對應(yīng)的表中不含有從根實(shí)體繼承而來的屬性,它們之間通過共享主鍵的方式進(jìn)行關(guān)聯(lián),。
Table-per-concrete-class 策略:這個策略就是將繼承關(guān)系中的每一個實(shí)體映射到數(shù)據(jù)庫中的一個單獨(dú)的表中,,與“Joined”策略不同的是,子實(shí)體對應(yīng)的表中含有從根實(shí)體繼承而來的屬性,。這種策略在 JPA2.0 中仍然是自由選取得,,也就是說實(shí)現(xiàn) JPA2.0 規(guī)范的持久化引擎,如 Toplink,,Hibernate 等 , 仍然可以自由選取是否實(shí)現(xiàn)這種策略,。
得益于注解 (annotation) 方式的使用,JPA 2.0 中實(shí)現(xiàn)繼承關(guān)系的映射非常簡單,。當(dāng)實(shí)體 (entity) 之間有繼承關(guān)系的時候,,一定有一個根實(shí)體 (root entity),JPA2.0 中只需要在這個根實(shí)體 (root entity) 上標(biāo)注 @Inheritance 注解并且指明想要采用的映射策略就可以了,。如果是不用 @Inheritance 注解,,或者使用了 @Inheritance 注解但是沒有指明所要采用的映射策略的時候,,默認(rèn)就是采用單表策略 (Single-table strategy)。下面用圖 1 這個繼承關(guān)系分別說明這三種映射策略的使用,。
圖 1.Item,、Magazine、Phone 繼承關(guān)系的 UML 類圖
Item 類中定義了 Title,、Price 和 Description 屬性,,其中 Magazine 類和 Phone 類繼承了 Item 類,并且給自添加了自己獨(dú)有的屬性,,Magazine 中添加了 ISBN 和 Publisher 屬性,,Phone 中添加了 Factory 和 DurationTime 屬性。
Single Table 映射策略
單表(Single-Table)映射是繼承映射中的缺省映射策略,,在不加說明的情況下,,也就是不在根實(shí)體 (root entity) 中指定映射策略的時候默認(rèn)就是使用的這種映射策略。在本例中根實(shí)體 (root entity) 指的是實(shí)體 Item 類,。
清單 1.定義成單表映射的 Item 實(shí)體
@Entity public class Item implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private Float price; private String decription; // Getters and Setters }
Item 實(shí)體類沒有用 @Inheritance 這個注解 (annotation) 進(jìn)行注釋,,說明在這個繼承關(guān)系中使用的是單表映射策略。同時 Item 實(shí)體類是 Phone 實(shí)體類和 Magazine 實(shí)體類的根實(shí)體 (root entity),,它們繼承了 Item 的屬性并且擁有自己的獨(dú)有的屬性,。
清單 2.Phone 實(shí)體繼承自 Item 實(shí)體
@Entity public class Phone extends Item { private String factory; private Float DurationTime; // Getters and Setters }
清單 3.Magazine 實(shí)體繼承自 Item 實(shí)體
@Entity public class Magazine extends Item { private String isbn; private String publisher; // Getters and Setters }
因為采用的是單表映射策略,數(shù)據(jù)庫中只有一張表與之對應(yīng),,父類和子類中的屬性集中在同一個數(shù)據(jù)庫表中顯示,,默認(rèn)的數(shù)據(jù)庫表的名字是根實(shí)體 (root entity) 類的類名。
圖 2 就是對應(yīng)的數(shù)據(jù)庫表的 ER 圖,。
從圖 2 可以看出來,,單表映射中除了將所有子類中的屬性和父類集中在同一個表中之外,還多添加了一個 DTYPE 的列,,這個列主要是為了區(qū)別子類的,,他的缺省屬性是 String 類型,缺省值就是子實(shí)體 (entity) 類的類名,。如圖 3,,數(shù)據(jù)庫中記錄的一個片斷,DTYPE 列的值默認(rèn)就是類的名字,,它是用來區(qū)分本行的記錄屬于繼承關(guān)系的中的哪個類的。DTYPE 的值是 Item 的行,,就是由根實(shí)體持久化而來的,,以此類推。
圖 2.Item 表的 ER 圖
圖 3.Item 表的數(shù)據(jù)片斷
當(dāng)然這個用來區(qū)分子類的字段也是可根據(jù)需要而修改的.
清單 4.重新定義區(qū)分字段的名字和類型
@Entity @DiscriminatorColumn(name="DISC",discriminatorType=DiscriminatorType.CHAR) @DiscriminatorValue("I") public class Item implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private Float price; private String decription; // Getters and Setters }
清單 4 中重新定義了用來區(qū)分不同實(shí)體的列的列名和屬性,,將 String 類型修改成了 Char 類型,。并且指明了當(dāng)這個字段中的內(nèi)容為字符”I”時,,那么本行的地記錄屬于根實(shí)體自己。
清單 5.定義 Magazine 實(shí)體類區(qū)分字段得值
@Entity @DiscriminatorValue("M") public class Magazine extends Item { private String isbn; private String publisher; // Getters and Setters }
清單 6.定義 Phone 實(shí)體類區(qū)分字段得值
@Entity @DiscriminatorValue("P") public class Phone extends Item { private String factory; private Float DurationTime; // Getters and Setters }
清單 5 和清單 6 中分別對 Magazine 和 Phone 實(shí)體在區(qū)分子段中的要現(xiàn)實(shí)的值進(jìn)行了修改,,修改為字符“M”和字符“P”,,當(dāng)然也可以是其他的字符。圖 4 就是修改后的繼承關(guān)系在數(shù)據(jù)庫中對應(yīng)的表的數(shù)據(jù)記錄片斷,??梢钥闯鰠^(qū)分子段已經(jīng)發(fā)生了相應(yīng)的改變。
圖 4. 重新定義后的 Item 表
單表映射策略是缺省的映射策略,,這種策略對實(shí)體之間的多種關(guān)聯(lián)關(guān)系能提供很好的支持,,同時在查詢方面也有很好的效率。但是這種映射策略在數(shù)據(jù)庫表中會有很多的空字段的存在,,如圖 3 和圖 4 所示,。這樣勢必會造成數(shù)據(jù)庫資源的大量浪費(fèi),同時這個映射策略也要求子類中的所有屬性也必須是可空 (null able) 的,。
Joined 映射策略
在這種映射策略里面,,繼承結(jié)構(gòu)中的每一個實(shí)體 (entity) 類都會映射到數(shù)據(jù)庫中一個單獨(dú)的表中,也就是說每個實(shí)體 (entity) 都會被映射到數(shù)據(jù)庫中,,一個實(shí)體 (entity) 類對應(yīng)數(shù)據(jù)庫中的一個表,。其中根實(shí)體 (root entity) 對應(yīng)的表中定義了主鍵 (primary key),所有的子類對應(yīng)的數(shù)據(jù)庫表都要共同使用這個主鍵,,同時這個表中和單表映射策略一樣還定義了區(qū)分列 (DTYPE),。
清單 7.連接映射策略中的根實(shí)體 (root entity)
@Entity @Inheritance(strategy=InheritanceType.JOINED) @Table(name="Item_Joined") public class Item implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private Float price; private String decription; // Getters and Setters }
清單 8.連接映射策略中的 Magazine 子實(shí)體 (subclass)
@Entity public class Magazine extends Item { private String isbn; private String publisher; // Getters and Setters }
清單 9.連接映射策略中的 Phone 子實(shí)體 (subclass)
@Entity public class Phone extends Item { private String factory; private Float DurationTime; // Getters and Setters }
從清單 7,清單 8,,清單 9 中可以看出,,這種映射策略和缺省的單表映射策略唯一的區(qū)別就是在根實(shí)體中使用 @Inheritance 注解 (annotation) 并指定使用 Joined 映射策略。圖 5 是這種策略在在數(shù)據(jù)庫中的 ER 圖,,從該圖中可以看出來子類對應(yīng)的表和父類對應(yīng)的表除了共享主鍵外,,與單表映射不同的是,他們各自擁有自己的屬性,。同時根實(shí)體 (root entity) 對應(yīng)的表中的 DTYPE 屬性說明,,您仍舊可以使用 @DiscriminatorColumn 和 @DiscriminatorValue 在其中定義用來區(qū)分子類的字段和值。
圖 5. 連接映射的 ER 圖
這種映射策略,,在繼承比較多時,,查尋起來效率就會差一些,因為在查詢的過程中需要多表的連接,,連接的表數(shù)越多,,查詢效率越低下。
Table-per-class 映射策略
這種映射策略和連接映射策略很類似,不同的是子類對應(yīng)的表中要繼承根實(shí)體 (root entity) 中的屬性,,根實(shí)體 (root entity) 對應(yīng)的表中也不需要區(qū)分子類的列,,表之間沒有共享的表,也沒有共享的列,。
清單 10.根實(shí)體 (root entity)
@Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) @Table(name="Item_TPC") public class Item implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private Float price; private String decription; // Getters and Setters }
Item 根實(shí)體在 @Inheritance 注解中指定使用 TABLE_PER_CLASS 映射策略,。
清單 11.Magazine 實(shí)體 (entity)
@Entity public class Magazine extends Item { private String isbn; private String publisher; // Getters and Setters }
清單 12.Phone 實(shí)體 ( entity)
@Entity public class Phone extends Item { private String factory; private Float DurationTime; // Getters and Setters }
Magazine 和 Phone 子實(shí)體沒有任何變化。圖 6 是對應(yīng)的 ER 圖,,從圖中可以看出,,不同的子實(shí)體對應(yīng)的表中,不僅有自己的屬性,,還包含了從根實(shí)體繼承而來的屬性,,這點(diǎn)與 Joined 映射策略不同,Joined 映射策略中,,子實(shí)體對應(yīng)的表中不包含根實(shí)體的屬性,。
圖 6. 映射的 ER 圖
重寫根實(shí)體的屬性
因為每個子實(shí)體對應(yīng)的表中都繼承了根實(shí)體中的屬性,為了區(qū)分可以使用 @AttributeOverride 注解來修改根實(shí)體中的屬性在不同的子實(shí)體中的對應(yīng)的列名,。
清單 13.Magazine 實(shí)體 (entity)
@Entity @AttributeOverrides({ @AttributeOverride(name="id", column=@Column(name="Magazine_id")), @AttributeOverride(name="title", column=@Column(name="Magazine_title")), @AttributeOverride(name="description", column=@Column(name="Magazine_desc")), }) public class Magazine extends Item { private String isbn; private String publisher; // Getters and Setters }
Magazine 實(shí)體將從 Item 根實(shí)體中繼承而來的 id,、title、description 屬性分別重寫為 Magazine_id,、Magazine_title 和 Magazine_desc,。
清單 14.Phone 實(shí)體 ( entity)
@Entity @AttributeOverrides({ @AttributeOverride(name="id", column=@Column(name="Phone_id")), @AttributeOverride(name="title", column=@Column(name="Phone_title")), @AttributeOverride(name="description", column=@Column(name="Phone_desc")), }) public class Phone extends Item { private String factory; private Float DurationTime; // Getters and Setters }
Phone 實(shí)體將從 Item 根實(shí)體中繼承而來的 id、title,、description 屬性分別重寫為 Phone _id,、Phone _title 和 Phone_desc。
從圖 7 就可以看出,,根實(shí)體中的屬性,,在子實(shí)體中的列名已經(jīng)被相應(yīng)地修改了。
圖 7. 映射的 ER 圖
繼承映射中的其他類型 :
上面的例子中的所使用的根實(shí)體類都是具體 (concrete) 實(shí)體類,,下面將介紹一下當(dāng)根實(shí)體是其他類型類的情況下的映射規(guī)則,。
抽象實(shí)體類 (Abstract entity) :
上面的例子中所使用的 Item 是個具體類 (concrete class),并且使用 @Entity 注解 (annotation) 進(jìn)行了注釋,。那么當(dāng) Item 類不是具體類 (concrete class),,而是一個抽象類 (abstract class) 的時候,也就是當(dāng) Item 類的聲明中使用了 abstract 關(guān)鍵字的時候,,是如何影射的呢,?事實(shí)上根實(shí)體是否是抽象實(shí)體類,在數(shù)據(jù)庫中映射成的表沒有任何區(qū)別,。也就是說上面的例子中如果根實(shí)體類 Item 是個抽象實(shí)體類,,使用了 abstract 關(guān)鍵字的話,,在數(shù)據(jù)庫中生成的表和上面的例子是相同的。唯一的區(qū)別就是,,如果根實(shí)體是抽象實(shí)體類的話,就不能使用 new 關(guān)鍵字來生成這個實(shí)體類的對象了,。他們的區(qū)別只是在 Java 語言語法上的區(qū)別,,在持久化上沒有任何區(qū)別。
非實(shí)體類 (Nonentity):
非實(shí)體類 (Nonentity) 也叫瞬態(tài)類 (transient class),,就是普通的 POJO 類,,沒有使用 @Entity 注解 (annotation) 注釋,這種類在持久化的時候不會被映射到數(shù)據(jù)庫中,,因為根據(jù)之前介紹過的持久化的原則,,一個類如果想被持久化到數(shù)據(jù)庫中,必須使用 @Entity 注解,。那么當(dāng)一個實(shí)體類繼承了一個這樣的非實(shí)體類的情況下,,該如何影射呢。
清單 15.Item 類是一個沒有使用 @Entity 普通的 POJO 的 Java 類
public class Item{ private String title; private Float price; private String decription; // Getters and Setters }
清單 16 中的 Magazine 實(shí)體類繼承了 Item 非實(shí)體類,,根據(jù) Java 的語法規(guī)則,,Magazine 實(shí)體類的對象將可以訪問 Item 類中的屬性。并且根據(jù)上面介紹過,,Item 類可以是具體類 (concrete class) 也可以是抽象類 (abstract class),,這對持久化后的結(jié)果沒有影響。
清單 16.Magazine 實(shí)體繼承了 Item 非實(shí)體類
@Entity public class Magazine extends Item { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String isbn; private String publisher; // Getters and Setters }
Magazine 實(shí)體類繼承了 Item 類,,但是在持久化的時候,,只有 Magazine 類中的屬性才能持久化到數(shù)據(jù)庫中去。從 Item 類中繼承下來的屬性不會持久化數(shù)據(jù)庫中,。清單 17 就是將 Magazine 實(shí)體類持久化到數(shù)據(jù)庫中的表的結(jié)構(gòu),,從中可以看出并沒有 Item 中的屬性。
清單 17.Magazine 實(shí)體繼承了 Item 非實(shí)體類
CREATE TABLE `magazine` ( `ID` bigint(20) NOT NULL, `ISBN` varchar(255) default NULL, `PUBLISHER` varchar(255) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Mapped SupperClass:
JPA 有一種特殊的類叫做 Mapped Supper class,,這種類不是實(shí)體類,,他與實(shí)體類的區(qū)別就是用 @MappedSuperclass 注解來替代 @Entity 注解,其他方面沒有變化,。如清單 18 中的例子所示,,Employee 類被注解成了一個 Mapped Superclass,并且使用了繼承映射中的 Joined 映射策略,。
清單 18.Employee 是一個 Mapped superclass
@MappedSuperclass @Inheritance(strategy=InheritanceType.JOINED) public class Employee { @Id @GeneratedValue private Long id; private String name; private String depart; // Getters and Setters }
這種類不能被映射到數(shù)據(jù)庫中,,持久化后數(shù)據(jù)庫中沒有與之對應(yīng)的表,因此就不能使用 @Table 注解,,也就不能對他進(jìn)行查詢等操作,。但是任何繼承他的子類可以將繼承而來的屬性持久化到數(shù)據(jù)庫中。
清單 19.Manager 繼承了 Employee
@Entity public class Manager extends Employee { private String office; private String car; // Getters and Setters }
清單 19 中的 Manager 類繼承了 Employee 類,清單 20 就是 Manager 類映射到數(shù)據(jù)庫中后對應(yīng)的表結(jié)構(gòu),。從表結(jié)構(gòu)中可以看出,,Manager 從 Employee 繼承而來的屬性也被映射到了數(shù)據(jù)庫中。
清單 20.Manager 表的結(jié)構(gòu)
CREATE TABLE `manager` ( `ID` bigint(20) NOT NULL, `OFFICE` varchar(255) default NULL, `CAR` varchar(255) default NULL, `DEPART` varchar(255) default NULL, `NAME` varchar(255) default NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
總結(jié)
綜上所述,,雖然面向?qū)ο蟮?Java 語言中,,類之間的繼承關(guān)系在關(guān)系型數(shù)據(jù)庫中的表并沒有對應(yīng)的關(guān)聯(lián)方式。但是我們可以使用上述的方法將兩者輕松地實(shí)現(xiàn)對象 - 關(guān)系的映射,,即使類之間的繼承關(guān)系很復(fù)雜,,實(shí)現(xiàn)對象 - 關(guān)系映射的方法無外乎上面所述幾種情況,稍加分析我們就可以以不變應(yīng)萬變,。