Tip
所有例子持久化类的set、get方法自行补充,减少篇幅。
继承映射
对于面向对象的程序设计语言,继承、多态是两个最基本的概念。Hibermate 的继承映射可以理解两个持久化类之间的继承关系,例如老师和人之间的关系,老师继承了人,可以认为老师是一一个特殊的人,如果对人进行查询,老师实例也将被得到,而无须关注人的实例、老师的实例底层数据库的存储。
采用subclass 继承映射
使用例子
public class Person {
private Integer id;
private String name;
private String gender;
private Address address;// 组件属性
}
public class Address {
private String detail;
private String zip;// 邮政编码
private String country;
}
public class Customer extends Person {
private String comments;// 评论
private Employee employeer;
}
public class Employee extends Person {
private String title;// 职位信息
private double salary;
private Set customers = new java.util.HashSet<Customer>();
// 雇员和客户的关系
private Manager manager;// 雇员和经理的关系
}
public class Manager extends Employee {
private String department;// 部门信息
private Set<Employee> employeers = new java.util.HashSet<Employee>();
映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="com.example.test.bean">
<class name="Person" table="PERSON" dynamic-insert="true"
dynamic-update="false" discriminator-value="comman_person">
<id name="id" type="integer" column="person_id">
<generator class="identity">
</generator>
</id>
<discriminator column="type" type="string"></discriminator>
<property name="name" type="string" column="person_name"></property>
<property name="gender" type="string" column="person_gender"></property>
<component name="address"><!--映射组件属性 -->
<property name="detail" type="string" column="address_detail"></property>
<property name="zip" type="string" column="address_zip"></property>
<property name="country" type="string" column="address_country"></property>
</component>
<subclass name="Employee" discriminator-value="employee"><!-- 这里的name 就是person
子类的类名 -->
<property name="salary" type="double" column="employee_salary"></property>
<property name="title" type="string" column="employee_title"></property>
<many-to-one name="manager" class="Manager" column="manager_id"></many-to-one><!--
多个雇员对应一个经理 -->
<set name="customers" inverse="true"><!-- 一对多的关系,把控制权给多方 -->
<key column="employee_id"></key><!-- 该类是默认关联本实例,也就是person的主键,可以通过property-ref去指定 -->
<one-to-many class="Customer"></one-to-many><!--一个雇员多个客户 -->
</set>
<subclass name="Manager" discriminator-value="manager"><!-- 这个subclass
为啥要写在employee subclass 中,原因是继承于employee,不是继承于person -->
<property name="department" type="string" column="manager_department"></property>
<set name="employeers">
<key column="manager_id"></key><!-- 该类是默认关联本实例,也就是person的主键,可以通过property-ref去指定 -->
<one-to-many class="Employee"></one-to-many><!--一个经理对应多个职员 -->
</set>
</subclass>
</subclass>
<subclass name="Customer" discriminator-value="customer">
<property name="comments" type="string" column="customer_comments"></property>
<many-to-one name="employeer" class="Employee" column="employee_id"></many-to-one>
<!-- 产生外键 参照 employee 表的employee 的主键 -->
<!--这里和customer 配置是差不多的 -->
</subclass>
</class>
</hibernate-mapping>
Person p = new Person();
Address adress = new Address();
adress.setCountry("china");
adress.setDetail("way 78");
adress.setZip("111111");
p.setAddress(adress);
p.setGender("woman");
p.setName("laoqiang");
ss.save(p);//插入一个普通人
com.example.test.bean.Customer c = new com.example.test.bean.Customer();
Address adress1 = new Address();
adress1.setCountry("china");
adress1.setDetail("way 79");
adress1.setZip("22222");
c.setAddress(adress1);
c.setGender("man");
c.setName("laohe");
ss.save(c);//插入一个客户
com.example.test.bean.Customer c1 = new com.example.test.bean.Customer();
Address adress5 = new Address();
adress5.setCountry("china");
adress5.setDetail("way 79");
adress5.setZip("22222");
c1.setAddress(adress5);
c1.setGender("woman");
c1.setName("laolai");
ss.save(c1);//插入一个客户
com.example.test.bean.Manager m = new com.example.test.bean.Manager();
Address adress3 = new Address();
adress3.setCountry("china");
adress3.setDetail("way 09");
adress3.setZip("28882");
m.setDepartment("it");
m.setAddress(adress3);
m.setGender("man");
m.setName("laocai");
ss.save(m);//插入一个经理
com.example.test.bean.Employee e = new com.example.test.bean.Employee();
Address adress2 = new Address();
adress2.setCountry("china");
adress2.setDetail("way 09");
adress2.setZip("28882");
e.setAddress(adress2);
e.setGender("man");
e.setName("laocai");
e.setSalary(1200.00);
e.setManager(m);//将雇员和经理关联
e.setTitle("it");
Set set = new java.util.HashSet<com.example.test.bean.Customer>();
set.add(c);
set.add(c1);
e.setCustomers(set);
e.setManager(m);
ss.save(e);//插入一个雇员
com.example.test.bean.Employee e1 = new com.example.test.bean.Employee();
Address adress4 = new Address();
adress4.setCountry("china");
adress4.setDetail("way 16");
adress4.setZip("56666");
e1.setAddress(adress4);
e1.setGender("man");
e1.setName("laogui");
e1.setManager(m);//将雇员和经理关联
e.setSalary(2400.00);
e.setTitle("work");
ss.save(e1);//插入一个雇员
在这种映射策略下,整个继承树的所有实例都将保存在同一个表内,即对于如上的Person、Employee、Customer 和Manager实例都将保存在同一个表内。因为将父、子类的实例全部保存在同一个表内, 因此,需要在该表中额外增加一列,使用该列来区分每行记录到底是哪个类的实例一这个列被称为辨别者列( discriminator)。
在这种映射策略下,使用<subclas…>来映射子持久化类,使用discriminator元素来映射辨别者列,定义在class 标签中。除此之外,每个类映射中都需要指定辨别者列的值。 该列的值没有实际意义,仅用于区分每条记录对应哪个持久化类。
每个子持久化类不再使用<class…/>元素进行映射,而是改为使用<subclas…>元素进行映射。实际上, <subclas…>元素和<class…>元素的配置几乎一一样,只是<subclas…/>元素用于映射子持久化类,而<las…>元素用于映射普通持久化类。
<subclass…>元素比<clas…/>元素多了一个extends属性,该属性指定该持久化类的父类(必须是持久化类), 当把<subcas…>作为<clas…/>元素或者<subcla…元素的子元素来使用时,无须指定extends属性, Hibermate将自动把该<subca…>的父<clas…/>元素或者<subcas…>元素所映射的持久化类当成父类。但映射文件把<subclas…/>当成<hibermate-mappng…/>元素的子元素使用时,必须指定extends属性。
数据库中见到很多NULL值,这正是这种映射策略的劣势:所有子类定义的字段,不能有非空约束(也就是字段必须可以为NULL,这里你要注意数据库中NULL 和 空值的区别,NULL代表是未知的,占一定的空间,空值就是没有值,不占任何空间)。因为如果为这些字段增加非空约束(就是不能为空),那么父类的实例根本看不到子类的实例的属性,这肯定引起数据完整性冲突,导致父类的实例无法保存到数据库。
但这种映射策略也有一一个 非常大的好处:在这种映射策略下,整棵继承树的所有数据都保存在一张表内,因此不管进行怎样的查询、不管查询继承树中的哪一层的实体,底层数据库都只需在一张表中查询即可,无须进行多表连接查询,也无须进行union查询,因此性能比较好。
join-subclass 继承映射
采用这种映射策略时,父类实例保存在父类表里,而子类实例则由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例(子类实例下面可能还有实例),因此必然也包含了父类实例的属性,于是将子类与父类共有的
属性保存在父类表中;而子类增加的属性,则保存在子类表中。
使用实例
持久类不变,修改映射文件:
<hibernate-mapping package="com.example.test.bean">
<class name="Person" table="PERSON" dynamic-insert="true"
dynamic-update="false" >
<id name="id" type="integer" column="person_id">
<generator class="identity">
</generator>
</id>
<property name="name" type="string" column="person_name"></property>
<property name="gender" type="string" column="person_gender"></property>
<component name="address"><!--映射组件属性 -->
<property name="detail" type="string" column="address_detail"></property>
<property name="zip" type="string" column="address_zip"></property>
<property name="country" type="string" column="address_country"></property>
</component>
<joined-subclass name="Employee"><!--name 是子类的类名 -->
<key column="employee_id"></key><!-- property-ref默认关联 父类的主键,外键列 -->
<property name="title" column="employee_title" type="string"/>
<property name="salary" column="employee_salary" type="double"/>
<set name="customers" inverse="true"><!-- 控制权给多方 -->
<key column="employee_id"/><!-- 外键列,关联本实例employee的主键 ,这个外键列也是产生在customer中的-->
<one-to-many class="Customer" ></one-to-many>
</set>
<joined-subclass name="Manager"><!-- 继承于employee,所以在employee 的joined——subclass 标签中 -->
<key column="manager_id"/><!--property-ref默认关联 父类employee的主键,外键列,这个地方会有问题,就是employee 和manager 双方都持有外键约束,注意一下,删除表的时候,你最好手动删除一个外键,不然无法删除 -->
<property column="manager_department" name="department" type="string"/>
<set name="employeers" inverse="true">
<key column="manager_id"></key><!-- 外键列,关联本实例employee的主键 -->
<one-to-many class="Employee"></one-to-many>
</set>
</joined-subclass>
</joined-subclass>
<joined-subclass name="Customer"><!--继承于person -->
<key column="customer_id" /><!--关联父类的主键 -->
<property column="customer_comments" name="comments" type="string"></property>
<many-to-one name="employeer" column="employee_id" class="Employee"></many-to-one>
<!-- property——ref 关联的是关联持久类对象的主键,产生的外键是在customer这张表中 -->
</joined-subclass>
</class>
</hibernate-mapping>
在这种映射策略下,无须使用辨别者列,但需要为每个子类使用元素映射共有主键,这个主键列还将参照父类表的主键列。
测试类依旧使用上面的代码不变。
通过查看数据库,发现每一个实例都产生了一张表,而子类的属性则保存在自己的表,由于不在同一个表中,所以子类的属性可以添加非空约束。
采用joined- subclass映射策略时,无须使用辨别者列,子类增加的属性也可以拥有非空约束,是一种比较理想的映射策略。只是在查询子类实例的数据时,可能需要跨越多个表来查询。
union-class 继承映射
与joined-subclass映射策略不同的是,子类实例的数据仅保存在子类表中,没有在父类表中有任何记录。在这种映射策略下,子类表的字段会比父类表字段要多,因为子类表的字段等于父类属性加子类增加属性的总和。
使用例子
持久类不变,修改映射文件:
<hibernate-mapping package="com.example.test.bean">
<class name="Person" table="PERSON" dynamic-insert="true"
dynamic-update="false" >
<id name="id" type="integer" column="person_id">
<generator class="org.hibernate.id.MultipleHiLoPerTableGenerator"><!-- 这种主键身成的方式,会通过一张表来保存
,存该主键的值,该值是根据某个公式去计算,这个不管,大致了一下。
可选属性:
table: table name (default hibernate_sequences)
primary_key_column: key column name (default sequence_name) 主键的列名
value_column: hi value column name(default sequence_next_hi_value) 主键值的列名
primary_key_value: key value for the current entity (default to the entity's primary table name) 主键值
primary_key_length: length of the key column in DB represented as a varchar (default to 255)
max_lo: max low value before increasing hi (default to Short.MAX_VALUE)
-->
<param name="max_lo">100</param><!-- 主键最大值 -->
</generator>
</id>
<property name="name" type="string" column="person_name"></property>
<property name="gender" type="string" column="person_gender"></property>
<component name="address"><!--映射组件属性 -->
<property name="detail" type="string" column="address_detail"></property>
<property name="zip" type="string" column="address_zip"></property>
<property name="country" type="string" column="address_country"></property>
</component>
<union-subclass name="Employee" table="employee_info"><!-- name 指定子类的类名,实例指定表生成的名字 -->
<property name="title" column="employee_title" type="string"/>
<property name="salary" column="employee_salary" type="double"/>
<set name="customers" inverse="true">
<key column="employee_id"></key><!-- 外键列,产生在customer中,关联的是本实例的主键-->
<one-to-many class="Customer"></one-to-many>
</set>
<union-subclass name="Manager" table="manager_info"><!--子类继承于employee,故作为employee union——subclass子标签 -->
<property name="department" column="manager_department" type="string"></property>
<set name="employeers" inverse="true">
<key column="manager_id"></key>
<one-to-many class="Employee"></one-to-many>
</set>
</union-subclass>
</union-subclass>
<union-subclass name="Customer" table="customer_info">
<property name="comments" column="customer_comments" type="string"></property>
<many-to-one class="Employee" column="employee_id" name="employeer"></many-to-one>
</union-subclass>
</class>
</hibernate-mapping>
测试类不变。
在这种映射策略下,既不需要使用辨别者列,也无须使用<key…>元素来映射共有主键。
在这种映射策略下,不同持久化类实例保存在不同的表中,不会出现加载一个实例内容时需要跨越多个表取数据的情况。
该映射策略不可以使用identity 主键生成策略,因为同一类继承层次中所有实体类必须使用同一一个主键种子(即多个持久化实体对应的记录的主键是连续的)。
这是采用上述主键生成器,生成的表: