1. 程式人生 > >jpa繼承關係詳解

jpa繼承關係詳解

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 策略為類的繼承體系採用同一個表。表名是基類的名稱。例如:

Java程式碼
@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;  


@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建立的表結構如下:

Sql程式碼
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    |                |  
+--------------+--------------+------+-----+---------+----------------+  

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    |                |
+--------------+--------------+------+-----+---------+----------------+
Java程式碼
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(); 

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):Sql程式碼
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     |  
+----+-----------------+----------+--------------+--------------+  

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建立表的時候,這個屬性才被使用。
   以下是個簡單的例子:

Java程式碼
@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;  


@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建立的表結構如下:

Sql程式碼
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    |       |  
+--------------+--------------+------+-----+---------+-------+ 

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    |       |
+--------------+--------------+------+-----+---------+-------+

Java程式碼
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(); 

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表的資料如下:Sql程式碼
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     |  
+----+--------------+ 

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
實現只需要同時查詢子類對映的表。
    以下是個簡單的例子:

Java程式碼
@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;  


@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建立的表結構如下:

Sql程式碼
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    |                |  
+--------------+--------------+------+-----+---------+----------------+ 

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    |                |
+--------------+--------------+------+-----+---------+----------------+ Java程式碼
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(); 

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表的資料如下:

Sql程式碼
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     |  
+----+-----------------+--------------+ 

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。