hibernate5(12)註解映射[4]一對一外鍵關聯
在實際博客站點中,文章內容的數據量非常多,它會影響我們檢索文章其他數據的時間,如查詢公布時間、標題、類別的等。
這個時候,我們能夠嘗試將文章內容存在還有一張表中,然後建立起文章——文章內容的一對一映射
一對一關聯有兩種方式,一種是外鍵關聯。還有一種是復合主鍵關聯。
外鍵關聯
以下我們先看一個一對一單向關聯的實例
/*************關聯關系維護方************/
@Table(name = "t_article")
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String title;
@OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval = true,targetEntity = ArticleContent.class)
@JoinColumn(name = "article_content_id")
private ArticleContent articleContent;
//忽略get和set方法
}
以下是我們的文章內容類
@Table(name = "t_article_content" )
@Entity
public class ArticleContent {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Lob
private String content;
//忽略get和set方法
}
以下是我們的測試類
public class Test3 {
private ApplicationContext ac;
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@BeforeClass//在測試類初始化時調用此方法,完畢靜態對象的初始化
public static void before(){
}
@Before//每個被註解Test方法在調用前都會調用此方法一次
public void setup(){//建立針對我們當前測試方法的的會話和事務
ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
sessionFactory = (SessionFactory) ac.getBean("sessionFactory");
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
//測試級聯關系映射註解配置:一對一單向關聯
@Test
public void test1(){
//測試級聯加入
Article article = new Article();
article.setTitle("title");
ArticleContent articleContent = new ArticleContent();
articleContent.setContent("content");
article.setArticleContent(articleContent);//建立映射關系
session.save(articleContent);
session.save(article);
//測試級聯刪除
// Article article = (Article) session.get(Article.class,1);
// session.delete(article);
@After//每個被註解Test方法在調用後都會調用此方法一次
public void teardown(){
if(transaction.isActive()){//假設當前事務尚未提交,則
transaction.commit();//提交事務,主要為了防止在測試中已提交事務,這裏又反復提交
}
session.close();
}
調用我們的測試方法test1。
控制臺打印:
Hibernate: insert into t_article_content (content) values (?)
Hibernate: insert into t_article (article_content_id, title) values (?, ?)
此時查看數據庫:
mysql> show tables; ————————————hibernate幫我們新建的表格
+———————+
| Tables_in_hibernate |
+———————+
| t_article |
| t_article_content |
+———————+
2 rows in set (0.00 sec)mysql> desc t_article; ————————————單方維護映射關系,通過article_content_id維護
+——————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| article_content_id | int(11) | YES | MUL | NULL | |
+——————–+————–+——+—–+———+—————-+
3 rows in set (0.00 sec)mysql> desc t_article_content;
+———+———-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———+———-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content | longtext | YES | | NULL | |
+———+———-+——+—–+———+—————-+
2 rows in set (0.00 sec)mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 1 | title | 1 |
+—-+——-+——————–+
1 row in set (0.00 sec)mysql> select * from t_article_content;
+—-+———+
| id | content |
+—-+———+
| 1 | content |
+—-+———+
1 row in set (0.00 sec)
凝視掉測試代碼的級聯加入部分,執行級聯刪除部分:
Hibernate: delete from t_article where id=?
Hibernate: delete from t_article_content where id=?
在這裏,我們觀察到它是先刪除文章(維護關系方)。再刪除t_article_content的,回憶我們之前的一對多關聯測試。都是先刪除維護關系方的。這事實上非常好理解,我們肯定要清除掉相應的關聯關系(體如今數據庫的外鍵上)才幹完畢被關聯內容的刪除操作
一對一雙向關聯非常easy,直接在articleContent上加入:
@OneToOne(cascade = CascadeType.ALL,mapperBy = "articleContent")
private Article article;
//忽略getter/setter
使用和上面一樣的測試代碼。hibernate會幫我們生成表格並插入數據:
mysql> select * from t_article_content;
+—-+———+
| id | content |
+—-+———+
| 1 | content |
+—-+———+
1 row in set (0.00 sec)mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 1 | title | 1 |
+—-+——-+——————–+
1 row in set (0.00 sec)
這時候假設我們嘗試在放棄維護的articleContent端進行級聯加入:
//測試articleContent級聯加入
Article article = new Article();
article.setTitle("title");
ArticleContent articleContent = new ArticleContent();
articleContent.setContent("content");
articleContent.setArticle(article);
session.save(articleContent);
我們的article對象能被成功保存。可是。兩者的關聯關系建立失敗:
mysql> select * from t_article_content;
+—-+———+
| id | content |
+—-+———+
| 1 | content |
| 2 | content |
+—-+———+
2 rows in set (0.00 sec)mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 1 | title | 1 |
| 2 | title | NULL |
+—-+——-+——————–+
2 rows in set (0.00 sec)
這時候我們再嘗試從放棄維護端刪除:
//這次刪除是有級聯關系的
ArticleContent articleContent = (ArticleContent) session.get(ArticleContent.class, 1);//註意這裏id為1
session.delete(articleContent);
mysql> select * from t_article_content;
+—-+———+
| id | content |
+—-+———+
| 5 | content |
+—-+———+
1 row in set (0.00 sec)mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 6 | title | NULL |
+—-+——-+——————–+
1 row in set (0.00 sec)
會看到我們相應article對象也被刪除了!因此,我們須要明白放棄維護關聯關系並不代表放棄關聯關系,從ArticleContent端,我們一樣能進行與關聯關系雙管的級聯加入、刪除操作。僅僅是不正確兩者關系進行維護。因而在加入時Article端的外鍵屬性article_content_id=null
我們使用mappedBy屬性放棄關聯。但級聯操作依舊有效,因此須要區分開維護關聯關系和級聯操作的差別。
這裏須要特別註意的是。在這樣的一對一映射中。我們最好選擇一個被動方並設定mapperBy屬性。即讓一方放棄維護關聯關系,否則,我們會看到下述現象:
mysql> desc t_article;
+——————–+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——————–+————–+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| article_content_id | int(11) | YES | MUL | NULL | |
+——————–+————–+——+—–+———+—————-+
3 rows in set (0.00 sec)mysql> desc t_article_content;
+————+———-+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————+———-+——+—–+———+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content | longtext | YES | | NULL | |
| article_id | int(11) | YES | MUL | NULL | |
+————+———-+——+—–+———+—————-+
3 rows in set (0.00 sec)
兩個表中都建立了關於對方的關聯映射。
這是全然沒有必要的,並且這樣會造成的更嚴重後果,我們來測試級聯加入
先調用例如以下測試代碼:
//測試article級聯加入
Article article = new Article();
article.setTitle("title");
ArticleContent articleContent = new ArticleContent();
articleContent.setContent("content");
article.setArticleContent(articleContent);
session.save(article);
再調用例如以下測試代碼:
//測試articleContent級聯加入
Article article = new Article();
article.setTitle("title");
ArticleContent articleContent = new ArticleContent();
articleContent.setContent("content");
articleContent.setArticle(article);
session.save(articleContent);
我們會看到數據庫相應記錄:
mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 1 | title | 1 |
| 2 | title | NULL |
+—-+——-+——————–+
2 rows in set (0.00 sec)mysql> select * from t_article_content;
+—-+———+————+
| id | content | article_id |
+—-+———+————+
| 1 | content | NULL |
| 2 | content | 2 |
+—-+———+————+
2 rows in set (0.00 sec)
即兩方各維護各的關聯關系。假設這時候我們嘗試交換測試級聯刪除:
Article article = (Article) session.get(Article.class,2);
session.delete(article);
會看到例如以下結果:
mysql> select * from t_article;
+—-+——-+——————–+
| id | title | article_content_id |
+—-+——-+——————–+
| 1 | title | 1 |
+—-+——-+——————–+
1 row in set (0.00 sec)mysql> select * from t_article_content;
+—-+———+————+
| id | content | article_id |
+—-+———+————+
| 1 | content | NULL |
| 2 | content | 2 |
+—-+———+————+
2 rows in set (0.00 sec)
即級聯刪除失敗了,而這是顯然的。由於id為2的文章,相應article_content_id屬性為null,在文章方看來,兩者都沒建立關聯關系。這樣的時候肯定不是報錯就是級聯刪除失敗,而報錯是由於假設設置了數據庫在t_article_content中設置了對article_id的的外鍵關聯,由於存在記錄article_id=2,這時候我們嘗試刪除article表中id為2的記錄,則會由於外鍵關系約束失敗而報錯
hibernate5(12)註解映射[4]一對一外鍵關聯