hibernate的继承映射

本文简单讲述hibernate的继承映射相关知识,以备不时之需。继承映射,顾名思义就是有继承关系的几个实体之间的映射关系。

1.首先看看annotation的API中关于继承映射的描述

2.2.4. 映射继承关系

EJB3支持三种类型的继承映射:

  • 每个类一张表(Table per class)策略: 在Hibernate中对应<union-class>元素:
  • 每个类层次结构一张表(Single table per class hierarchy)策略:在Hibernate中对应<subclass>元素
  • 连接的子类(Joined subclasses)策略:在Hibernate中对应 <joined-subclass>元素

你可以用 @Inheritance注解来定义所选择的策略. 这个注解需要在每个类层次结构(class hierarchy) 最顶端的实体类上使用.

注意

目前还不支持在接口上进行注解.

2.2.4.1. 每个类一张表

这种策略有很多缺点(例如:多态查询和关联),EJB3规范, Hibernate参考手册, Hibernate in Action,以及其他许多地方都对此进行了描述和解释. Hibernate使用SQL UNION查询来实现这种策略. 通常使用场合是在一个继承层次结构的顶端:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable {
            

这种策略支持双向的一对多关联. 这里不支持IDENTITY生成器策略,因为id必须在多个表间共享. 当然,一旦使用这种策略就意味着你不能使用 AUTO 生成器和IDENTITY生成器.

2.2.4.2. 每个类层次结构一张表

整个继承层次结构中的父类和子类的所有属性都映射到同一个表中, 他们的实例通过一个辨别符(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 注解只能用于实体层次结构的顶端.

2.2.4.3. 连接的子类

当每个子类映射到一个表时, @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进行关联.

2.2.4.4. 从父类继承的属性

有时候通过一个(技术上或业务上)父类共享一些公共属性是很有用的, 同时还不用将该父类作为映射的实体(也就是该实体没有对应的表). 这个时候你需要使用@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表的形式出现, 该表拥有idlastUpdate 和 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中关于继承映射的相关描述

Hibernate支持三种基本的继承映射策略:

此外,Hibernate还支持第四种稍有不同的多态映射策略:

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 <subclass><joined-subclass> and <union-subclass> mappings under the same root <class> element. It is possible to mix together the table per hierarchy and table per subclass strategies under the the same <class> element, by combining the <subclass> and <join> elements (see below for an example).

It is possible to define subclassunion-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.

 <hibernate-mapping>
     <subclass name="DomesticCat" extends="Cat" discriminator-value="D">
          <property name="name" type="string"/>
     </subclass>
 </hibernate-mapping>

另一种可供选择的方法是采用隐式多态:

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

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,[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] in the DOCTYPE declaration and &allproperties; in the mapping).

这种方法的缺陷在于,在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>

Since the subclasses are each mapped in their own <class> 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.

<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="CREDIT_CARD" type="string"/>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
    <subclass name="MasterCardPayment" discriminator-value="MDC"/>
    <subclass name="VisaPayment" discriminator-value="VISA"/>
</class>

<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
    <id name="id" type="long" column="TXN_ID">
        <generator class="native"/>
    </id>
    ...
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CASH_AMOUNT"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CHEQUE_AMOUNT"/>
        ...
    </joined-subclass>
</class>

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 <union-subclass> mappings.

下面表格中列出了在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 pfrom 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 pfrom 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 pfrom Order o join o.payment p支持
每个具体类一张表(隐式多态)<any>不支持不支持<many-to-any>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()from Payment p不支持不支持
本文的例子是使用annotation来进行的

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使用的较多,当选择使用继承映射时,需要综合考虑。




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值