Hibernate級聯之一對多和inverse解析

hibernate的級聯能夠說是hibernate最重要的部分,只有深刻了解了級聯的特性與用法,才能運用自如。html

  此次討論一對多的狀況,因此就使用博客項目的用戶表和博客表做爲示例,來一塊兒學習hibernate的級聯java

基本準備

文件結構:mysql

hibernate核心配置文件hibernate.cfg.xml:sql

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
<hibernate-configuration>
    <!-- 先配置SessionFactory標籤,一個數據庫對應一個SessionFactory標籤 -->
    <session-factory>
        <!-- 必須的配置的參數5個,4個鏈接參數,1個數據庫方言 -->
        <!--
        #hibernate.connection.driver_class com.mysql.jdbc.Driver
        #hibernate.connection.url jdbc:mysql:///test
        #hibernate.connection.username gavin
        #hibernate.connection.password 
        數據庫方言
        #hibernate.dialect org.hibernate.dialect.MySQLDialect
         -->
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql:///blog</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
         <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <!-- 可選配置 -->
        <!-- 顯示sql語句 -->
        <property name="hibernate.show_sql">true</property>
        <!-- 格式化sql語句 -->
        <property name="hibernate.format_sql">false</property>
        <!-- 生成數據庫的表結構 
        (hbm2dd全稱hibernate mapping to db define language auto create)
        update 沒表會自動建立,有表添加數據。
            若是開發中間須要添加字段,能夠在實體類添加屬性。update會自動在數據庫添加字段,而且不改變原來數據庫值
        validate 校驗實體屬性和數據庫是否一致
        -->
        <property name="hibernate.hbm2ddl.auto">update</property>
        
        <!-- 映射配置文件,能夠在map配置文件右鍵copy qualified name-->
        <mapping resource="com/cky/domain/User.hbm.xml"/>
        <mapping resource="com/cky/domain/Blog.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
View Code

若是對hibernate的配置還不是很清楚,能夠看看這裏數據庫

實體類的建立

  Hibernate中,能夠直接將表的關係用對象表示。緩存

  如本例中,一個博客只能有一個做者,因此Blog就能夠添加一個User對象。session

  一個用戶有多個博客,因此能夠在User中添加一個BlogSet集合。app

  這裏須要注意的是若是關聯的是一個對象,那麼不能在類中進行初始化new操做。框架

  若是關聯的是一個集合,那麼必須用HashSet在類中進行初始化new操做dom

實體類Blog.java

package com.cky.domain;

import java.sql.Timestamp;

public class Blog {
    private int bId;
    private String bSubject;
    private String bContent;
    private Timestamp createtime;
    private Timestamp updatetime;
    //hibernate中關聯對象不能初始化
    private User user;
    
    //...getter setter 方法省略
    public int getbId() {
        return bId;
    }
    public void setbId(int bId) {
        this.bId = bId;
    }
    public String getbSubject() {
        return bSubject;
    }
    public void setbSubject(String bSubject) {
        this.bSubject = bSubject;
    }
    public String getbContent() {
        return bContent;
    }
    public void setbContent(String bContent) {
        this.bContent = bContent;
    }
    public Timestamp getCreatetime() {
        return createtime;
    }
    public void setCreatetime(Timestamp createtime) {
        this.createtime = createtime;
    }
    public Timestamp getUpdatetime() {
        return updatetime;
    }
    public void setUpdatetime(Timestamp updatetime) {
        this.updatetime = updatetime;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    
    
}
View Code

實體類User.java

package com.cky.domain;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

public class User {
    private Integer uId;
    private String uEmail;
    private String uName;
    private String uUsername;
    private String uPassword;
    private String uAge;
    private String uDetail;
    private String uAvatar;
    private String isAdmin;
    private Timestamp createtime;
    private Timestamp updatetime;
    //hibernate的集合必須初始化
    private Set<Blog> blogs=new HashSet<Blog>();
    
