【SSH進階之路】【十二】hibernate5 註解映射【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的,回想我們之前的一對多關聯測試,都是先刪除維護關係方的,這其實很好理解,我們肯定要清除掉相應的關聯關係(體現在數據庫的外鍵上)才能完成被關聯內容的刪除操作

一對一雙向關聯很簡單,直接在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的記錄,則會由於外鍵關係約束失敗而報錯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章