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>
若是對hibernate的配置還不是很清楚,能夠看看這裏數據庫
Hibernate中,能夠直接將表的關係用對象表示。緩存
如本例中,一個博客只能有一個做者,因此Blog就能夠添加一個User對象。session
一個用戶有多個博客,因此能夠在User中添加一個Blog的Set集合。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; } }
實體類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; } }
多對一時,使用<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(); } }
到這裏,基本的配置都設置完了,接下來測試配置的怎麼樣
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:
<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(); }
發現能夠正確執行,由於保存user時,會自動級聯保存兩個blog,因此他們就全是持久態。
<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上面已經簡單介紹過,就是解除關係時會把節點刪除而不僅是刪除外鍵。
咱們把使用和不使用孤兒刪除分別用代碼實現,並作一次比較:
原來的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:
再看看錶狀況:
正常狀況,解除關係只是刪除外鍵。
爲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的執行狀況
數據表變化:
什麼是外鍵維護呢?
就是在兩個關聯對象中,若是關係發生改變須要修改外鍵。這麼一說感受這個功能確定是必備的,要否則這麼保證對象之間的關係呢?
在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。
可是按照人的思惟定式,在不知道的狀況仍是會按上面兩個步驟走,感受更合理。
因此解決方法就在一方放棄外鍵維護。而且在多對多的狀況下必須有一方須要放棄外鍵,否者程序沒法運行。