    //...getter setter 方法省略
    public Integer getuId() {
        return uId;
    }
    public void setuId(Integer uId) {
        this.uId = uId;
    }
    public String getuEmail() {
        return uEmail;
    }
    public void setuEmail(String uEmail) {
        this.uEmail = uEmail;
    }
    public String getuName() {
        return uName;
    }
    public void setuName(String uName) {
        this.uName = uName;
    }
    public String getuUsername() {
        return uUsername;
    }
    public void setuUsername(String uUsername) {
        this.uUsername = uUsername;
    }
    public String getuPassword() {
        return uPassword;
    }
    public void setuPassword(String uPassword) {
        this.uPassword = uPassword;
    }
    public String getuAge() {
        return uAge;
    }
    public void setuAge(String uAge) {
        this.uAge = uAge;
    }
    public String getuDetail() {
        return uDetail;
    }
    public void setuDetail(String uDetail) {
        this.uDetail = uDetail;
    }
    public String getuAvatar() {
        return uAvatar;
    }
    public void setuAvatar(String uAvatar) {
        this.uAvatar = uAvatar;
    }
    public String getIsAdmin() {
        return isAdmin;
    }
    public void setIsAdmin(String isAdmin) {
        this.isAdmin = isAdmin;
    }
    public Timestamp getCreatetime() {
        return createtime;
    }
    public void setCreatetime(Timestamp createtime) {
        this.createtime = createtime;
    }
    public Timestamp getUpdatetime() {
        return updatetime;
    }
    public void setUpdatetime(Timestamp updatetime) {
        this.updatetime = updatetime;
    }
    public Set<Blog> getBlogs() {
        return blogs;
    }
    public void setBlogs(Set<Blog> blogs) {
        this.blogs = blogs;
    }

    
}
View Code

編寫基礎映射文件

  多對一狀況映射文件的編寫

  多對一時,使用<many-to-one>標籤,只須要指定三個屬性:

  name:指定此標籤所映射的屬性名

  class:關聯的表所對應的實體類的全限定類名

  column:關聯表的外鍵名

  Blog.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.cky.domain.Blog"  table="blog">
        <id name="bId" column="b_id">
            <generator class="native"></generator>
        </id>
        <!-- 普通屬性 -->
        <property name="bSubject" column="b_subject"></property>
        <property name="bContent" column="b_content"></property>
        <property name="createtime" column="createtime"></property>
        <property name="updatetime" column="updatetime"></property>
        <!-- 
        private User user; 
        多對一 配置-->
        <many-to-one name="user" class="com.cky.domain.User" column="u_id" ></many-to-one>
    </class>
</hibernate-mapping>

  一對多狀況映射文件的編寫

  與多對一狀況不一樣的是,一對多時關聯對象是一個set集合。

  配置文件須要使用<set>標籤來和集合對象創建聯繫,其中的name指定對應的屬性名

  在<set>中,須要指定查詢關聯對象所須要的表(實體類)和比較字段(外鍵)

  User.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.cky.domain.User" table="user">
<!-- 配置id
    name實體類屬性,column表字段,若是同樣,column能夠省略。-->
    <id name="uId" column="u_id">
    <!-- 主鍵生成策略 -->
        <generator class="native"></generator>
    </id>
    <!-- 普通屬性-->
    <property name="uEmail" column="u_email"></property>
    <property name="uName" column="u_name"></property>
    <property name="uUsername" column="u_username"></property>
    <property name="uPassword" column="u_password"></property>
    <property name="uAge" column="u_age"></property>
    <property name="uDetail" column="u_detail"></property>
    <property name="uAvatar" column="u_avatar"></property>
    <property name="isAdmin" column="is_admin"></property>
    <property name="createtime" column="createtime"></property>
    <property name="updatetime" column="updatetime"></property>
    <!-- private Set<Blog> blogs=new HashSet<Blog>();
        集合的配置
        name:這個類中對應的屬性名
     -->
     <set name="blogs">
         <!--column: 外鍵,hibernate會根據這個字段來查詢與這個對象對應的多端的全部對象 -->
         <key column="u_id"></key>
         <!--class:集合表明的實體類,同時也表明要查詢的表。
                 與上面的條件結合,就能夠查詢出表中全部外鍵字段爲指定值的全部結果的集合。
             -->
         <one-to-many class="com.cky.domain.Blog"/>
     </set>
</class>
</hibernate-mapping>

