我們在上節中,按前面設定的ER圖,將teacherId存入了Klass表,做為了Klass表中對應Teacher表的外鍵。Hibernate又是如何處理表與表之間的關系的呢?使用Hibernate后,我們該如何去定義外鍵呢?
在本節中,我們以 班級表與教師表的多對一關系為例,來共同學習Hibernate實體間的關聯關系。

# 多對一(manyToOne many-to-one many-one)
前面我們講過,Hibernate在配置實體時,有兩種方式。其一是使用xml的方式,其二是使用注解的方式。在本教程中,我們使用的注解方式。
<hr />
我們在google中,搜索 Hibernate ManyToOne annotations關鍵字,大概能找到如下官方文檔。
[https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/](https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/)
當然,我們也可以打開我們前面下載過的Hibernate文檔的single_html,然后在頁面中搜索ManyToOne,也能找到注解的相關信息。
[http://127.0.0.1:8081/manual/en-US/html_single](http://127.0.0.1:8081/manual/en-US/html_single)
<hr />
## 加入Teacher實體
ManyToOne是指:每個Klass實體中,都有一個Teacher實體。
所以,我們首先在Klass實體中,增加一個Teacher實體。并增加get/set方法.
```
private Long teacherId;
private Teacher teacher;
public Long getKlassId() {
return klassId;
}
...
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
```
## 增加注解
用注解的方法,來表示ManyToOne關系。同其它的注解一樣,我們可以將注解寫在字段定義的上方,也可以將注解寫在set方法的上方。
```
import javax.persistence.ManyToOne;
...
@ManyToOne
private Teacher teacher;
```
然后,我們執行在上一小節中Klass實體對應的單元測試的create方法,看看控制臺和數據表將發生什么。
控制臺:
```
Hibernate: insert into Klass (name, teacher_teacherId, teacherId) values (?, ?, ?)
```
查看數據表,我們發現增加了teacher_teacherId字段。很明顯,這是由于我們剛剛設置的ManyToOne注解為我們自動生成的。默認的,Hibernate會為我們生成 表名_主鍵名 的外鍵字段。

但這好像,并不符合我們的預期。因為我們更愿意看到teacherId做為外鍵出現。當我們需要指定外鍵名時,我們則需要JoinColumn注解(當然了,在實現的項目開發中,我們不推薦這么做,也不會這么做)。
然后,我們再運行單元測試。
如果不出意外,我們將在Junit控制面板中,得到如下錯誤:
```
org.hibernate.MappingException: Repeated column in mapping for entity: com.mengyunzhi.javaee.entity.Klass column: teacherId (should be mapped with insert="false" update="false")
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:682)
...
```
出現錯誤時,我們第一要做的事情永遠是:閱讀錯誤信息!如果錯誤信息是英文的呢?那么就應該是:翻譯錯誤信息。
```
異常類型:org.hibernate.MappingException: 在實體映射表中出現了重復的字段(列): com.mengyunzhi.javaee.entity.Klass 字段(列)名: teacherId (應該在映射時增加insert="false" update="false")
```
翻譯錯誤后,我們得知,原來是這樣。不錯,的確是這樣,我們在實體類中,已經聲明了teacherId, 然后再聲明關聯實體后,它又會為我們自動生成teacherId。而一個表中的字段是唯一的,這就造成的沖突。解決的方法很簡單,我們刪除原來的TeacherId就可以了。
我們刪除teacherId,然后結合eclipse,重構其對應的其它方法,再對teacher對應在定義時進行實例化。最終實體類Klass代碼如下:
```
package com.mengyunzhi.javaee.entity;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name="Klass")
public class Klass implements IdEntity {
/**
* 班級
*
*/
private static final long serialVersionUID = 1L;
@Id
@GenericGenerator(name="idGenerator",strategy="native")
@GeneratedValue(generator="idGenerator")
private Long klassId;
private String name;
@ManyToOne
@JoinColumn(name="teacherId")
private Teacher teacher = new Teacher();
public Long getKlassId() {
return klassId;
}
public void setKlassId(Long klassId) {
this.klassId = klassId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
/**
* 沒錯,現在teacherId直接存在于關聯對象中
* @return {[type]} [description]
* @author 夢云智 http://www.mengyunzhi.com
* @DateTime 2017-03-02T11:12:31+0800
*/
public Long getTeacherId() {
return (Long) this.teacher.getId();
}
/**
* 道理一樣,直接把teacherId存在關聯對象中
* @param {[type]} Long teacherId [description]
* @author 夢云智 http://www.mengyunzhi.com
* @DateTime 2017-03-02T11:13:03+0800
*/
public void setTeacherId(Long teacherId) {
this.teacher.setId(teacherId);
}
public Klass() {
}
public Klass(String name, long teacherId) {
this.name = name;
// 這里的外理方式變對給關聯對象賦值了
this.teacher.setId(teacherId);
}
/**
* @see com.mengyunzhi.javaee.entity.IdEntity#getId()
*/
public Serializable getId() {
return this.getKlassId();
}
/**
* @see com.mengyunzhi.javaee.entity.IdEntity#setId(java.io.Serializable)
*/
public void setId(Serializable id) {
this.setKlassId((Long) id);
}
}
```
此時,我們先在navicat中刪除數據表Klass(只所以這樣做,是由于hibernate再檢查數據表時,不會刪除冗余字段) 再次運行單元測試:
```
Hibernate: insert into Klass (name, teacherId) values (?, ?)
```

> git checkout -f step13.3
- README
- 第一章:準備
- 第二章:Hello World!
- 第一節:查看工程文件
- 第二節:JDK、JRE與環境變量
- 第三節:index.jsp
- 第三章:Hello Struts
- 第一節:Web.xml
- 第二節:單入口
- 第三節:Hello Struts
- 第四節:觸發C層
- 第四章:建立數據表
- 第一節:建立實體類
- 第二節:測試一
- 第三節:測試二
- 第四節:引入Hibernate
- 第五節:配置Hibernate
- 第六節:建立連接
- 第七節:實體類映射數據表
- 第八節:完善數據表
- 第五章:教師管理
- 第一節:增加數據--add
- 第二節:增加數據--save
- 1 獲取傳入數據數據
- 2 數據寫入測試
- 3 對接C層
- 第三節:數據列表
- 1 獲取數據
- 2 重構代碼
- 3 C層對接--初始化
- 4 C層添加數據
- 5 V層顯示數據
- 6 獲取數據庫中數據
- 7 顯示性別
- 8 分頁
- 9 條件查詢
- 第四節:修改數據
- 1 edit
- 2 update
- 第五節:刪除數據
- 第六節:總結
- 第六章:重構C層
- 第一節:繼承ActionSupport類
- 第二節:數據驗證
- 第七章:前臺分離(前臺)
- 第一節:環境搭建
- 第二節:運行環境
- 第三節:共享開發環境
- 第四節:生產環境
- 第八章:前臺開發(前臺)
- 第一節:本地化
- 第二節:教師列表
- 1 引入M層
- 2 模擬后臺返回數據
- 3 C與M對接
- 4 C與V對接
- 第九章:前后臺對接(前后臺)
- 第一節:后臺輸出json(后臺)
- 第二節:對接前臺(全棧)
- 第二節:對接API(前臺)
- 第二節:跨域請求(后臺)
- 第三節:重構代碼(前臺)
- 第十章:重構后臺M層
- 第一節:數據訪問DAO層
- 第二節:項目整體重構
- 第十一章:用戶登陸(前后臺)
- 第一節:制定規范
- 第二節:定制測試用例
- 第三節:后臺輸入測試代碼(后臺)
- 第四節:postman(后臺)
- 第五節:新建用戶登陸模塊(前臺)
- 第六節:代碼重構(前臺)
- 第十二章:班級管理(前后臺)
- 第一節:班級列表
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第二節:Add
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第三節:Save
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第四節:Edit
- 1 原型開發
- 2 制定規范
- 3 后臺對接開發
- 4 前臺對接開發
- 第五節:Update
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第六節:Delete
- 1 制定規范
- 2 后臺對接開發
- 3 前臺對接開發
- 第七節:小結
- 第十三章:班級管理(API)
- 第一節:ER圖
- 第二節:create
- 1 實體層
- 2 dao層
- 3 service(server)層
- 4 action層
- 第三節:ManyToOne
- 第四節:Read
- 1 service(server)層
- 2 action層
- 第五節:update
- 1 service(server)層
- 2 action層
- 第六節:update
- 第十四章:重構服務層