hibernate的继承映射

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

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

2.2.4. 映射继承关系

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

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

你可以用 @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中关于继承映射的相关描述

第 9 章 Inheritance mapping

9.1. The three strategies
9.1.1. 每个类分层结构一张表(Table per class hierarchy) 9.1.2. 每个子类一张表(Table per subclass) 9.1.3. Table per subclass: using a discriminator 9.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表” 9.1.5. 每个具体类一张表(Table per concrete class) 9.1.6. Table per concrete class using implicit polymorphism 9.1.7. 隐式多态和其他继承映射混合使用
9.2. 限制

9.1. The three strategies

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

 
     
          
     
 

9.1.1. 每个类分层结构一张表(Table per class hierarchy)

Suppose we have an interface Payment with the implementors CreditCardPaymentCashPayment, 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.

9.1.2. 每个子类一张表(Table per subclass)

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.

9.1.3. Table per subclass: using a discriminator

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的数据。

9.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表”

You can even mix the table per hierarchy and table per subclass strategies using the following approach:


    
        
    
    
    
    ...
    
        
            
            ...
        
    
    
        ...
    
    
        ...
    

对上述任何一种映射策略而言,指向根类Payment的 关联是使用进行映射的。

9.1.5. 每个具体类一张表(Table per concrete class)

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.

9.1.6. Table per concrete class using implicit polymorphism

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


    
        
    
    
    ...



    
        
    
    
    ...



    
        
    
    
    ...

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的多态关联映射。


    
    
    
    
    

9.1.7. 隐式多态和其他继承映射混合使用

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.

9.2. 限制

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 不支持 不支持
本文的例子是使用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,运行结果为

hibernate的继承映射_第1张图片hibernate的继承映射_第2张图片

可以看到数据库只有1张表person,并有1个flag字段标识类型

3.2再来看看testLoad方法,执行结果为

hibernate的继承映射_第3张图片

当知道类型时关联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测试,结果为

hibernate的继承映射_第4张图片hibernate的继承映射_第5张图片hibernate的继承映射_第6张图片hibernate的继承映射_第7张图片hibernate的继承映射_第8张图片

可以看到生成了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();
	}

结果为

hibernate的继承映射_第9张图片hibernate的继承映射_第10张图片
可以看到通过子类查询时会直接查询对应的表,而通过父类查询时会关联查询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进行测试,结果为

hibernate的继承映射_第11张图片hibernate的继承映射_第12张图片hibernate的继承映射_第13张图片hibernate的继承映射_第14张图片hibernate的继承映射_第15张图片

可以看到创建了3张表,并且保存子类实体会同时向父表和子表插入数据,子表和附表是主键关联

5.2再来看看testLoad,测试结果为

hibernate的继承映射_第16张图片hibernate的继承映射_第17张图片
可以看到当子类类型确定时会将此子类和父类进行关联查询,当直接查询父类时,会将父类和所有的子类进行关联查询

以上即为继承映射的相关内容,在实际的使用中,singleTable和joined使用的较多,当选择使用继承映射时,需要综合考虑。




你可能感兴趣的:(hibernate,java,hibernate,继承映射,Inheritance)