映射一對多關聯關係
- 在領域模型中,類與類之間最普遍的關係就是關聯關係。
- 在UML中,關聯是有方向的。
以Customer
和Order
爲例:一個用戶能發出多個訂單,而一個訂單隻能屬於一個客戶,從Order
到Customer
的關聯是多對一的關聯,而從Customer
到Order
是一對多的關聯。
單向n-1
- 單向n-1關聯只需從n端可以訪問1端。
- 域模型:從
Order
到Customer
的多對一單向關聯需要在Order
類中定義一個Customer
屬性,而在Customer
類中無需定義存放Order
對象的集合屬性。 - 關係數據模型:orders表中的customer_id參照customer表的主鍵。
- 在Hibernate映射文件中使用
<many-to-one>
標籤可以來映射多對一的關聯關係,其標籤有如下屬性:
name
:一的那一端的屬性的名字
class
:一的那一端對應的類名
column
:一的那一端在多的一端中對應的數據表中的外鍵的名字
在此以Order和Customer爲例,Order與Customer爲多對一關係。首先我們建立Order
類和Customer
類,如下:
package com.cerr.hibernate.n21;
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
package com.cerr.hibernate.n21;
public class Customer {
private Integer customerId;
private String customerName;
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
IDEA只支持由數據表映射出實體類和映射文件,而我們此時是要通過實體類和映射文件來生成數據表,因此映射文件應該我們自己來新建。因此我們新建Customer.hbm.xml
文件和Order.hbm.xml
文件。映射信息如下:
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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
</class>
</hibernate-mapping>
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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.Order" table="orders" schema="hibernate5" dynamic-update="true">
<id name="orderId" column="order_id">
<generator class="native" />
</id>
<property name="orderName" column="order_name"/>
<!-- 映射多對一的關聯關係 -->
<many-to-one name="customer" class="com.cerr.hibernate.n21.Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
保存操作的測試類:
package com.cerr.hibernate.n21;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test4(){
Customer customer = new Customer();
customer.setCustomerName("aa");
Order order = new Order();
order.setOrderName("1");
Order order1 = new Order();
order1.setOrderName("2");
//設定關聯關係
order.setCustomer(customer);
order1.setCustomer(customer);
//先插入1的一端,再插入n的一端。只有三條insert語句。
session.save(customer);
session.save(order);
session.save(order1);
//如果先插入n的一端,再插入1的一端,則會多出來num(n的一端)條update語句。
// session.save(order);
// session.save(order1);
// session.save(customer);
}
}
在測試類中我們分別新建了兩個Order
類和一個Customer
類,並且在調用save()
時我們使用了兩種情況,一種情況是先保存Customer
類(即先保存1),再保存Order
類(再保存n)。另一種是先保存Order
類(先保存n),再保存Customer
類(再保存1)。兩種結果都會插入成功,但是第一種的話只會生成3條INSERT語句,但是第二種會生成3條INSERT語句和2條UPDATE語句。因爲先插入n的一端時,無法確定1的一端的外鍵值,所以只能先進行插入,然後等1的一端插入之後,再額外的發送UPDATE去修改n的一端的外鍵屬性。
所以在插入的時候我們推薦先插入1的那一端,再插入n的那一端。
生成的數據表如下:
查詢操作的測試類:
package com.cerr.hibernate.n21;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test5(){
//若查詢n的一端的對象,默認情況下只查詢n的一端的對象,而沒有查詢1的那一端的對象
Order order = session.get(Order.class,1);
System.out.println(order);
//在需要使用關聯的對象時,纔會發送對應的sql語句。
Customer customer = order.getCustomer();
System.out.println(customer);
}
}
會發現若查詢n的一端的對象,默認情況下只查詢n的一端的對象,而沒有查詢1的那一端的對象,只有在需要使用到關聯對象時,纔會發送對應的sql語句,而如果在查詢n端之後把session
關了,纔來使用關聯對象(Customer
)會導致LazyInitializationException異常
。
在獲取Order
對象後,打印其關聯的Customer
對象,發現其關聯的Customer
對象是一個代理對象。
使用刪除操作的話,在沒有設定級聯關係的情況下,且1端的對象有n端對象在引用它(即存在n-1的映射關係),不能直接刪除1端的對象。
雙向1-n
雙向1-n與雙向n-1是完全相同的兩種情形。雙向1-n需要在1端可以訪問n端,反之亦然。
- 域模型:以
Order
和Customer
爲例,從Order
到Customer
的多對一雙向關聯需要在Order
類中定義一個Customer
屬性,而在Customer
類中需定義存放Order
對象的集合屬性。 - 關係數據模型:orders表中的customer_id參照customer表的主鍵。
我們在上述的Order
類和Customer
類中修改,因爲此時一端需要有一個Set集合來維護n端的Order
,因此在Customer
類中多出一個屬性也就是一個維護n端的集合。
對於這個集合要注意兩點:
- 對於這個
Set
集合類型,在聲明時,要使用接口類型,因爲hibernate在獲取集合類型時,返回的是hibernate內置的集合類型,而不是javase的標準集合實現。 - 爲了防止空指針異常,在聲明集合的時候就要把集合給初始化。
Customer
類如下:
package com.cerr.hibernate.n21.both;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private Integer customerId;
private String customerName;
//維護Order的集合
//需要把集合做初始化,防止出現空指針異常。
private Set<Order> orders = new HashSet <>();
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Set < Order > getOrders() {
return orders;
}
public void setOrders(Set < Order > orders) {
this.orders = orders;
}
}
而Order
類沒有改變,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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Order" table="orders" schema="hibernate5" dynamic-update="true">
<id name="orderId" column="order_id">
<generator class="native" />
</id>
<property name="orderName" column="order_name"/>
<!-- 映射關係 -->
<many-to-one name="customer" class="com.cerr.hibernate.n21.both.Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
對於映射文件新增的<set>
標籤的屬性:
-
table
:該值要和n-1中的n端的數據表名字一致。 -
<key>子標籤
:指定n的表中的外鍵列的名字 -
inverse
:指定由哪一方來維護關聯關係,通常在一端的映射文件中設置爲true
,表明由n端來維護關聯關係。 -
cascade
:設置級聯操作。 -
order-by
:在查詢時對集合中的元素進行排序,order-by
中使用的是表的字段名,而不是持久化類的屬性名。
在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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
<!-- 映射1對多的那個集合屬性 -->
<set name="orders" table="orders" cascade="delete">
<key column="customer_id"></key>
<one-to-many class="com.cerr.hibernate.n21.both.Order"/>
</set>
</class>
</hibernate-mapping>
保存操作的測試類:
package com.cerr.hibernate.n21.both;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test4(){
Customer customer = new Customer();
customer.setCustomerName("aa");
Order order = new Order();
order.setOrderName("1");
Order order1 = new Order();
order1.setOrderName("2");
//設定關聯關係
order.setCustomer(customer);
order1.setCustomer(customer);
customer.getOrders().add(order);
customer.getOrders().add(order1);
//先插入1的一端,再插入n的一端。三條insert語句和2條UPDATE。
session.save(customer);
session.save(order);
session.save(order1);
//如果先插入n的一端,再插入1的一端,則會多出來2條update語句。合計三條INSERT語句,4條update
// session.save(order);
// session.save(order1);
// session.save(customer);
}
}
在save()
時,發現如果先插入一端,再插入n端的話,會發送三條INSERT語句和兩條UPDATE語句,因爲此時一端和n端都維護關聯關係,所以會多出UPDATE語句。可以在1端的<set>
節點中指定inverse=true
來使一端放棄維護關聯關係。
補充後的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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
<!-- 映射1對多的那個集合屬性 -->
<set name="orders" table="orders" inverse="true">
<key column="customer_id"></key>
<one-to-many class="com.cerr.hibernate.n21.both.Order"/>
</set>
</class>
</hibernate-mapping>
對於查詢操作,查n端的情況如上述單向n-1的情況一樣。我們來看查一端的。
測試類:
@org.junit.Test
public void test6(){
//對n端的集合使用延遲加載
Customer customer = session.get(Customer.class,1);
System.out.println(customer.getCustomerName());
//集合返回的是Hibernate內置的集合類型
//該類型具有延遲加載和存放代理對象的功能。
System.out.println(customer.getOrders().getClass().getName());
}
查詢一端時,如果沒有用到一端中關聯n端的集合的話,它也只會去查一端的對應表,而不會查n端的對應的數據表,因此對n端也是使用延遲加載,再需要用到那個關聯n端的集合時纔會去查表。並且該集合返回的類型是Hibernate內置的集合類型,該類型具有延遲加載和存放代理對象的功能。在關閉session
後再去獲取集合時也會發生LazyInitializationException異常
。
在刪除時也要注意級聯操作,跟上面的單向n-1一樣。
映射一對一關聯關係
- 域模型
以部門Department
和經理Manager
爲例,假設一個部門只有一個經理,一個經理只能管一個部門,這樣就形成了一個一對一的關聯關係。 - 關係數據模型
有兩種。一種是按照外鍵映射,另一種是按照主鍵映射。
基於外鍵映射的一對一關聯關係
對於基於外鍵的1-1關聯,其外鍵可以存放在任意一邊,在需要存放外鍵的一端增加<many-to-one>
元素,爲<many-to-one>
元素添加一個unique=true
屬性來表示爲1-1關聯。
另一端需要使用<one-to-one>
元素,該元素使用property-ref
屬性指定使用被關聯實體主鍵以外的字段作爲關聯屬性。
- 沒有使用該屬性的SQL語句:
select
manager0_.mgr_id as mgr_id1_1_0_,
manager0_.mgr_name as mgr_name2_1_0_,
department1_.dept_id as dept_id1_0_1_,
department1_.dept_name as dept_nam2_0_1_,
department1_.mgr_id as mgr_id3_0_1_
from
manager manager0_
left outer join
//這裏錯了
departments department1_
on manager0_.mgr_id=department1_.dept_id
where
manager0_.mgr_id=?
- 使用該屬性後的SQL:
select
manager0_.mgr_id as mgr_id1_1_0_,
manager0_.mgr_name as mgr_name2_1_0_,
department1_.dept_id as dept_id1_0_1_,
department1_.dept_name as dept_nam2_0_1_,
department1_.mgr_id as mgr_id3_0_1_
from
manager manager0_
left outer join
//這裏沒錯
departments department1_
on manager0_.mgr_id=department1_.mgr_id
where
manager0_.mgr_id=?
對於上述的例子,我們首先新建Department
類和Manager
類,如下:
package com.cerr.hibernate.one2one.foreign;
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public Manager getMgr() {
return mgr;
}
public void setMgr(Manager mgr) {
this.mgr = mgr;
}
}
package com.cerr.hibernate.one2one.foreign;
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
public Integer getMgrId() {
return mgrId;
}
public void setMgrId(Integer mgrId) {
this.mgrId = mgrId;
}
public String getMgrName() {
return mgrName;
}
public void setMgrName(String mgrName) {
this.mgrName = mgrName;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
}
編寫映射文件
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.cerr.hibernate.one2one.foreign.Department" table="departments" schema="hibernate5" dynamic-update="true">
<id name="deptId" column="dept_id">
<generator class="native" />
</id>
<property name="deptName" column="dept_name"/>
<!-- 使用many-to-one的方式來映射1-1關聯關係-->
<many-to-one name="mgr" class="com.cerr.hibernate.one2one.foreign.Manager"
column="mgr_id" unique="true"></many-to-one>
</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>
<class name="com.cerr.hibernate.one2one.foreign.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.foreign.Department"
property-ref="mgr"></one-to-one>
</class>
</hibernate-mapping>
在hibernate.cfg.xml
中加入配置:
<mapping resource="com/cerr/hibernate/one2one/foreign/Manager.hbm.xml"/>
<mapping resource="com/cerr/hibernate/one2one/foreign/Department.hbm.xml"/>
測試類文件:
package com.cerr.hibernate.one2one.foreign;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test1(){
Department department = session.get(Department.class,1);
Manager manager = department.getMgr();
System.out.println(manager.getMgrName());
}
@org.junit.Test
public void test(){
Department department = new Department();
department.setDeptName("AA");
Manager manager = new Manager();
manager.setMgrName("aa");
//設定關聯關係
manager.setDept(department);
department.setMgr(manager);
//保存操作
session.save(manager);
session.save(department);
}
}
在查詢有外鍵的實體對象時,使用的依舊是懶加載。其關聯對象在需要使用時才初始化。
而在查詢沒有外鍵的實體對象時,發現使用的是左外連接查詢,一併查詢出其關聯的對象並進行初始化。
基於主鍵映射的一對一關聯關係
基於主鍵的映射策略指一端的生成器使用foreign
策略,表明根據對方的主鍵來生成自己的主鍵,自己並不能獨立生成主鍵。<param>
子元素指定使用當前持久化類的哪個屬性作爲對方。
constrained(約束):指定爲當前持久化類對應的數據庫表的主鍵添加一個外鍵約束,引用被關聯的對象(對方)所對應的數據庫表主鍵。
採用foreign
主鍵生成器策略的一端增加one to one
元素映射關聯屬性,其one to one
屬性還應增加constrained=true
屬性,另一端增加one to one
元素映射關聯屬性。
我們在上述的Demo中進行修改,首先因爲都是一對一的關聯關係,因此兩個實體類是不需要修改的,要修改的是映射文件,修改後的映射文件如下:
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.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
</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>
<class name="com.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
</class>
</hibernate-mapping>
映射單向多對多關聯關係
單向n-n的關聯必須使用連接表。
首先我們先建立兩個實體類:
package com.cerr.hibernate.n2n;
import java.util.HashSet;
import java.util.Set;
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet <>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set < Item > getItems() {
return items;
}
public void setItems(Set < Item > items) {
this.items = items;
}
}
package com.cerr.hibernate.n2n;
public class Item {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
對於映射文件:
與1-n映射類似,必須爲set集合元素添加key子元素,指定categories_items表中參照categories表的外鍵爲c_id
,與1-n關聯映射不同的是,建立n-n關聯時,集合中的元素使用<many-to-many>
,<many-to-many>
子元素的class
屬性指定items
集合中存放的是Item對象,<many-to-many>
子元素的column
屬性指定categories_items
表中參照items表的外鍵爲i_id
。
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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Category" table="categories" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<!-- table:指定中間表 -->
<set name="items" table="categories_items">
<key>
<column name="c_id"/>
</key>
<!-- 使用many-to-many指定多對多的關聯關係,column指定set集合中的持久化類在中間表的外鍵列的名稱-->
<many-to-many class="com.cerr.hibernate.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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
測試類:
package com.cerr.hibernate.n2n;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import java.util.Set;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test1(){
Category category = session.get(Category.class,3);
System.out.println(category.getName());
Set<Item> items = category.getItems();
System.out.println(items.size());
}
@org.junit.Test
public void test(){
Category category = new Category();
category.setName("AA");
Category category1 =new Category();
category1.setName("BB");
Item item = new Item();
Item item1 = new Item();
item.setName("aa");
item1.setName("bb");
//設定關聯關係
category.getItems().add(item);
category.getItems().add(item1);
category1.getItems().add(item);
category1.getItems().add(item1);
//執行保存操作
session.save(category);
session.save(category1);
session.save(item);
session.save(item1);
}
}
映射雙向多對多關聯關係
雙向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
的column
值爲a,many-to-many
的column
爲b;則另一邊的set
元素的key
的column
值爲b,many-to-many
的column
值爲a。對於雙向n-n關聯,必須把其中一端的
inverse
設置爲true
,否則兩端都維護關聯關係可能會造成主鍵衝突。
我們在上面實體類和映射文件的基礎上改,需要在Item
類中添加一個集合
package com.cerr.hibernate.n2n;
import java.util.HashSet;
import java.util.Set;
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet <>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set < Category > getCategories() {
return categories;
}
public void setCategories(Set < Category > categories) {
this.categories = categories;
}
}
再更改其映射文件Item.hbm.xml
,需要添加<set>
元素
<?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>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<!-- 指定中間表 -->
<set name="categories" table="categories_items" inverse="true">
<key column="i_id"></key>
<many-to-many class="com.cerr.hibernate.n2n.Category" column="c_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
測試類的方法修改爲如下:
@org.junit.Test
public void test(){
Category category = new Category();
category.setName("AA");
Category category1 =new Category();
category1.setName("BB");
Item item = new Item();
Item item1 = new Item();
item.setName("aa");
item1.setName("bb");
//設定關聯關係
category.getItems().add(item);
category.getItems().add(item1);
category1.getItems().add(item);
category1.getItems().add(item1);
item.getCategories().add(category);
item.getCategories().add(category1);
item1.getCategories().add(category);
item1.getCategories().add(category1);
//執行保存操作
session.save(category);
session.save(category1);
session.save(item);
session.save(item1);
}
映射繼承關係
對於面向對象的程序設計語言而言,繼承和多態是兩個最基本的概念,Hibernate的繼承映射可以理解持久化類之間的繼承關係。例如:人和學生之間的關係,學生繼承了人,可以認爲學生是一個特殊的人,如果對人進行查詢,學生的實例也將被得到。
採用subclass元素的繼承映射
- 採用
subclass
的繼承映射可以實現對於繼承關係中父類和子類使用同一張表 - 因爲父類和子類的實例全部保存在同一個表中,因此需要在該表內增加一列,使用該列來區分每行記錄到底是哪個類的實例,這個列被稱爲辨別者列。
- 在這種映射策略下,使用
subclass
來映射子類,使用class
或subclass
的discriminator-value
屬性指定辨別者列的值 - 所有子類定義的字段都不能有非空約束。如果爲那些字段添加非空約束,那麼父類的實例在那些列其實並沒有值,這將引起數據庫完整性衝突,導致父類的實例無法保存到數據庫中。
新建實體類Person
和Student
類,Student
類繼承自Person
類。
package com.cerr.hibernate.subclass;
public class Person {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.cerr.hibernate.subclass;
public class Student extends Person{
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
映射文件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.cerr.hibernate.subclass">
<!--加入select-before-update="true" -->
<class name="Person" table="persons" schema="hibernate5" discriminator-value="Person">
<id name="id" column="id">
<generator class="native" />
</id>
<!-- 配置辨別者列-->
<discriminator column="type" type="java.lang.String"></discriminator>
<property name="name" column="name"/>
<property name="age" column="age"/>
<!-- 映射子類Student,使用subclass進行映射-->
<subclass name="Student" discriminator-value="Student">
<property name="school" type="java.lang.String" column="school"></property>
</subclass>
</class>
</hibernate-mapping>
在Hibernate配置文件中添加:
<mapping resource="com/cerr/hibernate/subclass/Person.hbm.xml"/>
。
測試類:
package com.cerr.hibernate.subclass;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import java.util.List;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
/**
* 缺點:
* 1.使用了辨別者列
* 2.子類獨有的字段不能添加非空約束
* 3.若繼承層次很深,則數據表的字段也會較多。
*/
/**
* 查詢
* 1.查詢父類記錄,只需要查詢一張數據表
* 2.對於子類記錄,也只需要查詢一張數據表
*/
@org.junit.Test
public void testQuery(){
List<Person> personList = session.createQuery("FROM Person ").list();
System.out.println(personList.size());
List<Student> students = session.createQuery("FROM Student").list();
System.out.println(students.size());
}
/**
* 插入操作:
* 1.對於子類對象只需把記錄插入到一張數據表中
* 2.辨別者列由Hibernate自動維護。
*/
@org.junit.Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("aa");
session.save(person);
Student student = new Student();
student.setAge(12);
student.setSchool("aa");
student.setName("aaa");
session.save(student);
}
}
運行後新建了一個表,如下:
採用joined-subclass元素的繼承映射
採用
joined-subclass
元素的繼承映射可以實現每個子類一張表採用這種映射策略時,父類示例保存在父類表中,子類實例由父類表和子類表共同存儲。因爲子類實例也是一個特殊的父類實例,因此必然也包含了父類實例的屬性。於是將子類和父類共有的屬性保存在父類表中,子類增加的屬性則保存在子類表中。
在這種映射策略下,無需使用鑑別者列。但需要爲每個子類使用key元素映射共有主鍵。
子類增加的屬性可以添加非空約束,因爲子類的屬性和父類的屬性沒有保存在同一個表中。
我們還是用上述的Person
和Student
的例子,我們需要在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.cerr.hibernate.joined">
<class name="Person" table="persons" >
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<property name="age" column="age"/>
<!-- 使用joined-subclass -->
<joined-subclass name="Student" table="students">
<key column="student_id"></key>
<property name="school" type="java.lang.String" column="school"></property>
</joined-subclass>
</class>
</hibernate-mapping>
然後在配置文件中加上:
<mapping resource="com/cerr/hibernate/joined/Person.hbm.xml"/>
測試類還是使用上面的,運行後將會生成兩張表,一張父類表,一張子類獨有的屬性的表,子類的數據爲父類表的字段+子類表的字段。
對於插入操作,插入子類對象需要插兩張表。
對於查詢操作,查詢父類記錄時,做一個左外連接查詢;查詢子類記錄,做一個內連接查詢。
採用union-subclass元素的繼承映射
- 採用
union-subclass
元素可以實現將每一個實體對象映射到一個獨立的表中。 - 子類增加的屬性可以有非空約束,即父類實例的數據保存在父表中,而子類實例的數據保存在子類表中。
- 子類實例的數據僅保存在子類表中,而在父類表中沒有任何記錄。
- 在這種映射策略下,子類表的字段會比父類表的映射字段要多,因爲子類表的字段等於父類表的字段加上子類獨有屬性的總和。
- 在這種映射策略下,既不需要使用鑑別者列,也無序使用
key
元素來映射共有主鍵。 -
使用
union-subclass
映射策略是不可使用identity
的主鍵生成策略,因爲同一類繼承層次中所有實體類都需要使用同一個主鍵種子,即多個持久化實體對應的記錄的主鍵應該是連續的,受此影響,也不該使用native
主鍵生成策略,因爲native
會根據數據庫來選擇使用identity
或sequence
。
新建Person
類和Student
類
Person
類:
package com.cerr.hibernate.union.subclass;
public class Person {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Student
類:
package com.cerr.hibernate.union.subclass;
public class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
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.cerr.hibernate.union.subclass">
<class name="Person" table="PERSONS" >
<id name="id" column="ID" type="java.lang.Integer">
<generator class="increment" />
</id>
<property name="name" column="NAME"/>
<property name="age" column="AGE"/>
<union-subclass name="Student" table="STUDENTS">
<property name="school" column="SCHOOL" type="java.lang.String"/>
</union-subclass>
</class>
</hibernate-mapping>
測試類:
package com.cerr.hibernate.union.subclass;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
/**
* 優點:
* 1.不需要使用辨別者列
* 2.子類獨有的字段能添加非空約束
*
* 缺點:
* 1.子表中存在冗餘的字段
* 2.若更新父表的字段,則更新的效率較低。
*/
/**
* 查詢
* 1.查詢父類記錄,需把父表和子表記錄彙總到一起再做查詢,性能稍差。
* 2.對於子類記錄,也只需要查詢一張數據表
*/
@org.junit.Test
public void testQuery(){
List<Person> personList = session.createQuery("FROM Person ").list();
System.out.println(personList.size());
List<Student> students = session.createQuery("FROM Student").list();
System.out.println(students.size());
}
/**
* 插入操作:
* 1.對於子類對象只需把記錄插入到一張數據表中。
*/
@org.junit.Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("aa");
session.save(person);
Student student = new Student();
student.setAge(12);
student.setSchool("aa");
student.setName("aaa");
session.save(student);
}
}
生成的表如下: