hibernate5(11)註解對映[3]一對多多對一雙向關聯
在上兩篇文章裡,我們詳細地分別講解了一對多和多對一的單向關聯配置的具體屬性含義,在這一篇文章裡,我們完成兩者的的整合建立雙向關聯。
在實際的部落格網站中,我們可能需要根據文章讀取作者(使用者)資訊,但肯定也要讓使用者能獲取自己的文章資訊,針對這種需求,我們可以建立文章(多)對使用者(一)的雙向關聯對映。
下面先看例項對映配置檔案:
/********************一方配置User********************/
@Entity//聲明當前類為hibernate對映到資料庫中的實體類
@Table(name = "t_user1")//宣告在資料庫中自動生成的表名為t_user
public class User {
@Id//宣告此列為主鍵,作為對映物件的識別符號
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = true,mappedBy = "user")//使用者作為一方使用OneToMany註解
@JoinColumn(name = "user_id")
// @JoinTable(name = "t_user_articles",inverseJoinColumns = {@JoinColumn(name = "article_id")},joinColumns = {@JoinColumn(name = "user_id")})
private Set<Article> articles;//文章作為多方,我們使用Set集合來儲存,同時還能防止存放相同的文章
}
/*****************多方配置****************/
@Table(name = "t_article1" )
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String content;
/**
* @ManyToOne 使用此標籤建立多對一關聯,此屬性在“多”方使用註解在我們的“一”方屬性上
* @cascade 指定級聯操作,以陣列方式指定,如果只有一個,可以省略“{}”
* @fetch 定義抓取策略
* @optional 定義是否為必需屬性,如果為必需(false),但在持久化時user = null,則會持久化失敗
* @targetEntity 目標關聯物件,預設為被註解屬性所在類
*/
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private User user;
對映關係確立好,開始編寫我們的測試檔案:
public class Test2 {
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 test3(){
User user = new User();
user.setName("oneObject");
Set<Article> articles = new HashSet<Article>();
for(int i = 0 ; i < 3;i ++){
Article article = new Article();
article.setContent("moreContent" + i) ;
articles.add(article);
}
user.setArticles(articles);//建立關聯關係
session.save(user);
}
@After//每一個被註解Test方法在呼叫後都會呼叫此方法一次
public void teardown(){
if(transaction.isActive()){//如果當前事務尚未提交,則
transaction.commit();//提交事務,主要為了防止在測試中已提交事務,這裡又重複提交
}
session.clear();
session.close();
sessionFactory.close();
}
@After//在類銷燬時呼叫一次
public void after(){
}
}
這個時候,我們執行測試方法test3會發現報錯:
org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: com.zeng.model.User.articles
意思是,一旦被註解@mapperBy,即放棄了維護關聯關係,而@JoinColumn註解的都是在“主控方”,因而我們需要註解在Article類中
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "user_id",unique = false,updatable = true)
private User user;
然後我們再執行測試方法:會看到:
Hibernate: drop table if exists t_article1
Hibernate: drop table if exists t_user1
Hibernate: create table t_article1 (id integer not null auto_increment, content varchar(255), user_id integer, primary key (id))
Hibernate: create table t_user1 (id integer not null auto_increment, name varchar(255), primary key (id))
Hibernate: alter table t_article1 add index FK6D6D45665B90FD3C (user_id), add constraint FK6D6D45665B90FD3C foreign key (user_id) references t_user1 (id)——————在這裡,我們添加了外來鍵約束,這也是hibernate物件關聯在資料庫的重要體現
上面是我們的表建立工作,下面是記錄建立工作
Hibernate: insert into t_user1 (name) values (?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent0, id=3, user=null}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.User{id=1, articles=[com.zeng.model.Article#1, com.zeng.model.Article#2, com.zeng.model.Article#3], name=oneObject}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent2, id=1, user=null}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent1, id=2, user=null}
參考前面一篇文章的測試結果,在一對多單向配置中,因為關聯關係是有一方維護,所以在最後總有三句額外的update語句,來完成article表到user表的對映關係,但在user放棄維護權後,如果我們再嘗試通過儲存使用者通過建立起兩表的對映關係,是不成功的。 從藍色粗體部分,似乎User和article建立了關聯關係。事實上,這是一種偽關聯,它看似讓我們通過session.save(user)。就完成了4者的關聯建立,但在資料庫層次,他們的關聯關係是沒有建立的,這從藍色記錄Article記錄中user=null可以說明這一點。
此外,我們可以通過測試嘗試從user中獲取article物件來進一步驗證:
User user = (User) session.get(User.class, 1);
System.out.println("獲取使用者對應的文章資料:"+user.getArticles());
列印結果:獲取使用者對應的文章資料:[]
這是因為我們的關聯資訊是由多方維護的(user_id),我們想要真正完成兩者,必須從主維護方:article下手
執行以下測試程式碼:
User user = new User();
user.setName("oneObject1");
for(int i = 0 ; i < 3;i ++){
Article article = new Article();
article.setContent("moreContent1" + i) ;
article.setUser(user);//有article來建立關聯關係
session.save(article);//持久化
}
得到列印結果:
Hibernate: insert into t_user1 (name) values (?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
Hibernate: insert into t_article1 (content, user_id) values (?, ?)
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent10, id=4, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent12, id=6, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.Article{content=moreContent11, id=5, user=com.zeng.model.User#2}
DEBUG: org.hibernate.internal.util.EntityPrinter - com.zeng.model.User{id=2, articles=null, name=oneObject1}
再檢視資料庫:
mysql> select * from t_article1;
+—-+—————+———+
| id | content | user_id |
+—-+—————+———+
| 1 | moreContent2 | NULL |——————上次操作遺留
| 2 | moreContent1 | NULL |——————上次操作遺留
| 3 | moreContent0 | NULL |——————上次操作遺留
| 4 | moreContent10 | 2 |
| 5 | moreContent11 | 2 |
| 6 | moreContent12 | 2 |
+—-+—————+———+
6 rows in set (0.00 sec)
從sql語句和藍色DEBUG、資料庫記錄我們能夠看出,這才是最優雅的新增關聯操作,既沒有多餘的update語句,同時完成了資料庫關聯關係的建立。