1. 程式人生 > >hibernate5(12)註解映射[4]一對一外鍵關聯

hibernate5(12)註解映射[4]一對一外鍵關聯

rom 成功 查詢 content cat 回憶 target his var

在實際博客站點中,文章內容的數據量非常多,它會影響我們檢索文章其他數據的時間,如查詢公布時間、標題、類別的等。

這個時候,我們能夠嘗試將文章內容存在還有一張表中,然後建立起文章——文章內容的一對一映射

一對一關聯有兩種方式,一種是外鍵關聯。還有一種是復合主鍵關聯。

外鍵關聯

以下我們先看一個一對一單向關聯的實例

/*************關聯關系維護方************/
@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]一對一外鍵關聯