Hibernate:many-to-one(多对一)关联映射详解
多对一关联映射
什么是many-to-one?
概念
多对一关联,多个对象通过关系属性可以对应同一个对象。
在数据库中表现为:一张表中的记录通过外键字段与另一张表建立关联关系,外键作为另一张表对应记录的主键,且可以有多条记录拥有相同的外键,来对应另一张表中同一条记录。
举例:
一名游客来自中国,他的国籍属性为中国,而在一个旅行团中有多名游客来自中国,那么他们的国籍这一属性是相同的,都为中国。这就是一个多对一关系。
关系的维护
表与表之间通过外键关联,谁往外键中设值,谁就是在维护/创建记录和记录之间的关系。
建立模型
下文中我们将通过这个模型对多对一关联映射进行研究
使用并配置many-to-one
在hbm.xml映射文件中配置多对一关联映射
我们首先配置group表的映射:
将表名配置为t_group,主键的生成策略为native(数据库生成)。
紧接着我们配置User表:
表名配置为t_user,主键生成策略同样是native。
我们知道property中配置普通属性,但我们将group属性放入了many-to-one标签,且将属性在数据库中对应的字段名配置为groupid。
注: 已知我们将group对应的字段名命名为groupid,如果不使用column命名,会缺省生成和属性名相同的名为group的字段。但我们这个案例中必须重命名,因为group是MySql表的关键字。
说明:
要实现表和表之间的关联,就必须依靠相应的关联属性。经过配置后,此时User类和group类就建立了多对一的关系,而group就是User对象中的关联属性。关联属性作为外键,存放了表和表之间的关系。
也就是说,外键字段和对应的group属性的类型所对应的t_group表进行了关联、参照。
many-to-one的应用
使用多对一关联进行表的创建和使用
我们首先将两张表创建出来:
通过Hibernate发送的sql语句我们看到:t_user表中的groupid属性作为外键参照t_group表中的主键id,即t_user表中的groupid字段的值来自于t_group表中的主键。
插入数据
创建group对象:
查看运行结果
断点下移,执行save方法:
查看运行日志
由于group的主键生成策略是数据库生成,我们看到Hibernate发送了sql语句
id的值被初始化
由于发送了sql语句,临时集合被清空。map中存储了group对象的数据,说明group对象纳入Session管理,进入了持久态。
断点下移,创建User对象:
查看运行结果
由图可知,两个User对象的group属性都指向了group对象。
断点下移,对User1执行save方法:
User类的id生成策略也是数据库生成,即Hibernate发送sql语句。
id被初始化。
断点下移,对User2执行save方法
同样发送sql语句,初始化了id的值
此时三个对象都纳入Session管理,处于持久态。
断点下移,执行commit。我们查询数据库中存储的数据
t_group表:
t_user表:
User对象中group属性指向的是group类,为什么group在数据库中对应的字段groupid的值为整型1呢?
根据hbm.xml配置文件的配置信息,在向数据库上传数据时,Hibernate会自动将对象之间的关系转换成表里记录和记录之间的关系,groupid字段存入的是group属性所指对象的id的值。
查询数据
我们使用懒加载到User类所对应的的表中,将id为1的记录加载到User对象中:
由于是懒加载,Hibernate不会发送sql语句,生成代理对象
我们将断点下移,调用对象的方法:
Hibernate发送了sql语句,将记录中的数据取出存放到新建的User对象中
最后一行输出的是User对象name的值
target指向了生成的User对象,这时我们看到,group属性中指向了group代理对象
我们接着将断点下移,调用group对象的方法:
Hibernate发送sql语句,将记录中的数据取出存放到新建的group对象中
最后一行输出的是group对象name的值
group代理对象的target指向了生成的group对象
接着commit同步数据,关闭Session,查询完成
补充说明:
本例中查询关联表t_group中的数据时:
- Hibernate先从表t_user找出id为2的记录;
- 找出该记录的groupid的值;
- 到t_group表中找出与之相同的id值,并取出该记录,把该值放到group对象中;
- 再通过group对象调用相应的方法返回查询的数据。
TransientObjectException异常分析
异常测试
先分别创建出group和User两个对象:
查看两个对象的状态
此时两个对象都处于瞬时态,session中肯定是没有缓存的,由于配置的主键生成策略都为native,所以两个对象的id都为int类型的初值:0。
我们将断点下移,执行save方法:
一切正常,发送sql语句
User对象中的id被初始化。
User对象已经纳入了Session管理,进入持久态,而此时的group没有出现在Session的缓存中,说明group对象依旧处于瞬时态。
我们继续将断点下移,执行commit:
产生了TransientObjectException异常(瞬时态异常)
原因分析
当执行commit操作时,group对象依旧处于持久态未曾改变,即该对象的id没有通过主键生成策略生成,而是一个默认值0。
因此,User对象中的数据提交到数据库时groupid字段中的值就是0,这就已经违背了参照完整性(外键的值只能是主键或null,数据库生成的外键是从1开始的,不可能为0)。
官方给出的提示:
因为Group为Transient状态,id没有分配值,
persistent状态的对象是不能引用transient状态的对象的。
补充说明:
通过上述流程,我们得出:
- save不会对数据进行监测;
- 数据的确认由commit完成。
many-to-one中的cascade标签
TransientObjectException异常的解决方式:
- 在commit提交数据前,先对关联的对象调用save方法,确保生成正确地外键;
- 不对关联属性进行设置,保持外键为空;
- 使用级联。
直接执行配置cascade标签后的代码
查看运行结果:
我们看到,Hibernate发送了sql语句,且没有产生任何异常。
数据也完整地存入了数据库中
说明:本文仅用作学习笔记,无其他用途,如有冒犯可联系本人删除