hibernate5(9)註解對映[1]多對一單向關聯
在部落格網站中,我們可能需要從某一篇文章找到其所關聯的作者,這就需要從文章方建立起對使用者的關聯,即是多對一的對映關係。
現在先看一個配置例項:我們的文章實體類
package com.zeng.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.OneToMany;
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.zeng.test;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.AfterClass;
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.zeng.model.Article;
import com.zeng.model.User;
public class Test2 {
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("name1");
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 |