 爲了方便使用,還須要一個工具類HibernateUtils.java,很簡單就不介紹了,下面是代碼:

package com.cky.utils;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtils {
    //ctrl+shift+x
    private static final Configuration CONFIG;
    private static final SessionFactory FACTORY;
    
    //編寫靜態代碼塊
    static {
        //加載XML的配置文件
        CONFIG =new Configuration().configure();
        //構造工做
        FACTORY=CONFIG.buildSessionFactory();
    }
    /**
     * 從工廠獲取session對象
     */
    public static Session getSession() {
        return FACTORY.openSession();
    }
}
View Code

測試基礎配置(不使用級聯)

到這裏,基本的配置都設置完了,接下來測試配置的怎麼樣

package com.cky.Demo;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.cky.domain.Blog;
import com.cky.domain.User;
import com.cky.utils.HibernateUtils;

public class CascadeTest {
    @Test
    public void testMTO2() {
        Session session = HibernateUtils.getSession();
        Transaction tr = session.beginTransaction();
        //保存用戶和博客
        User user=new User();
        user.setuName("王五");
        
        Blog blog1=new Blog();
        blog1.setbSubject("王五平常一");
        blog1.setbContent("看電視");
        
        Blog blog2=new Blog();
        blog2.setbSubject("王五平常二");
        blog2.setbContent("玩遊戲");
        //爲用戶添加博客
        user.getBlogs().add(blog1);
        user.getBlogs().add(blog2);
        //保存用戶
        session.save(user);
        
        tr.commit();
        session.close();
    }       
}

什麼,竟然報錯了:TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing

翻譯一下,大體意思就是user對象引用了一個瞬時對象,由於當save(user)時,user已經被保存到緩存成爲持久態對象,而給他添加的blog1和blog2,由於沒有設置級聯,因此不會被自動添加到緩存中,依然是瞬時態對象。

解決方法就是把兩個blog1和blog2也進行save(),保存到session中:

@Test
    public void testMTO2() {
       //.....上面省略
        //保存用戶
        session.save(user);
        session.save(blog1); session.save(blog2);
        tr.commit();
        session.close();
    }       

 關於級聯

  hibernate中用cascade屬性設置級聯。

  在基礎的配置中,由於沒有設置級聯,默認是none,也就是不進行級聯操做。

  就如上面的代碼同樣,咱們須要手動的保證對象和他級聯的對象都在同一狀態,才能正確運行,這顯然是很麻煩的,下面就看看如何經過設置級聯屬性來讓代碼更簡單。

  cascade取值共有5個:

    none     默認值,不級聯

    save-update  在保存、更新操做時自動級聯保存更新關聯對象

    delete    在刪除時自動級聯刪除關聯對象

    all      相似save-update-delete,即因此的操做都會級聯

    all-delete-orphan 解除某一節點的關係時刪除該節點(默認只是清除外鍵關係)

  接下來就在上面的基礎配置上添加上面的屬性看看有什麼區別:

  save-update:

  爲user配置文件的添加cascade屬性

<set name="blogs" cascade="save-update">
    <key column="u_id"></key>
    <one-to-many class="com.cky.domain.Blog" />
</set>

此時咱們運行上次報錯的那段代碼:

@Test
    public void testMTO() {
        Session session = HibernateUtils.getSession();
        Transaction tr = session.beginTransaction();
        //保存用戶和博客
        User user=new User();
        user.setuName("王五");
        
        Blog blog1=new Blog();
        blog1.setbSubject("王五平常一");
        blog1.setbContent("看電視");
        
        Blog blog2=new Blog();
        blog2.setbSubject("王五平常二");
        blog2.setbContent("玩遊戲");
        
        user.getBlogs().add(blog1);
        user.getBlogs().add(blog2);
        
        blog1.setUser(user);
        blog2.setUser(user);
        //自動關聯
        session.save(user);
                //刪除掉保存blog的代碼
        
        tr.commit();
        session.close();
    }
View Code

發現能夠正確執行,由於保存user時,會自動級聯保存兩個blog,因此他們就全是持久態。

