Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一个部门经理也对应唯一一个部门。
在基于外键的一对一关联关系中,一端通过一个主键以外的字段关联另一端的主键,如下图所示:
在基于主键的一对一关联关系中,一端直接通过主键关联另一端的主键,并通过另一端的主键生成自己的主键,如下图所示:
下面进行详细说明。
基于外键的一对一关联关系
对于基于外键的1-1关联关系,外键可以存放在任意一端,例如在本例中,外键既可以存放在department一端,也可以存放在manager一端,我们假设存放在department一端。在需要存放外键的一端,增加many-to-one节点,并且为many-to-one节点添加unique=”true”属性,来表示为1-1关联,添加unique=”true”属性了以后,不同的department就不能关联同一个manager了。在不存放外键的一端,需要使用one-to-one节点,并且在该节点中添加property-ref属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:
映射文件:
Department.hbm.xml
Manager.hbm.xml
运行一个空的test程序,可以生成数据库表managers和departments:
下面测试基于外键的1-1关联关系的save和get操作:
基于外键的1-1关联关系的save操作:
这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
基于外键的1-1关联关系的get操作:
同n-1关联关系一样,当查询存放外键的一端的对象department的时候,使用懒加载机制,即不会立即加载它关联的另一端的对象manager,而只有等到要使用manager的时候,才会发送select语句加载。那么同样也有可能发生懒加载异常。运行结果如下图所示:
值得注意的是,当要使用到manager对象时,是通过左外连接查询到manager对象的,连接条件是dept.manager_id = mgr.manager_id,这是正确的,因为我们在Manager.hbm.xml中的one-to-one节点中配置了property-ref=”mgr”,指定关联字段为department的mgr字段。如果没有设置property-ref属性,那么默认关联的字段为department的id字段,例如,我们去掉property-ref=”mgr”的设置,运行testGet()方法,则会打印如下的sql语句(连接条件是dept.dept_id = mgr.manager_id),这显然是不符合需求的。
还有一个注意点,就是当首先查询不存放外键的一端的对象时,即manager,由于其中没有设置外键关联到department,所以会使用左外连接查询,一并查询出另一端的对象,即department,而且已经完成了初始化。如下所示:
运行结果:
关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
基于主键的一对一关联关系
基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。<param>子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:
采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
下面我们仍以department和manager的例子进行测试,首先新建两个类:
映射文件:
Department.hbm.xml
Manager.hbm.xml
生成的数据库表如下:
managers
departments
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
下面测试save和get方法:
基于主键的1-1关联关系的save操作:
和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
基于主键的1-1关联关系的get操作:
基于主键的1-1和基于外键的1-1十分相似,在查询department时都使用懒加载机制,可能会抛出懒加载异常,在查询manager时都会使用左外连接,但不同的是,我们在Manager.hbm.xml文件的one-to-one节点中没有设置property-ref属性,即默认department中关联manager的字段是department的id,这正是我们在基于主键的1-1关系中希望的,所以可以看到,左外连接的连接条件是dept.dept_id = mgr.manager_id: