我们在上节中,按前面设定的ER图,将teacherId存入了Klass表,做为了Klass表中对应Teacher表的外键。Hibernate又是如何处理表与表之间的关系的呢?使用Hibernate后,我们该如何去定义外键呢?
在本节中,我们以 班级表与教师表的多对一关系为例,来共同学习Hibernate实体间的关联关系。
![https://box.kancloud.cn/946a1713ae6fd9a1c471c3555b9cff1c_669x180.png](https://box.kancloud.cn/946a1713ae6fd9a1c471c3555b9cff1c_669x180.png)
# 多对一(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会为我们生成 表名_主键名 的外键字段。
![https://box.kancloud.cn/ae5118d7a2cd2e80fa86c38b2e17bb95_1256x258.png](https://box.kancloud.cn/ae5118d7a2cd2e80fa86c38b2e17bb95_1256x258.png)
但这好像,并不符合我们的预期。因为我们更愿意看到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 (?, ?)
```
![https://box.kancloud.cn/dc42ba8b0b7d1c0939e60b611c4455e5_638x140.png](https://box.kancloud.cn/dc42ba8b0b7d1c0939e60b611c4455e5_638x140.png)
> 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
- 第十四章:重构服务层