  咱們同時爲blog配置文件添加cascade屬性

<many-to-one name="user" 
  class
="com.cky.domain.User"
  column
="u_id"
  
cascade="save-update"></many-to-one>

而後保存一個blog看看會發生什麼

@Test
    public void testMTO() {
        Session session = HibernateUtils.getSession();
        Transaction tr = session.beginTransaction();

        User user=new User();
        user.setuName("王五");
        
        Blog blog1=new Blog();
        blog1.setbSubject("王五平常一");
        blog1.setbContent("看電視");
        
        Blog blog2=new Blog();
        blog2.setbSubject("王五平常二");
        blog2.setbContent("玩遊戲");

        user.getBlogs().add(blog1);
        user.getBlogs().add(blog2);
        
        blog1.setUser(user);
        blog2.setUser(user);
        
        /*session.save(user);
        session.save(blog1);*/
        //只保存blog2
        session.save(blog2);
        
        tr.commit();
        session.close();
    }

運行成功,不過更有意思的是他保存了三條信息,而不是兩條。

由於當保存 blog2 時,會級聯保存 user ,而user又會級聯把 blog1 保存

刪除也是一樣的道理,就不演示了,下面再研究一個all-delete-orphan,傳說的孤兒刪除

  關於all-delete-orphan

all-delete-orphan上面已經簡單介紹過,就是解除關係時會把節點刪除而不僅是刪除外鍵。

咱們把使用和不使用孤兒刪除分別用代碼實現,並作一次比較:

正常狀況下的解除關係:

  原來的blog表中兩條數據都和user id=1產生關係

如今咱們把user和其中一個blog id=1解除關係

//普通解除關係
    @Test
    public void testMTO4() {
        Session session = HibernateUtils.getSession();
        Transaction tr = session.beginTransaction();
        
        User user=(User) session.get(User.class, 1);
        Blog blog=(Blog) session.get(Blog.class, 1);
        //解除關係只須要把user集合中的blog移除便可
        user.getBlogs().remove(blog);
        
        tr.commit();
        session.close();
    }

運行sql:

再看看錶狀況:

正常狀況,解除關係只是刪除外鍵。

使用all-delete-orphan時解除關係:

爲user配置文件添加all-delete-orphan

 <set name="blogs" cascade="all-delete-orphan">
     <key column="u_id"></key>
     <one-to-many class="com.cky.domain.Blog" />
</set>

執行一樣的代碼解除關係:

//孤兒刪除
    @Test
    public void testMTO4() {
        Session session = HibernateUtils.getSession();
        Transaction tr = session.beginTransaction();
        
        User user=(User) session.get(User.class, 1);
        Blog blog=(Blog) session.get(Blog.class, 1);
        //解除關係只須要把user集合中的blog移除便可
        user.getBlogs().remove(blog);
        
        tr.commit();
        session.close();
    }

sql的執行狀況

數據表變化:

關於inverse(外鍵維護)

 什麼是外鍵維護呢?

  就是在兩個關聯對象中,若是關係發生改變須要修改外鍵。這麼一說感受這個功能確定是必備的,要否則這麼保證對象之間的關係呢?

  在hibernate是根據對象關係來判斷是否要維護外鍵

  這裏有兩個關鍵字,對象關係外鍵

  什麼是對象關係?在hibernate中就是你這個對象A存的有對象B的引用,那麼對象A就有對象B的的對象關係。有趣的是,對象關係能夠是單向的,即A有B的對象關係,B不必定有A的對象關係。Hibernate是根據對象的對象關係來進行外鍵處理的。若是兩邊的對象關係都改變,那麼默認hibernate都會進行外鍵處理(處理兩次)。

  舉個例子

  user1有blog1和blog2倆對象關係  、user2有blog3和blog4倆對象關係

  1.如今咱們把blog3添加到user1中(對象關係改變)

  2.由於這時blog3中的user仍是user2,還要把blog3的user換成user1(對象關係改變)

  上面兩個操做都改變了對象關係,如以前說的,session的緩存和快照不一致了,對於User對象,須要更新外鍵,對於Blog對象,也須要更新外鍵。

  可是,他們更新的是同一外鍵,也就是說對同一外鍵更新了兩次,多了一個無心義的操做無疑增長了數據庫的壓力。

  

  也許有人可能會說,我不執行步驟2不就好了,結果仍是正確的,還減小了sql。

  可是按照人的思惟定式,在不知道的狀況仍是會按上面兩個步驟走,感受更合理。

  因此解決方法就在一方放棄外鍵維護。而且在多對多的狀況下必須有一方須要放棄外鍵,否者程序沒法運行。

相關文章
相關標籤/搜索