Hibernate學習筆記 | 詳解映射關係(一對一、一對多、多對多、繼承映射)

映射一對多關聯關係

  • 在領域模型中,類與類之間最普遍的關係就是關聯關係。
  • 在UML中,關聯是有方向的。
    CustomerOrder爲例:一個用戶能發出多個訂單,而一個訂單隻能屬於一個客戶,從OrderCustomer的關聯是多對一的關聯,而從CustomerOrder是一對多的關聯。

單向n-1

  • 單向n-1關聯只需從n端可以訪問1端。
  • 域模型:從OrderCustomer的多對一單向關聯需要在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端,反之亦然。

  • 域模型:以OrderCustomer爲例,從OrderCustomer的多對一雙向關聯需要在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關聯的兩邊都需指定連接表的表名及外鍵列的列名,兩個集合元素的settable元素的值必須指定,而且必須相同,該值是連接表的表名。set元素的兩個子元素:keymany-to-many都必須指定column屬性,其中keymany-to-many分別指定本持久化類和關聯類在連接表中的外鍵列名,因爲兩邊的keymany-to-manycolumn屬性交叉相同。
    例如一個邊的set元素的keycolumn值爲a,many-to-manycolumn爲b;則另一邊的set元素的keycolumn值爲b,many-to-manycolumn值爲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來映射子類,使用classsubclassdiscriminator-value屬性指定辨別者列的值
  • 所有子類定義的字段都不能有非空約束。如果爲那些字段添加非空約束,那麼父類的實例在那些列其實並沒有值,這將引起數據庫完整性衝突,導致父類的實例無法保存到數據庫中。

新建實體類PersonStudent類,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元素映射共有主鍵。

  • 子類增加的屬性可以添加非空約束,因爲子類的屬性和父類的屬性沒有保存在同一個表中。

我們還是用上述的PersonStudent的例子,我們需要在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會根據數據庫來選擇使用identitysequence

新建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);
    }
}

生成的表如下:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章