本文简单讲述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还支持第四种稍有不同的多态映射策略:
<hibernate-mapping> <subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>
A table per subclass mapping looks like this:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> </class>
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class>
可选的声明fetch="select"
,是用来告诉Hibernate,在查询超类时, 不要使用外部连接(outer
join)来抓取子类ChequePayment
的数据。
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
对上述任何一种映射策略而言,指向根类Payment
的 关联是使用<many-to-one>
进行映射的。
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
<class name="Payment"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class>
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>
这种方法的缺陷在于,在Hibernate执行多态查询时(polymorphic queries)无法生成带 UNION
的SQL语句。
对于这种映射策略而言,通常用<any>
来实现到 Payment
的多态关联映射。
<any name="payment" meta-type="string" id-type="long"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
下面表格中列出了在Hibernte中“每个具体类一张表”的策略和隐式多态的限制。
表 9.1. 继承映射特性(Features of inheritance mappings)
继承策略(Inheritance strategy) | 多态多对一 | 多态一对一 | 多态一对多 | 多态多对多 |
Polymorphic load()/get() |
多态查询 | 多态连接(join) | 外连接(Outer join)读取 |
---|---|---|---|---|---|---|---|---|
每个类分层结构一张表 | <many-to-one> |
<one-to-one> |
<one-to-many> |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
table per subclass | <many-to-one> |
<one-to-one> |
<one-to-many> |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每个具体类一张表(union-subclass) | <many-to-one> |
<one-to-one> |
<one-to-many> (forinverse="true" only) |
<many-to-many> |
s.get(Payment.class, id) |
from Payment p |
from Order o join o.payment p |
支持 |
每个具体类一张表(隐式多态) | <any> |
不支持 | 不支持 | <many-to-any> |
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使用的较多,当选择使用继承映射时,需要综合考虑。
原文:http://blog.csdn.net/u011526599/article/details/51233213