7 Inheritance
对象使用引用以便关联到其它对象;关系型数据库表之间采用外键来描述表的关系。在关系型数据库中通常没有自然且有效的方法来描述类的继承关系。JPA通过Inheritance annotation提供了几种继承策略,它有以下属性:
关于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用来指定子类映射的表如何关联到父类映射的表。它有以下属性:
以下是个简单的例子:
@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。