本文简单讲述hibernate的继承映射相关知识,以备不时之需。继承映射,顾名思义就是有继承关系的几个实体之间的映射关系。
1.首先看看annotation的API中关于继承映射的描述
EJB3支持三种类型的继承映射:
你可以用 @Inheritance注解来定义所选择的策略. 这个注解需要在每个类层次结构(class hierarchy) 最顶端的实体类上使用.
目前还不支持在接口上进行注解.
这种策略有很多缺点(例如:多态查询和关联),EJB3规范, Hibernate参考手册, Hibernate in Action,以及其他许多地方都对此进行了描述和解释. Hibernate使用SQL UNION查询来实现这种策略. 通常使用场合是在一个继承层次结构的顶端:
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Flight implements Serializable {
这种策略支持双向的一对多关联. 这里不支持IDENTITY生成器策略,因为id必须在多个表间共享. 当然,一旦使用这种策略就意味着你不能使用 AUTO 生成器和IDENTITY生成器.
整个继承层次结构中的父类和子类的所有属性都映射到同一个表中, 他们的实例通过一个辨别符(discriminator)列来区分.:
@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name="planetype", discriminatorType=DiscriminatorType.STRING ) @DiscriminatorValue("Plane") public class Plane { ... } @Entity @DiscriminatorValue("A320") public class A320 extends Plane { ... }
在上面这个例子中,Plane是父类,在这个类里面将继承策略定义为 InheritanceType.SINGLE_TABLE,并通过 @DiscriminatorColumn注解定义了辨别符列(还可以定义辨别符的类型). 最后,对于继承层次结构中的每个类,@DiscriminatorValue注解指定了用来辨别该类的值. 辨别符列的名字默认为 DTYPE,其默认值为实体名(在@Entity.name中定义),其类型 为DiscriminatorType.STRING. A320是子类,如果不想使用默认的辨别符,只需要指定相应的值即可. 其他的如继承策略,辨别标志字段的类型都是自动设定的.
@Inheritance 和 @DiscriminatorColumn 注解只能用于实体层次结构的顶端.
当每个子类映射到一个表时, @PrimaryKeyJoinColumn 和@PrimaryKeyJoinColumns 注解定义了每个子类表关联到父类表的主键:
@Entity @Inheritance(strategy=InheritanceType.JOINED) public class Boat implements Serializable { ... } @Entity public class Ferry extends Boat { ... } @Entity @PrimaryKeyJoinColumn(name="BOAT_ID") public class AmericaCupClass extends Boat { ... }
以上所有实体都使用了JOINED策略, Ferry表和Boat表使用同名的主键. 而AmericaCupClass表和Boat表使用了条件 Boat.id = AmericaCupClass.BOAT_ID进行关联.
有时候通过一个(技术上或业务上)父类共享一些公共属性是很有用的, 同时还不用将该父类作为映射的实体(也就是该实体没有对应的表). 这个时候你需要使用@MappedSuperclass注解来进行映射.
@MappedSuperclass public class BaseEntity { @Basic @Temporal(TemporalType.TIMESTAMP) public Date getLastUpdate() { ... } public String getLastUpdater() { ... } ... } @Entity class Order extends BaseEntity { @Id public Integer getId() { ... } ... }
在数据库中,上面这个例子中的继承的层次结构最终以Order表的形式出现, 该表拥有id, lastUpdate 和 lastUpdater三个列.父类中的属性映射将复制到其子类实体. 注意这种情况下的父类不再处在继承层次结构的顶端.
注意,没有注解为@MappedSuperclass的父类中的属性将被忽略.
除非显式使用Hibernate annotation中的@AccessType注解, 否则将从继承层次结构的根实体中继承访问类型(包括字段或方法)
这对于@Embeddable对象的父类中的属性持久化同样有效. 只需要使用@MappedSuperclass注解即可 (虽然这种方式不会纳入EJB3标准)
可以将处在在映射继承层次结构的中间位置的类注解为@MappedSuperclass.
在继承层次结构中任何没有被注解为@MappedSuperclass 或@Entity的类都将被忽略.
你可以通过 @AttributeOverride注解覆盖实体父类中的定义的列. 这个注解只能在继承层次结构的顶端使用.
@MappedSuperclass public class FlyingObject implements Serializable { public int getAltitude() { return altitude; } @Transient public int getMetricAltitude() { return metricAltitude; } @ManyToOne public PropulsionType getPropulsion() { return metricAltitude; } ... } @Entity @AttributeOverride( name="altitude", column = @Column(name="fld_altitude") ) @AssociationOverride( name="propulsion", joinColumns = @JoinColumn(name="fld_propulsion_fk") ) public class Plane extends FlyingObject { ... }
在上面这个例子中,altitude属性的值最终将持久化到Plane 表的fld_altitude列.而名为propulsion的关联则保存在fld_propulsion_fk外间列.
你可以为@Entity和@MappedSuperclass注解的类 以及那些对象为@Embeddable的属性定义 @AttributeOverride和@AssociationOverride.
Hibernate支持三种基本的继承映射策略:
每个类分层结构一张表(table per class hierarchy)
table per subclass
每个具体类一张表(table per concrete class)
此外,Hibernate还支持第四种稍有不同的多态映射策略:
隐式多态(implicit polymorphism)
It is possible to use different mapping strategies for different branches of the same inheritance hierarchy. You can then make use of implicit polymorphism to achieve polymorphism across the whole hierarchy. However, Hibernate does not support mixing
,
and
mappings under the same root
element. It is possible to mix together the table per hierarchy and table per subclass strategies under the the same
element, by combining the
and
elements (see below for an example).
It is possible to define subclass
, union-subclass
, and joined-subclass
mappings in separate mapping documents directly beneath hibernate-mapping
. This allows you to extend a class hierarchy by adding a new mapping file. You must specify an extends
attribute in the subclass mapping, naming a previously mapped superclass. Previously this feature made the ordering of the mapping documents important. Since Hibernate3, the ordering of mapping files is irrelevant when using the extends keyword. The ordering inside a single mapping file still needs to be defined as superclasses before subclasses.
Suppose we have an interface Payment
with the implementors CreditCardPayment
, CashPayment
, andChequePayment
. The table per hierarchy mapping would display in the following way:
... ... ... ...
Exactly one table is required. There is a limitation of this mapping strategy: columns declared by the subclasses, such as CCTYPE
, cannot have NOT NULL
constraints.
A table per subclass mapping looks like this:
... ... ... ...
Four tables are required. The three subclass tables have primary key associations to the superclass table so the relational model is actually a one-to-one association.
Hibernate's implementation of table per subclass does not require a discriminator column. Other object/relational mappers use a different implementation of table per subclass that requires a type discriminator column in the superclass table. The approach taken by Hibernate is much more difficult to implement, but arguably more correct from a relational point of view. If you want to use a discriminator column with the table per subclass strategy, you can combine the use of
and
, as follows:
... ... ... ...
可选的声明fetch="select"
,是用来告诉Hibernate,在查询超类时, 不要使用外部连接(outer join)来抓取子类ChequePayment
的数据。
You can even mix the table per hierarchy and table per subclass strategies using the following approach:
... ... ... ...
对上述任何一种映射策略而言,指向根类Payment
的 关联是使用
进行映射的。
There are two ways we can map the table per concrete class strategy. First, you can use
.
... ... ... ...
这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。
The limitation of this approach is that if a property is mapped on the superclass, the column name must be the same on all subclass tables. The identity generator strategy is not allowed in union subclass inheritance. The primary key seed has to be shared across all unioned subclasses of a hierarchy.
If your superclass is abstract, map it with abstract="true"
. If it is not abstract, an additional table (it defaults to PAYMENT
in the example above), is needed to hold instances of the superclass.
另一种可供选择的方法是采用隐式多态:
... ... ...
Notice that the Payment
interface is not mentioned explicitly. Also notice that properties of Payment
are mapped in each of the subclasses. If you want to avoid duplication, consider using XML entities (for example,[ ]
in the DOCTYPE
declaration and &allproperties;
in the mapping).
这种方法的缺陷在于,在Hibernate执行多态查询时(polymorphic queries)无法生成带 UNION
的SQL语句。
对于这种映射策略而言,通常用
来实现到 Payment
的多态关联映射。
Since the subclasses are each mapped in their own
element, and since Payment
is just an interface), each of the subclasses could easily be part of another inheritance hierarchy. You can still use polymorphic queries against the Payment
interface.
... ... ... ...
Once again, Payment
is not mentioned explicitly. If we execute a query against the Payment
interface, for example from Payment
, Hibernate automatically returns instances of CreditCardPayment
(and its subclasses, since they also implement Payment
), CashPayment
and ChequePayment
, but not instances ofNonelectronicTransaction
.
There are limitations to the "implicit polymorphism" approach to the table per concrete-class mapping strategy. There are somewhat less restrictive limitations to
mappings.
下面表格中列出了在Hibernte中“每个具体类一张表”的策略和隐式多态的限制。
表 9.1. 继承映射特性(Features of inheritance mappings)
继承策略(Inheritance strategy) | 多态多对一 | 多态一对一 | 多态一对多 | 多态多对多 | Polymorphic load()/get() |
多态查询 | 多态连接(join) | 外连接(Outer join)读取 |
---|---|---|---|---|---|---|---|---|
每个类分层结构一张表 |
|
|
|
|
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
table per subclass |
|
|
|
|
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每个具体类一张表(union-subclass) |
|
|
(forinverse="true" only) |
|
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每个具体类一张表(隐式多态) |
|
不支持 | 不支持 |
|
s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() |
from Payment p |
不支持 | 不支持 |
3.使用singleTable实现继承映射
使用Person、Student和Teacher,其中Person是父类,Student和Teacher是子类,继承Person
根据上面的API,需要在Person使用@Inheritance(strategy=InheritanceType.SINGLE_TABLE),指定继承类型
并使用@DiscriminatorColumn(name="flag",discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("person")
定义标志列的名称和类型,并指明本类使用的标志值
同样的,Student extends Person,并需要使用@DiscriminatorValue("student")指明本类使用的标志值
同样的,Teacher extends Person,并需要使用@DiscriminatorValue("teacher")指明本类使用的标志值
Person
package com.baosight.model;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="flag",discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("person")
public class Person {
private String id;
private String name;
@Id
@GeneratedValue//auto
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Student
package com.baosight.model;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("student")
public class Student extends Person{
private String score;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
}
Teacher
package com.baosight.model;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("teacher")
public class Teacher extends Person{
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
JUnit测试类
package com.baosight.model;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class OrMappingTest {
private static SessionFactory sf = null;
@BeforeClass
public static void beforeClass(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
// 读取配置文件
Configuration cfg = new AnnotationConfiguration();
// 得到session工厂
sf = cfg.configure().buildSessionFactory();
}
@Test
public void testSave() {
Student s = new Student();
s.setName("学生");
s.setScore("80");
Teacher t = new Teacher();
t.setName("教师");
t.setTitle("中级");
Session session = sf.getCurrentSession();
session.beginTransaction();
session.save(s);
session.save(t);
session.getTransaction().commit();
}
@Test
public void testLoad() {
testSave();
Session s = sf.getCurrentSession();
s.beginTransaction();
Student u = (Student) s.load(Student.class, "1");
System.out.println(u.getName());
Person p = (Person) s.load(Person.class, "2");
System.out.println(p.getName());
s.getTransaction().commit();
}
/*@Test
public void testSchemaExport() {
}*/
@AfterClass
public static void afterClass(){
// 关闭session工厂
sf.close();
}
}
注:本测试类可以复用,下面不再赘述
3.1首先执行testSave,运行结果为
可以看到数据库只有1张表person,并有1个flag字段标识类型
3.2再来看看testLoad方法,执行结果为
当知道类型时关联person表的标识字段查询,不知道类型时,直接根据id进行查询
4.使用tablePerClass实现继承映射
使用Person、Student和Teacher,其中Person是父类,Student和Teacher是子类,继承Person
根据上面的API,需要在Person使用@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS),指定继承类型
另外,子类主键继承自父类,需要保证子类主键的唯一,本例使用Table方式生成主键
即使用@TableGenerator(name="tableGEN",table="table_gen",pkColumnName="pk_key",valueColumnName="pk_value",pkColumnValue="teacher",initialValue=1,allocationSize=1)
并在getId上使用@GeneratedValue(strategy=GenerationType.TABLE,generator="tableGEN")
Student和Teacher需要extends Person,并需要使用@Entity
Person
package com.baosight.model;
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.TableGenerator;
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@TableGenerator(name="tableGEN",table="table_gen",pkColumnName="pk_key",valueColumnName="pk_value",pkColumnValue="teacher",initialValue=1,allocationSize=1)
public class Person {
private int id;
private String name;
@Id
@GeneratedValue(strategy=GenerationType.TABLE,generator="tableGEN")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Student
package com.baosight.model;
import javax.persistence.Entity;
@Entity
public class Student extends Person{
private String score;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
}
Teacher
package com.baosight.model;
import javax.persistence.Entity;
@Entity
public class Teacher extends Person{
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
4.1使用testSave测试,结果为
可以看到生成了3张表,并且每张表的字段都是与实体类对应的全部字段
4.2使用testLoad测试
@Test
public void testLoad() {
testSave();
Session s = sf.getCurrentSession();
s.beginTransaction();
Student u = (Student) s.load(Student.class, 1);
System.out.println(u.getName());
Person p = (Person) s.load(Person.class, 2);
System.out.println(p.getName());
s.getTransaction().commit();
}
可以看到通过子类查询时会直接查询对应的表,而通过父类查询时会关联查询3张表
5.使用joined实现继承映射
使用Person、Student和Teacher,其中Person是父类,Student和Teacher是子类,继承Person
根据上面的API,需要在Person使用@Inheritance(strategy=InheritanceType.JOINED),指定继承类型
Student和Teacher需要extends Person,并需要使用@Entity
Person
package com.baosight.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Person {
private String id;
private String name;
@Id
@GeneratedValue//auto
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Student
package com.baosight.model;
import javax.persistence.Entity;
@Entity
public class Student extends Person{
private String score;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
}
Teacher
package com.baosight.model;
import javax.persistence.Entity;
@Entity
public class Teacher extends Person{
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
5.1使用testSave进行测试,结果为
可以看到创建了3张表,并且保存子类实体会同时向父表和子表插入数据,子表和附表是主键关联
5.2再来看看testLoad,测试结果为
可以看到当子类类型确定时会将此子类和父类进行关联查询,当直接查询父类时,会将父类和所有的子类进行关联查询
以上即为继承映射的相关内容,在实际的使用中,singleTable和joined使用的较多,当选择使用继承映射时,需要综合考虑。