一、映射组成关系
Hibernate 把持久化类的属性分为两种:
- 值(value)类型: 没有 OID,不能被单独持久化,生命周期依赖于所属的持久化类的对象的生命周期。
- 实体(entity)类型:有 OID,可以被单独持久化,有独立的生命周期,无法直接用 property 进行映射。Hibernate 使用
<component>
元素来映射组成关系。
<!--映射组成关系-->
<component name="pay" class="Pay">
<!--指定组成关系的组件的属性-->
<parent name="worker"/> <!--<用于指定组件属性所属的整体类-->
<property name="monthPay" column="monthPay"></property>
<property name="yearPay" column="yearPay"></property>
<property name="vocationWithPay" column="vocationWithPay"></property>
</component>
说明:
① <component>
元素来映射组成关系,其中的class属性:设定组成关系属性的类型。在此处表明 pay 属性为 Pay 类型。
② <parent>
元素指定组件属性所属的整体类,其中的name属性:整体类在组件类中的属性名。
二、映射一对多关联关系
1、单向一对多的关联关系
以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联;而从 Customer 到 Order 是一对多关联。
- 单向 n-1 关联只需从 n 的一端可以访问 1 的一端
- 域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
- Hibernate 使用 元素来映射多对一关联关系
<many-to-one> 元素来映射组成关系
name: 设定待映射的持久化类的属性的名字
column: 设定和持久化类的属性对应的表的外键
class:设定待映射的持久化类的属性的类型
实体类Customer:
public class Customer {
private int customerId;
private String customerName;
public Customer() {
}
//getter/setter方法
}
实体类映射文件Customer.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zm.n21.Customer" table="customer">
<id name="customerId" type="java.lang.Integer">
<column name="customer_id"/>
<generator class="native"/>
</id>
<property name="customerName" type="java.lang.String">
<column name="customer_name"/>
</property>
</class>
</hibernate-mapping>
实体类Orders:
public class Orders {
private int orderId;
private String orderName;
//多对一的映射。多端:Orders;一端:Customer
Customer customer;
public Orders() {
}
//getter/setter方法
}
实体类映射文件Orders.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.n21">
<class name="com.zm.n21.Orders" table="orders">
<id name="orderId" type="java.lang.Integer">
<column name="order_id"/>
<generator class="native"/>
</id>
<property name="orderName" type="java.lang.String">
<column name="order_name"/>
</property>
<!--映射多对一的关联关系,使用many-to-one来映射多对一的关联关系
name:多这一端关联的那一端的属性的名字
class:一那一端的属性对应的类名
column:一那一端在多那一端对应的数据表中的外键的名字
-->
<many-to-one name="customer" class="Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
在hibernate.cfg.xml注册映射文件:
<mapping resource="com/zm/n21/Orders.hbm.xml"/>
<mapping resource="com/zm/n21/Customer.hbm.xml"/>
(1)插入操作的测试
先插入Customer再插入Order:
@Test
public void many2One(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Orders orders1 = new Orders();
orders1.setOrderName("Orders-1");
Orders orders2 = new Orders();
orders2.setOrderName("Orders-2");
//设定关联关系
orders1.setCustomer(customer);
orders2.setCustomer(customer);
//执行save操作
session.save(customer);
session.save(orders1);
session.save(orders2);
}
3条INSERT语句。总结:先插入1的一端,再插入n的一端,只有INSERT语句
先插入Order再插入Customer:
@Test
public void many2One(){
Customer customer = new Customer();
customer.setCustomerName("BB");
Orders orders1 = new Orders();
orders1.setOrderName("Order-3");
Orders orders2 = new Orders();
orders2.setOrderName("Order-4");
//设定关联关系
orders1.setCustomer(customer);
orders2.setCustomer(customer);
/*先插入Orders,再插入Customer*/
session.save(orders1);
session.save(orders2);
session.save(customer);
}
3条INSERT语句,2条UPDATE语句。先插入n的一端,再插入1的一端,会多出UPDATE语句。因为在插入多的一端时,无法确定1的一端的外键值,只能等1的一端插入后,再额外发送UPDATE语句。
总的来说,推荐先插入1的一端,再插入n的一端。
(2)多对一单向映射查询操作时
- 若查询多的一端的一个对象,默认情况下,只查询了多的一端的对象,而没有查询关联的1的那端的对象。
- 在需要使用到关联的对象时,才发送对应的SQL语句
- 在查询Customer对象时,由多的一端导航到1的一端时,若此时session已经被关闭,则在默认情况下可能会发生懒加载异常(LazyInitializationException)
- 获取Order对象时,默认情况下,其关联的Customer对象是一个代理对象
(3)多对一单向映射修改操作时
修改1那一端的数据时,会发送两个SELECT语句(一个是查询多端对象,一个是查询1端对象),然后再发送一个UPDATE语句。
(4)多对一单向映射删除操作时
在不设定级联关系的情况下,且1的这一端的对象有n的对象在引用,不能直接删除1这一端的对象。
三、双向一对多映射
- 双向 1-n 与 双向 n-1 是完全相同的两种情形
- 双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
- 域模型:从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
- 关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
- Hibernate 使用 元素来映射 set 类型的属性
<!-- set: 映射 set 类型的属性;name:一的这一端关联的多的那一端的属性名;
table: set 中的元素对应的记录放在哪一个数据表中。该值需要和多对一的多的那个表的名字一致 -->
<set name="orders" table="ORDERS">
<!-- 指定关联的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
几个注意点:
- 当 Session 从数据库中加载 Java 集合时,创建的是 Hibernate 内置集合类的实例,因此在持久化类中定义集合属性时必须把属性声明为 Java 接口类型。例如应该声明为Set而不是HashSet。
①Hibernate 的内置集合类具有集合代理功能,支持延迟检索策略。类似于在单向n-1关系的get操作,如果在双向1-n的get操作中获取了customer对象,如果不使用它存放order的集合,那么这个集合就不会被加载,只有使用到时才会加载。
② 事实上,Hibernate 的内置集合类封装了 JDK 中的集合类,这使得 Hibernate 能够对缓存中的集合对象进行脏检查,按照集合对象的状态来同步更新数据库。 - 在定义集合属性时,通常把它初始化为集合实现类的一个实例。这样可以提高程序的健壮性,避免应用程序访问取值为 null 的集合的方法抛出
NullPointerException
。
代码实例如下:
实体类对象Order:
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
//getter/setter方法
}
实体类对象Customer:
public class Customer {
private Integer customerId;
private String customerName;
/*注意两点:
* 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取
* 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的集合实现.
* 2. 需要把集合进行初始化, 可以防止发生空指针异常
*/
private Set<Order> orders = new HashSet<>();
//getter/setter方法
}
实体类对象对应的映射文件:
Order.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.n21.both">
<class name="Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>
<!--
映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系
name: 多这一端关联的一那一端的属性的名字
class: 一那一端的属性对应的类名
column: 一那一端在多的一端对应的数据表中的外键的名字
-->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.n21.both">
<class name="Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
<!-- 映射 1 对多的那个集合属性 -->
<!-- set: 映射 set 类型的属性, name:一的这一端关联的多的那一端的属性名,table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 -->
<set name="orders" table="ORDERS">
<!-- 执行多的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
set元素中的3个重要属性
(1)set元素中的inverse
属性:
- 指定由哪一方来维护关联关系。
inverse = false
的为主动方,inverse = true
的为被动方。由主动方
负责维护关联关系通常设置为true
,以指定由多的一端来维护关联关系。在没有设置inverse=true
的情况下,父子两边都维护父子。 - 在 1-n 关系中,将 n 方设为主控方将有助于性能改善关系 。在 1-N 关系中,若将 1 方设为主控方,会额外多出 update 语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束。
(2)set元素中的cascade
属性:
- 在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素,
<set>
、<many-to-one>
和<one-to-one>
都有一个cascade
属性,它用于指定如何操纵与当前对象关联的其他对象。
在开发时不建议设定该属性,建议使用手工的方式来处理。
(3)集合排序属性
- 元素有一个 order-by 属性,如果设置了该属性,当 Hibernate 通过 select 语句到数据库中检索集合对象时,利用 order by 子句进行排序
- order-by 属性中还可以加入 SQL 函数
在hibernate.cfg.xml文件中注册:
<mapping resource="com/zm/n21/both/Customer.hbm.xml"/>
<mapping resource="com/zm/n21/both/Order.hbm.xml"/>
1、多对1双向映射保存操作
(1)执行save操作:先插入Customer,再插入Order。3条INSERT,2条UPDATE。因为1的一端和多额一端都维护关联关系,所以会多出uPDATE。可以在1的一端的set节点指定inverse=true,来使1的一端放弃维护关联关系!建议设定set的inverse=true,建议先插入1的一端,后插入多的一端。好处:不会多出UPDATE语句。
@Test
public void testManytoOneSave(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Order order1 = new Order();
order1.setOrderName("Order-1");
Order order2 = new Order();
order2.setOrderName("Order-2");
//设定关联关系
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//执行save操作:先插入Customer,再插入Order
session.save(customer);
session.save(order1);
session.save(order2);
}
(2)执行save操作:先插入Order,再插入Customer。3条INSERT,4条UPDATE。
//执行save操作:先插入Order,再插入Customer
session.save(order1);
session.save(order2);
session.save(customer);
2、双向1对多的获取操作
@Test
public void testOneToMany(){
//1. 对n的一端的集合使用延迟加载
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
//2.返回的多的一端的集合是Hibenrate内置的集合类型,该类型具有延迟加载和存放代理对象的功能
//class org.hibernate.collection.internal.PersistentSet
System.out.println(customer.getOrders().getClass());
//session.close();
//3.可能会抛出懒加载异常LazyInitializationException
//4.需要使用集合中元素的时候进行初始化
}
3、双向1对多修改操作
四、基于外键的1对1关系映射
- 对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端,增加
many-to-one
元素。为many-to-one
元素增加unique=“true”
属性来表示为1-1关联。
<!--使用外键的方式来映射1-1关联关系-->
<many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
unique="true"></many-to-one>
- 另一端需要使用
one-to-one
元素,该元素使用property-ref
属性指定使用被关联实体主键以外的字段作为关联字段。
<!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>
相关实例如下:
实体类对象Department:
public class Department {
private Integer deptId;
private String deptName;
private Manager manager;
//getxxx/setxxx方法
}
对应的映射文件Department.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zm.one2one.foreignkey.Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<generator class="native"/>
</id>
<property name="deptName" type="java.lang.String">
<column name="dept_name"/>
</property>
<!--使用外键的方式来映射1-1关联关系-->
<many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
unique="true"></many-to-one>
</class>
</hibernate-mapping>
实体类对象Manager:
public class Manager {
private Integer mangerId;
private String managerName;
private Department dept;
//getxxx/setxxx方法
}
对应的映射文件Manager.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zm.one2one.foreignkey.Manager" table="MANAGERS">
<id name="mangerId" type="java.lang.Integer">
<column name="manger_id"/>
<generator class="native"/>
</id>
<property name="managerName" type="java.lang.String">
<column name="manager_name"/>
</property>
<!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射
没有外键的一端需要使用one-to-one元素,该元素使用property-ref属性指定使用被关联主键以外的字段作为关联字段-->
<one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>
</class>
</hibernate-mapping>
在hibernate的配置文件中注册对应的映射文件:
<!--需要关联的hibernate映射文件.hbm.xml-->
<mapping resource="com/zm/one2one/foreignkey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/foreignkey/Manager.hbm.xml"/>
1、保存操作
@Test
public void testSave(){
Department dept = new Department();
// dept.setDeptName("Dept-AA");
dept.setDeptName("Dept-BB");
Manager manager = new Manager();
// manager.setManagerName("Manager-AA");
manager.setManagerName("Manager-BB");
//设定关联关系
manager.setDept(dept);
dept.setManager(manager);
//先保存的是没有外键列的对象,再保存有外键列的对象。2条INSERT语句,没有UPDATE语句
// Hibernate: insert into MANAGERS (manager_name) values (?)
// Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
// session.save(manager);
// session.save(dept);
//先保存有外键列对象,在保存无外键列对象。2条INSERT语句,1条UPDATE语句
//Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
//Hibernate: insert into MANAGERS (manager_name) values (?)
//Hibernate: update DEPARTMENTS set dept_name=?, manger_id=? where dept_id=?
session.save(dept);
session.save(manager);
}
总结:
- 先保存没有外键列的对象,再保存有外键列的对象:2条INSERT语句,没有UPDATE语句。先保存有外键列对象,在保存无外键列对象:2条INSERT语句,1条UPDATE语句
- 推荐先保存无外键列的对象,在保存有外键列的对象。
2、查询操作
@Test
public void testGet(){
//1.默认情况下对关联属性使用懒加载
//Hibernate: select department0_.dept_id as dept_id1_0_0_, department0_.dept_name as dept_nam2_0_0_,
// department0_.manger_id as manger_i3_0_0_ from DEPARTMENTS department0_ where department0_.dept_id=?
//Dept-AA
Department department = (Department) session.get(Department.class, 1);
System.out.println(department.getDeptName());
//2.关闭session后,再获取对象的属性值,会出现懒加载异常LazyInitializationException
// session.close();
// Manager manager = department.getManager();
// System.out.println(manager.getClass());
// System.out.println(manager.getManagerName());
//在one-to-one中没有设置property-ref属性的值时,查询Manager对象的连接条件会出现错误,
// 应该是dept.manager_id=manager.manager_id,而不是dept.dept_id=manager.manager_id
//在添加了property-ref的属性设置时正确
Manager manager = department.getManager();
System.out.println(manager.getManagerName());
}
在查询没有外键的实体对象时,使用的是左外连接查询,一并查出其关联的对象,并且已经初始化。
@Test
public void testGet2(){
//在查询没有外键的实体对象时,使用的是左外连接查询,一并查出其关联的对象,并且已经初始化
Manager manager = (Manager) session.get(Manager.class,1);
System.out.println(manager.getManagerName());
System.out.println(manager.getDept().getDeptName());
}
五、基于主键映射的1对1
- 基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”
对方
”的主键来生成自己的主键,自己并不能独立生成主键。 子元素指定使用当前持久化类的哪个属性作为 “对方”。
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<!--使用外键的方式生成当前的主键-->
<generator class="foreign">
<!--property属性指定使用当前持久化类的哪一个属性的主键作为外键-->
<param name="property">manager</param>
</generator>
</id>
- 采用foreign主键生成器策略的一端增加
one-to-one
元素映射关联属性,其one-to-one
属性还应增加constrained=“true”
属性;另一端增加one-to-one
元素映射关联属性。 - constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键。
<!--采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one节点还应
增加constrained="true"属性,以使当前的主键上添加外键约束-->
<one-to-one name="manager" class="Manager" constrained="true"/>
实例代码:
实体类对象Department:
public class Department {
private Integer deptId;
private String deptName;
private Manager manager;
//getxxx/setxxx方法
}
实体类对象Manager:
public class Manager {
private Integer mangerId;
private String managerName;
private Department dept;
//getxxx/setxxx方法
}
对应的映射文件:
Department.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.one2one.primarykey">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<!--使用外键的方式生成当前的主键-->
<generator class="foreign">
<!--property属性指定使用当前持久化类的哪一个属性的主键作为外键-->
<param name="property">manager</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="dept_name"/>
</property>
<!--采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one节点还应
增加constrained="true"属性,以使当前的主键上添加外键约束-->
<one-to-one name="manager" class="Manager" constrained="true"/>
</class>
</hibernate-mapping>
Manager.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.one2one.primarykey">
<class name="Manager" table="MANAGERS">
<id name="mangerId" type="java.lang.Integer">
<column name="manger_id"/>
<generator class="native"/>
</id>
<property name="managerName" type="java.lang.String">
<column name="manager_name"/>
</property>
<!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="Department"></one-to-one>
</class>
</hibernate-mapping>
在hibernate的配置文件中注册:
<mapping resource="com/zm/one2one/primarykey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/primarykey/Manager.hbm.xml"/>
1、保存操作
不论是否先插入有外键列的一端,都只有INSERT语句。
@Test
public void testSave(){
Department dept = new Department();
dept.setDeptName("Dept-AA");
//dept.setDeptName("Dept-BB");
Manager manager = new Manager();
manager.setManagerName("Manager-AA");
//manager.setManagerName("Manager-BB");
//设定关联关系
dept.setManager(manager);
manager.setDept(dept);
//先插入哪一个都一样,只有INSERT语句
session.save(manager);
session.save(dept);
// session.save(dept);
// session.save(manager);
}
六、映射多对多关联关系
1、单向多对多关联关系
- n-n 的关联必须使用连接表
- 与 1-n 映射类似,必须为
set
集合元素添加key
子元素,指定CATEGORIES_ITEMS
表中参照CATEGORIES
表的外键为CATEGORIY_ID
。与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用many-to-many
,many-to-many 子
元素的class
属性指定 items 集合中存放的是 Item 对象,column
属性指定CATEGORIES_ITEMS
表中参照 ITEMS 表的外键为I_ID
<!--table:指定中间表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
<many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
</set>
实例代码:
实体类Category:
package com.zm.n2n;
import java.util.HashSet;
import java.util.Set;
public class Category {
private Integer categoryId;
private String categoryName;
private Set<Item> items = new HashSet<>();
//getxxx/setxxx方法
}
实体类Item:
public class Item {
private Integer id;
private String name;
//getxxx/setxxx方法
}
实体类对应的映射文件:
Category.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zm.n2n.Category" table="CATEGORIES">
<id name="categoryId" type="java.lang.Integer">
<column name="category_id"/>
<generator class="native"/>
</id>
<property name="categoryName" type="java.lang.String">
<column name="category_name"/>
</property>
<!--table:指定中间表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
<many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.zm.n2n.Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
</class>
</hibernate-mapping>
在hibernate的配置文件中注册映射文件:
<mapping resource="com/zm/n2n/Category.hbm.xml" />
<mapping resource="com/zm/n2n/Item.hbm.xml"/>
2、双向多对多关联关系
- 双向 n-n 关联需要两端都使用集合属性
- 双向n-n关联必须使用连接表
- 集合属性应增加 key 子元素用以映射外键列,集合元素里还应增加
many-to-many
子元素关联实体类 - 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名。两个集合元素 set 的 table 元素的值必须指定,而且必须相同。set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a。
- 对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true。否则两端都维护关联关系可能会造成主键冲突.
实例代码:
实体类Category:
public class Category {
private Integer categoryId;
private String categoryName;
private Set<Item> items = new HashSet<>();
//setxxx/getxxx方法
}
实体类Item:
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet<>();
//getxxx/setxxx方法
}
对应的映射文件:
Category.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.n2n.both">
<class name="Category" table="CATEGORIES">
<id name="categoryId" type="java.lang.Integer">
<column name="category_id"/>
<generator class="native"/>
</id>
<property name="categoryName" type="java.lang.String">
<column name="category_name"/>
</property>
<!--table:指定中间表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item.hbm.xml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.n2n.both">
<class name="Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<!--将items表作为主动方-->
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="Category" column="C_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
七、继承关系映射
Hibernate 的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系。学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。
父类Person:
public class Person {
private Integer id;
private String name;
private Integer age;
//getxxx/setxxx方法
}
子类Student:
public class Student extends Person{
private String school;
//getxxx/setxxx方法
}
Hibernate支持三种继承映射策略:
1、 subclass
将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
- 采用
subclass
的继承映射可以实现对于继承关系中父类和子类使用同一张表 - 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到低是哪个类的实例----这个列被称为辨别者列(discriminator)
- 在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
- 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中
Person.hbm.xml映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.zm.inherit.subclass">
<class name="Person" table="PERSON" discriminator-value="PERSON">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<!--配置辨别者列-->
<discriminator column="TYPE" type="java.lang.String"></discriminator>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE"/>
</property>
<!--映射子类Student,使用subclass进行映射
discriminator-value:用于指定辨别者列的值
-->
<subclass name="Student" discriminator-value="STUDENT">
<property name="school" type="java.lang.String" column="SCHOOL"></property>
</subclass>
</class>
</hibernate-mapping>
(1)插入操作
/*插入操作:
* 1.对于子类对象只需把记录插入到一张数据表中
* 2.辨别者列有Hibernate自动维护
* */
@Test
public void testSave(){
Person person = new Person();
person.setName("AA");
person.setAge(21);
session.save(person);
Student student = new Student();
student.setName("Stu_AA");
student.setAge(12);
student.setSchool("超星学院");
session.save(student);
}
所插入的数据在一张表中,以TYPE字段区分对象:
(2)查询操作
/*查询:
* 1.查询父类记录,只需要查询一张数据表
* 2.对于子类记录,也只需要查询一张数据表(根据type)
* */
@Test
public void testGet(){
List<Person> persons = session.createQuery("from Person").list();
System.out.println(persons.size());
List<Student> students = session.createQuery("from Student").list();
System.out.println(students.size());
}
缺点:
① 使用了辨别者列
② 子类独有的字段不能添加非空约束
③ 若继承层次较深,则数据表的字段也会较多
2、joined-subclass
- 采用
joined-subclass
元素的继承映射可以实现每个子类一张表。 - 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
- 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键。
- 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中
(1)插入操作
对于子类对象至少需要插入到两张表中。
(2)查询操作
① 查询父类记录,做一个左外连接
② 对于子类记录,做一个内连接
优点:
① 不需要使用辨别者列
② 子类独有的字段能添加非空约束
③ 没有冗余字段
3、 union-subclass
- 采用
union-subclass
元素可以实现将每一个实体对象映射到一个独立的表中。 - 子类增加的属性可以有非空约束 — 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
- 子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录
- 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和。
- 在这种映射策略下,既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键。
- 使用
union-subclass
映射策略时不可使用 identity 的主键生成策略,因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的。受此影响,也不该使用native
主键生成策略。因为native
会根据数据库来选择使用identity
或sequence
.
(1)保存操作
对于子类对象只需把记录插入到一张数据表中。
(2)查询操作
① 查询父类记录,需把父表和子表汇总到一起再做查询,性能较差
② 对于子类记录,只需要查询一张数据表
优点:
① 无需使用辨别者列
② 子类独有的字段能添加非空约束
缺点:
① 存在冗余字段
② 若更新父表的字段,则更新的效率较低
三种继承映射方式的比较
推荐使用union-subclasss和joined-subclass