Hibernate学习笔记(四)关系映射之一对一关联映射
一、 一对一关联映射
² 两个对象之间是一对一的关系,如Person-IdCard(人—身份证号)
² 有两种策略可以实现一对一的关联映射
Ø 主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
Ø 唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关联关系。
对象模型
实体类:
/** 人-实体类 */
public class Person {
private int id;
private String name;
public int getId() {return id; }
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(Stringname) {this.name = name;}
}
/**身份证-实体类*/
public class IdCard {
private int id;
private String cardNo;
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getCardNo(){ return cardNo;}
public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}
(一) 唯一外键关联-单向(unilateralism)
1、 说明:
人—-> 身份证号(PersonàIdCard),从IdCard看不到Person对象
2、 对象模型
需要在Person类中持有IdCard的一个引用idCard,则IdCard中没有Person的引用
3、 关系模型
关系模型目的:是实体类映射到关系模型(数据库中),是要求persion中添加一个外键指向idcard
4、 实体类:
注:IdCard是被引用对象,没有变化。
/** 人-实体类 */
public class Person {
private int id;
private String name;
private IdCard idCard;//引用IdCard对象
public int getId() {return id; }
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name){this.name = name;}
public IdCard getIdCard() { return idCard;}
public void setIdCard(IdCardidCard) {this.idCard = idCard;}
}
5、 xml映射
IdCard实体类的映射文件:
因为IdCard是被引用的,所以没有什么特殊的映射
<hibernate-mapping>
<class name="com.wjt276.hibernate.IdCard" table="t_idcard">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="cardNo"/>
</class>
</hibernate-mapping>
Person实体类的映射文件
在映射时需要添加一个外键的映射,就是指定IdCard的引用的映射。这样映射到数据库时,就会自动添加一个字段并作用外键指向被引用的表
<hibernate-mapping>
<class name="com.wjt276.hibernate.Person" table="t_person">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name"/>
<!-- <many-to-one>:在多的一端(当前Person一端),加入一个外键(当前为idCard)指向一的一端(当前IdCard),但多对一 关联映射字段是可以重复的,所以需要加入一个唯一条件unique="true",这样就可以此字段唯一了。-->
<many-to-one name="idCard" unique="true"/>
</class>
</hibernate-mapping>
注意:这里的<many-to-one>标签中的name属性值并不是数据库中的字段名,而是Person实体类中引用IdCard对象成员属性的getxxx方法后面的xxx(此处是getIdCard,所以是idCard),要求第一个字段小写。如果不指定column属性,则数据库中的字段名同name值
6、 annotateon注解映射
注意IdCard是被引用对象,除正常注解,无需要其它注解
/**身份证*/
@Entity
public class IdCard {
private int id;
private String cardNo;
@Id
@GeneratedValue
public int getId() {return id;}
public void setId(int id) { this.id = id;}
public String getCardNo(){return cardNo;}
public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}
而引用对象的实体类需要使用@OneToOne进行注解,来表面是一对一的关系
再使用@JoinColumn注解来为数据库表中这个外键指定个字段名称就可以了。如果省略@JoinColumn注解,则hibernate会自动为其生成一个字段名(好像是:被引用对象名称_被引用对象的主键ID)
/** 人-实体类 */
@Entity
public class Person {
private int id;
private IdCard idCard;//引用IdCard对象
private String name;
@Id
@GeneratedValue
public int getId() {return id;}
@OneToOne//表示一对一的关系
@JoinColumn(name="idCard")//为数据中的外键指定个名称
public IdCard getIdCard(){ return idCard;}
public String getName() {return name;}
public void setId(int id) {this.id = id;}
public void setIdCard(IdCardidCard) {this.idCard = idCard;}
public void setName(Stringname) {this.name = name;}
}
7、 生成的SQL语句:
create tableIdCard (
id integernot null auto_increment,
cardNo varchar(255),
primary key(id)
)
create tablePerson (
id integernot null auto_increment,
namevarchar(255),
idCardinteger,//新添加的外键
primary key(id)
)
alter tablePerson
add indexFK8E488775BE010483 (idCard),
addconstraint FK8E488775BE010483
foreign key(idCard) //外键
referencesIdCard (id)//引用IdCard的id字段
8、 存储测试
Session session = sf.getCurrentSession();
IdCard idCard = new IdCard();
idCard.setCardNo("88888888888888888888888");
session.beginTransaction();
// 如果先不保存idCard,则出抛出Transient异常,因为idCard不是持久化状态。
session.save(idCard);
Person person = new Person();
person.setName("菜10");
person.setIdCard(idCard);
session.save(person);
session.getTransaction().commit();
(二) 唯一外键关联-双向
1、 说明:
人<—-> 身份证号(Person<->IdCard)双向:互相持有对方的引用
2、 对象模型:
3、 关系模型:
关系模型没有任务变化,同单向
4、 实体类:
实体类,只是相互持有对象的引用,并且要求getter和setter方法
5、 xml映射
Person实体类映射文件:同单向的没有变化
IdCard实体类映射文件:如果使用同样的方法映射,这样就会在表中也添加一个外键指向对象,但对象已经有一个外键指向自己了,这样就造成了庸字段,因为不需要在表另外添加字段,而是让hibernate在加载这个对象时,会根据对象的ID到对方的表中查询外键等于这个ID的记录,这样就把对象加载上来了。也同样需要使用<one-to-one>标签来映射,但是需要使用property-ref属性来指定对象持有你自己的引用的成员属性名称(是gettxxxx后面的名称),这样在生成数据库表时,就不会再添加一个多于的字段了。数据加载时hibernate会根据这些配置自己加载数据
<class name="com.wjt276.hibernate.IdCard" table="idcard">
<id name="id" column="id">
<generator class="native"/></id>
<property name="cardNo"/>
<!--<one-to-one>标签:告诉hibernate如何加载其关联对象
property-ref属性:是根据哪个字段进行比较加载数据 -->
<one-to-one name="person" property-ref="idCard"/>
</class>
一对一 唯一外键 关联映射 双向 需要在另一端(当前IdCard),添加<one-to-one>标签,指示hibernate如何加载其关联对象(或引用对象),默认根据主键加载(加载person),外键关联映射中,因为两个实体采用的是person的外键来维护的关系,所以不能指定主键加载person,而要根据person的外键加载,所以采用如下映射方式:
<!--<one-to-one>标签:告诉hibernate如何加载其关联对象
property-ref属性:是根据哪个字段进行比较加载数据 -->
<one-to-one name="person" property-ref="idCard"/>
6、 annotateon注解映射
Person注解映射同单向一样
IdCard注解映射如下:使用@OneToOne注解来一对一,但这样会在表中多加一个字段,因为需要使用对象的外键来加载数据,所以使用属性mappedBy属性在实现这个功能
@Entity
public class IdCard {
private int id;
private String cardNo;
private Person person;
//mappedBy:在指定当前对象在被Person对象的idCard做了映射了
//此值:当前对象持有引用对象中引用当前对象的成员属性名称(getXXX后的名称)
//因为Person对象的持有IdCard对象的方法是getIdCard()因为需要小写,所以为idCard
@OneToOne(mappedBy="idCard")
public Person getPerson(){return person;}
public void setPerson(Person person){this.person = person;}
@Id
@GeneratedValue
public int getId() {return id;}
public void setId(int id) { this.id = id;}
public String getCardNo(){return cardNo;}
public void setCardNo(StringcardNo) {this.cardNo = cardNo;}
}
7、 生成SQL语句
因为关系模型没有变化,也就是数据库的结构没有变化,只是在数据加载时需要相互加载对方,这由hibernate来完成。因为生成的sql语句同单向一样。
8、 存储测试
存储同单向一样
9、 总结:
规律:凡是双向关联,必设mappedBy
(三) 主键关联-单向(不重要)
主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
1、 说明:
人—-> 身份证号(PersonàIdCard),从IdCard看不到Person对象
2、 对象模型
站在人的角度来看,对象模型与唯一外键关联一个,只是关系模型不同
3、 关系模型
因为是person引用idcard,所以idcard要求先有值。而person的主键值不是自己生成的。而是参考idcard的值,person表中即是主键,同时也是外键
4、 实体类:
实体类同 一对一 唯一外键关联的实体类一个,在person对象中持有idcard对象的引用(代码见唯一外键关系)
5、 xml映射
IdCard映射文件,先生成ID
<class name="com.wjt276.hibernate.IdCard" table="t_idcard">
<id name="id"column="id">
<generator class="native"/>
</id>
<property name="cardNo"/>
</class>
Person实体类映射文件,ID是根据IdCard主键值
<class name="com.wjt276.hibernate.Person"table="t_person">
<id name="id"column="id">
<!--因为主键不是自己生成的,而是作为一个外键(来源于其它值),所以使用foreign生成策略
foreign:使用另外一个相关联的对象的标识符,通常和<one-to-one>联合起来使用。再使用元素<param>的属性值指定相关联对象(这里Person相关联的对象为idCard,则标识符为idCard的id)为了能够在加载person数据同时加载IdCard数据,所以需要使用一个标签<one-to-one>来设置这个功能。 -->
<generator class="foreign">
<!-- 元素<param>属性name的值是固定为property -->
<param name="property">idCard</param>
</generator>
</id>
<property name="name"/>
<!-- <one-to-one>标签
表示如何加载它的引用对象(这里引用对象就指idCard这里的name值是idCard),同时也说是一对一的关系。 默认方式是根据主键加载(把person中的主键取出再到IdCard中来取相关IdCard数据。) 我们也说过此主键也作为一个外键引用 了IdCard,所以需要加一个数据库限制(外键约束)constrained="true" -->
<one-to-one name="idCard"constrained="true"/>
6、 annotateon注解映射
Person实体类注解
方法:只需要使用@OneToOne注解一对一关系,再使用@PrimaryKeyJoinColumn来注解主键关系映射。
@Entity
public class Person {
private int id;
private IdCard idCard;//引用IdCard对象
private String name;
@Id
public int getId() {return id;}
@OneToOne//表示一对一的关系
@PrimaryKeyJoinColumn//注解主键关联映射
public IdCard getIdCard(){ return idCard;}
public String getName() {return name;}
public void setId(int id) {this.id = id;}
public void setIdCard(IdCard idCard){this.idCard = idCard;}
public void setName(Stringname) {this.name = name;}
}
IdCard实体类,不需要持有对象的引用,正常注解就可以了。
7、 生成SQL语句
生成的两个表并没有多余的字段,因为是通过主键在关键的
create tableIdCard (
id integernot null auto_increment,
cardNovarchar(255),
primary key (id)
)
create tablePerson (
id integernot null,
namevarchar(255),
primary key(id)
)
alter table person
add index FK785BED805248EF3 (id),
add constraint FK785BED805248EF3
foreign key (id) references idcard (id)
注意:annotation注解后,并没有映射出外键关键的关联,而xml可以映射,是主键关联不重要
8、 存储测试
session = HibernateUtils.getSession();
tx = session.beginTransaction();
IdCard idCard = new IdCard();
idCard.setCardNo("88888888888888888888888");
Person person = new Person();
person.setName("菜10");
person.setIdCard(idCard);
//不会出现TransientObjectException异常
//因为一对一主键关键映射中,默认了cascade属性。
session.save(person);
tx.commit();
9、 总结
让两个实体对象的ID保持相同,这样可以避免多余的字段被创建
<id name="id"column="id">
<!—person的主键来源idcard,也就是共享idCard的主键-->
<generator class="foreign">
<param name="property">idCard</param>
</generator>
</id>
<property name="name"/>
<!—one-to-one标签的含义:指示hibernate怎么加载它的关联对象,默认根据主键加载
constrained="true",表面当前主键上存在一个约束:person的主键作为外键参照了idCard-->
<one-to-one name="idCard" constrained="true"/>
(四) 主键关联-双向(不重要)
主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
主键关联映射,实际是数据库的存储结构并没有变化,只是要求双方都可以持有对象引用,也就是说实体模型变化,实体类都相互持有对方引用。
另外映射文件也变化了。
1、 xml映射
Person实体类映射文件不变,
IdCard如下:
<class name="com.wjt276.hibernate.IdCard" table="t_idcard">
<id name="id" column="id">
<generator class="native"/> </id>
<property name="cardNo"/>
<!—one-to-one标签的含义:指示hibernate怎么加载它的关联对象(这里的关联对象为person),默认根据主键加载-->
<one-to-one name="person"/>
</class>
2、 annotateon注解映射:
Person的注解不变,同主键单向注解
IdCard注解,只需要在持有对象引用的getXXX前加上
@OneToOne(mappedBy="idCard") 如下:
@Entity
public class IdCard {
private int id;
private String cardNo;
private Person person;
@OneToOne(mappedBy="idCard")
public Person getPerson(){
return person;
}}
(五) 联合主键关联(Annotation方式)
实现上联合主键的原理同唯一外键关联-单向一样,只是使用的是@JoinColumns,而不是@JoinColumn,实体类注解如下:
@OneToOne
@JoinColumns(
{
@JoinColumn(name="wifeId", referencedColumnName="id"),
@JoinColumn(name="wifeName", referencedColumnName="name")
}
)
public WifegetWife() {
return wife;
}
注意:@oinColumns注解联合主键一对一联系,然后再使用@JoinColumn来注解当前表中的外键字段名,并指定关联哪个字段,使用referencedColumnName指定哪个字段的名称
二、 component(组件)关联映射
(一) Component关联映射:
目前有两个类如下:
大家发现用户与员工存在很多相同的字段,但是两者有不可以是同一个类中,这样在实体类中每次都要输入很多信息,现在把联系信息抽取出来成为一个类,然后在用户、员工对象中引用就可以,如下:
值对象没有标识,而实体对象具有标识,值对象属于某一个实体,使用它重复使用率提升,而且更清析。
以上关系的映射称为component(组件)关联映射
在hibernate中,component是某个实体的逻辑组成部分,它与实体的根本区别是没有oid,component可以成为是值对象(DDD)。
采用component映射的好处:它实现了对象模型的细粒度划分,层次会更加分明,复用率会更高。
(二) User实体类:
public class User {
private int id;
private String name;
private Contact contact;//值对象的引用
public int getId() {return id;}
public void setId(int id) { this.id = id;}
public String getName() { return name;}
public void setName(Stringname) { this.name = name;}
public ContactgetContact() { return contact;}
public void setContact(Contactcontact) { this.contact = contact;}
}
(三) Contact值对象:
public class Contact {
private String email;
private String address;
private String zipCode;
private String contactTel;
public String getEmail(){ return email;}
public void setEmail(Stringemail) { this.email = email; }
public StringgetAddress() {return address;}
public void setAddress(Stringaddress) {this.address = address;}
public StringgetZipCode() {return zipCode;}
public void setZipCode(StringzipCode) {this.zipCode = zipCode;}
public StringgetContactTel() { return contactTel;}
public voidsetContactTel(String contactTel){this.contactTel = contactTel;}
}
(四) xml--User映射文件(组件映射):
<hibernate-mapping>
<class name="com.wjt276.hibernate.User" table="t_user">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<!-- <component>标签用于映射Component(组件)关系
其内部属性正常映射。
-->
<component name="contact">
<property name="email"/>
<property name="address"/>
<property name="zipCode"/>
<property name="contactTel"/>
</component>
</class>
</hibernate-mapping>
(五) annotateon注解
使用@Embedded用于注解组件映射,表示嵌入对象的映射
@Entity
public class User {
private int id;
private String name;
private Contact contact;//值对象的引用
@Id
@GeneratedValue
public int getId() { return id;}
@Embedded//用于注解组件映射,表示嵌入对象的映射
public ContactgetContact() {return contact;}
public void setContact(Contactcontact) {this.contact = contact;}
Contact类是值对象,不是实体对象,是属于实体类的某一部分,因此没有映射文件
(六) 导出数据库输出SQL语句:
create table User (
id integer not null auto_increment,
address varchar(255),
contactTel varchar(255),
email varchar(255),
zipCode varchar(255),
name varchar(255),
primary key (id)
)
(七) 数据表结构:
注:虽然实体类没有基本联系信息,只是有一个引用,但在映射数据库时全部都映射进来了。以后值对象可以重复使用,只要在相应的实体类中加入一个引用即可。
(八) 组件映射数据保存:
session =HibernateUtils.getSession();
tx =session.beginTransaction();
User user= new User();
user.setName("10");
Contactcontact = new Contact();
contact.setEmail("wjt276");
contact.setAddress("aksdfj");
contact.setZipCode("230051");
contact.setContactTel("3464661");
user.setContact(contact);
session.save(user);
tx.commit();
实体类中引用值对象时,不用先保存值对象,因为它不是实体类,它只是一个附属类,而session.save()中保存的对象是实体类。