本文简单讲述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.
2.再来看下xml的API中关于继承映射的相关描述
每个具体类一张表(table per concrete class)
此外,Hibernate还支持第四种稍有不同的多态映射策略:
... ... ... ...
A table per subclass mapping looks like this:
... ... ... ...
... ... ... ...
可选的声明fetch="select"
,是用来告诉Hibernate,在查询超类时, 不要使用外部连接(outer join)来抓取子类ChequePayment
的数据。
... ... ... ...
对上述任何一种映射策略而言,指向根类Payment
的 关联是使用
进行映射的。
There are two ways we can map the table per concrete class strategy. First, you can use
.
... ... ... ...
这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。
... ... ...
这种方法的缺陷在于,在Hibernate执行多态查询时(polymorphic queries)无法生成带UNION
的SQL语句。
对于这种映射策略而言,通常用
来实现到Payment
的多态关联映射。
... ... ... ...
下面表格中列出了在Hibernte中“每个具体类一张表”的策略和隐式多态的限制。
表9.1.继承映射特性(Features of inheritance mappings)
继承策略(Inheritance strategy) | 多态多对一 | 多态一对一 | 多态一对多 | 多态多对多 |
Polymorphicload()/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使用的较多,当选择使用继承映射时,需要综合考虑。