在實際博客網站中,文章內容的數據量非常多,它會影響我們檢索文章其它數據的時間,如查詢發佈時間、標題、類別的等。這個時候,我們可以嘗試將文章內容存在另一張表中,然後建立起文章——文章內容的一對一映射
一對一關聯有兩種方式,一種是外鍵關聯,另一種是複合主鍵關聯。
外鍵關聯
下面我們先看一個一對一單向關聯的實例
/*************關聯關係維護方************/
@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的,回想我們之前的一對多關聯測試,都是先刪除維護關係方的,這其實很好理解,我們肯定要清除掉相應的關聯關係(體現在數據庫的外鍵上)才能完成被關聯內容的刪除操作
一對一雙向關聯很簡單,直接在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的記錄,則會由於外鍵關係約束失敗而報錯