7 Inheritance
對象使用引用以便關聯到其它對象;關系型資料庫表之間采用外鍵來描述表的關系。在關系型資料庫中通常沒有自然且有效的方法來描述類的繼承關系。JPA通過Inheritance annotation提供了幾種繼承政策,它有以下屬性:
- InheritanceType strategy:用來聲明繼承政策。可選值是InheritanceType.SINGLE_TABLE、InheritanceType.JOINED和InheritanceType .TABLE_PER_CLASS。預設值是InheritanceType.SINGLE_TABLE。
關于Inheritance的更多内容,可以參考Hibernate實戰by Christian Bauer, Gavin King。
7.1 Single Table
InheritanceType.SINGLE_TABLE 政策為類的繼承體系采用同一個表。表名是基類的名稱。例如:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class Base {
@Id
private int id;
@Basic
private String baseName;
}
@Entity
public class Derived1 extends Base {
@Basic
private String derived1Name;
}
@Entity
public class Derived2 extends Base {
@Basic
private String derived2Name;
}
使用MappingTool建立的表結構如下:
mysql> describe base;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| baseName | varchar(255) | YES | | NULL | |
| DTYPE | varchar(255) | YES | MUL | NULL | |
| derived1Name | varchar(255) | YES | | NULL | |
| derived2Name | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Base base = new Base();
base.setBaseName("base");
em.persist(base);
Derived1 d1 = new Derived1();
d1.setBaseName("derived1's base");
d1.setDerived1Name("derived1");
em.persist(d1);
Derived2 d2 = new Derived2();
d2.setBaseName("derived2's base");
d2.setDerived2Name("derived2");
em.persist(d2);
em.getTransaction().commit();
em.close();
以上代碼執行後,資料庫中base表的資料如下(其中DTYPE列由OpenJPA自動插入,用于區分不同的class,關于Discriminator的詳細用法請參考OpenJPA User's Guide):
mysql> select * from base;
+----+-----------------+----------+--------------+--------------+
| id | baseName | DTYPE | derived1Name | derived2Name |
+----+-----------------+----------+--------------+--------------+
| 1 | base | Base | NULL | NULL |
| 2 | derived1's base | Derived1 | derived1 | NULL |
| 3 | derived2's base | Derived2 | NULL | derived2 |
+----+-----------------+----------+--------------+--------------+
7.1.1 Advantages
InheritanceType.SINGLE_TABLE 政策的優勢在于簡單且性能高(因為不需要使用連接配接查詢等)。如果類的繼承體系中,子類和父類間的差異主要在于行為,同時子類之間以及子類和父類之間的屬性差異不大(例如子類不增加屬性或者增加的屬性數目比較少),那麼适用于這個政策。
7.1.2 Disadvantages
這個政策導緻規範化級别降低。由于類繼承體系中的每個類的屬性都要映射到表的一列,是以當類的繼承體系變的複雜的時候,表也随之變大。子類中屬性對應的列必須聲明為nullable。
7.2 Joined
InheritanceType.JOINED政策為類繼承體系中的每個類建立不同的表。每個表隻包含類中定義的列,是以在load一個子類的時候,JPA實作需要同時查詢子類映射的表,以及通過關聯查詢所有的父類映射的表。PrimaryKeyJoinColumn annotation用來指定子類映射的表如何關聯到父類映射的表。它有以下屬性:
- String name: 子類映射表中的列名。如果隻有一個identity filed,那麼預設使用這個field對應的列名。
- String referencedColumnName: 父類映射表中用來關聯的列名。如果隻有一個identity filed,那麼預設使用這個field對應的列名。
- String columnDefinition: 資料庫中列的資料類型。隻有當JPA vendor支援通過metadata建立表的時候,這個屬性才被使用。
以下是個簡單的例子:
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Base {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Basic
private String baseName;
}
@Entity
@PrimaryKeyJoinColumn(name="id", referencedColumnName="id")
public class Derived1 extends Base {
@Basic
private String derived1Name;
}
@Entity
@PrimaryKeyJoinColumn(name="id", referencedColumnName="id")
public class Derived2 extends Base {
@Basic
private String derived2Name;
}
使用MappingTool建立的表結構如下:
mysql> describe base;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| baseName | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
mysql> describe derived1;
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | | |
| derived1Name | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+
mysql> describe derived2;
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | | |
| derived2Name | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Base base = new Base();
base.setBaseName("base");
em.persist(base);
Derived1 d1 = new Derived1();
d1.setBaseName("derived1's base");
d1.setDerived1Name("derived1");
em.persist(d1);
Derived2 d2 = new Derived2();
d2.setBaseName("derived2's base");
d2.setDerived2Name("derived2");
em.persist(d2);
em.getTransaction().commit();
em.close();
以上代碼執行後,資料庫中base表的資料如下:
mysql> select * from base;
+----+-----------------+
| id | baseName |
+----+-----------------+
| 1 | derived2's base |
| 2 | derived1's base |
| 3 | base |
+----+-----------------+
mysql> select * from derived1;
+----+--------------+
| id | derived1Name |
+----+--------------+
| 2 | derived1 |
+----+--------------+
mysql> select * from derived2;
+----+--------------+
| id | derived2Name |
+----+--------------+
| 1 | derived2 |
+----+--------------+
7.2.1 Advantages
InheritanceType. JOINED政策的優勢在于資料庫表中沒有備援字段,是以規範化級别比較高;當有新的子類加入到類的繼承體系中時,已有表的schema無須修改。如果類的繼承體系中,子類和父類間的差異不在于行為,同時子類間的屬性差異比較大,那麼适用于這個政策。
7.2.2 Disadvantages
由于在查詢的時候需要進行關聯,那麼查詢的速度會比其它方式慢。此外可能需要多個插入和更新語句來處理多個表。
7.3 Table Per Class
InheritanceType.TABLE_PER_CLASS政策為類繼承體系中的每個類建立不同的表。和InheritanceType.JOINED政策不同的是,每個表中包含所有的子類和父類中定義的所有列。是以在load一個子類的時候,JPA實作隻需要同時查詢子類映射的表。
以下是個簡單的例子:
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Base {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Basic
private String baseName;
}
@Entity
public class Derived1 extends Base {
@Basic
private String derived1Name;
}
@Entity
public class Derived2 extends Base {
@Basic
private String derived2Name;
}
使用MappingTool建立的表結構如下:
mysql> describe base;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| baseName | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
mysql> describe derived1;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| baseName | varchar(255) | YES | | NULL | |
| derived1Name | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
mysql> describe derived2;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| baseName | varchar(255) | YES | | NULL | |
| derived2Name | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Base base = new Base();
base.setBaseName("base");
em.persist(base);
Derived1 d1 = new Derived1();
d1.setBaseName("derived1's base");
d1.setDerived1Name("derived1");
em.persist(d1);
Derived2 d2 = new Derived2();
d2.setBaseName("derived2's base");
d2.setDerived2Name("derived2");
em.persist(d2);
em.getTransaction().commit();
em.close();
以上代碼執行後,資料庫中base表的資料如下:
mysql> select * from base;
+----+----------+
| id | baseName |
+----+----------+
| 1 | base |
+----+----------+
mysql> select * from derived1;
+----+-----------------+--------------+
| id | baseName | derived1Name |
+----+-----------------+--------------+
| 1 | derived1's base | derived1 |
+----+-----------------+--------------+
mysql> select * from derived2;
+----+-----------------+--------------+
| id | baseName | derived2Name |
+----+-----------------+--------------+
| 1 | derived2's base | derived2 |
+----+-----------------+--------------+
7.3.1 Advantages
對于已知class類型的執行個體來說,這個政策十分有效。跟InheritanceType.JOINED政策類似,當有新的子類加入到類的繼承體系中時,已有表的schema無須修改。
7.3.2 Disadvantages
這個政策在處理多态關系的時候會存在很多限制,此時某個引用(或者集合中的引用)可能指向任何子類的執行個體。由于無法使用關聯查詢,是以在查詢的時候可能需要使用多個SQL語句或者使用UNION。