在博客網站中,我們可能需要從某一篇文章找到其所關聯的作者,這就需要從文章方建立起對用戶的關聯,即是多對一的映射關係。
現在先看一個配置實例:我們的文章實體類
package com.chenhao.hibernate.model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Table(name = "t_article")
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Lob //數據可能會非常長,映射爲數據庫支持的“大對象”
private String content;
/**
* @ManyToOne 使用此標籤建立多對一關聯,此屬性在“多”方使用註解在我們的“一”方屬性上
* @cascade 指定級聯操作,以數組方式指定,如果只有一個,可以省略“{}”
* @fetch 定義抓取策略
* @optional 定義是否爲必需屬性,如果爲必需(false),但在持久化時user = null,則會持久化失敗
* @targetEntity 目標關聯對象,默認爲被註解屬性所在類
*/
@ManyToOne(cascade ={CascadeType.ALL},fetch = FetchType.LAZY,optional = false,targetEntity = User.class)
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
因爲這裏是單向關聯,所以我們無須在在User類中建立對文章的關聯屬性
接下來編寫我們的測試類
package com.chenhao.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.chenhao.hibernate.model.Article;
import com.chenhao.hibernate.model.User;
public class ArticleTest {
private static ApplicationContext ac;
private static SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@BeforeClass//在測試類初始化時調用此方法,完成靜態對象的初始化
public static void before(){
ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
sessionFactory = (SessionFactory) ac.getBean("sessionFactory");
}
@Before//每一個被註解Test方法在調用前都會調用此方法一次
public void setup(){//建立針對我們當前測試方法的的會話和事務
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
//測試級聯關係映射註解配置:多對一單向關聯
@Test
public void test1(){
User user = new User();
user.setName("name3");
Article article = new Article();
article.setContent("content1");
article.setUser(user);//建立級聯關係
session.save(article);//注意這裏我們沒有保存我們的user對象
}
@After//每一個被註解Test方法在調用後都會調用此方法一次
public void teardown(){
transaction.commit();
session.clear();
session.close();
}
@After//在類銷燬時調用一次
public void after(){
//sessionFactory.close();
}
}
調用上面測試方法,我們會發現,hibernate幫我們在上篇文章已建立User類的基礎上,又幫我們創建了t_article數據表:
MySQL> desc t_article;
+———+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content | varchar(255) | YES | | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
+———+————–+——+—–+———+—————-+
3 rows in set (0.00 sec)
然後我們查看用戶表和文章表,會看到:
mysql> select * from t_user;
+—-+——-+
| id | name |
+—-+——-+
| 1 | name1 |
+—-+——-+
1 row in set (0.00 sec)
mysql> select * from t_article;
+—-+———-+———+
| id | content | user_id |
+—-+———-+———+
| 1 | content1 | 1 |
+—-+———-+———+
1 row in set (0.00 sec)
可以看到,這裏我們的user_id和user表的新建記錄id是對應的。
看完實例,下面我們針對配置的屬性進行具體分析:
1. cascade屬性
屬性 | 說明 |
---|---|
CascadeType.MERGE | 級聯更新:若user屬性修改了那麼article對象保存/更新時同時修改user在數據庫裏的屬性值 |
CascadeType.PERSIST | 級聯保存:對article對象保存時也對user裏的對象也會保存。 |
CascadeType.REFRESH | 級聯刷新:獲取article對象裏也同時也重新獲取最新的user時的對象。即會重新查詢數據庫裏的最新數據 |
CascadeType.REMOVE | 級聯刪除:對article對象刪除也會使對應user的象刪除 |
CascadeType.ALL | 包含PERSIST, MERGE, REMOVE, REFRESH, DETACH等; |
級聯屬性對於一方和多方的作用效果是不一樣的。經測試發現,在多對一中,多方使用CascadeType.PERSIST無法級聯保存對象,必須使用CascadeType.ALL。而級聯刪除既可使用CascadeType.REMOVE也可使用CascadeType.ALL
對於上述方法,如果我們沒有設置級聯保存,在我們保存文章對象時,用戶對象自然不會持久化到數據庫,這時候會報錯:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.zeng.model.User
在我們提交事務的時候,hibernate總會flush(清理)我們的session緩存,所謂清理,是指hibernate按照持久化對象的屬性變化來同步更新數據庫,當發現我們的article對象引用了臨時對象user,而article.user.id = null,會判斷user對象是瞬時的Transient,這個我們要持久化到數據庫中的article對象發生衝突,因此會保存失敗。這裏我們也意在說明,關係直接的級聯映射是通過用戶對象標識符id來確認的。意思是說,即使article.user.其它屬性全爲null,但只要article.user.id在數據庫中有相關記錄(saved)這時就能建立兩者的級聯關係了。
另一方面,如果我們習慣了xxx.htm.xml的方式來配置我們的實體映射關係,那我們必然對hibernate的級聯屬性更加熟悉,這時我們可以通過
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})來使用hibernate內置級聯屬性。關於hibernate的內置級聯屬性常見有:
屬性名 | 說明 |
---|---|
save-update | 級聯保存(load以後如果子對象發生了更新,也會級聯更新)。 但它不會級聯刪除 |
delete | 級聯刪除, 但不具備級聯保存和更新 |
all-delete-orphan | 在解除父子關係時,自動刪除不屬於父對象的子對象, 也支持級聯刪除和級聯保存更新。 |
all | 級聯刪除, 級聯更新,但解除父子關係時不會自動刪除子對象。 |
delete-orphan | 刪除所有和當前對象解除關聯關係的對象 |
2. @JoinColumn
它的具體值可參照下表
屬性 | 默認值 | 說明 |
---|---|---|
columnDefinition | 空 | JPA 使用最少量 SQL 創建一個數據庫表列。如果需要使用更多指定選項創建列,將 columnDefinition 設置爲在針對列生成 DDL 時希望 JPA 使用的 String SQL 片斷。 |
insertable | true | 默認情況下,JPA 持續性提供程序假設它可以插入到所有表列中。如果該列爲只讀,請將 insertable 設置爲 false。 |
name | 默認值 | 如果使用一個連接列,則 JPA 持續性提供程序假設外鍵列的名稱是以下名稱的連接: 1. 引用關係屬性的名稱 +“”+ 被引用的主鍵列的名稱。 2. 引用實體的字段名稱 +“”+ 被引用的主鍵列的名稱。 3. 如果實體中沒有這樣的引用關係屬性或字段(請參閱 @JoinTable),則連接列名稱格式化爲以下名稱的連接:實體名稱 +“_”+ 被引用的主鍵列的名稱。這是外鍵列的名稱。如果連接針對“一對一”或“多對一”實體關係,則該列位於源實體的表中。如果連接針對“多對多”實體關係,則該列位於連接表(請參閱 @JoinTable)中。 4. 如果連接列名難於處理、是一個保留字、與預先存在的數據模型不兼容或作爲數據庫中的列名無效,請將 name 設置爲所需的 String 列名。 |
nullable | true | 默認情況下,JPA 持續性提供程序假設允許所有列包含空值。如果不允許該列包含空值,請將nullable 設置爲 false。 |
referencedColumnName | 無 | 如果使用一個連接列,則 JPA 持續性提供程序假設在實體關係中,被引用的列名是被引用的主鍵列的名稱。如果在連接表(請參閱 @JoinTable)中使用,則被引用的鍵列位於擁有實體(如果連接是反向連接定義的一部分,則爲反向實體)的實體表中。要指定其他列名,請將 referencedColumnName 設置爲所需的 String 列名。 |
table | 無 | JPA 持續性提供程序假設實體的所有持久字段存儲到一個名稱爲實體類名稱的數據庫表中(請參閱 @Table)。如果該列與輔助表關聯(請參閱 @SecondaryTable),請將 name 設置爲相應輔助表名稱的 String 名稱 |
unique | false | 默認情況下,JPA 持續性提供程序假設允許所有列包含重複值。如果不允許該列包含重複值,請將 unique 設置爲 true。 |
updatable | true | 默認情況下,JPA 持續性提供程序假設它可以更新所有表列。如果該列爲只讀,則將 updatable 設置爲 false |