Hibernate的一对一关联关系
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属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
//getters and setters
}
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
//getters and setters
}
映射文件:
Department.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.foreign.Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<generator class="native" />
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
<!-- 添加unique="true"属性,来表示为1-1关联 -->
<many-to-one name="mgr" class="com.atguigu.hibernate.one2one.foreign.Manager" column="MGR_ID" unique="true"></many-to-one>
</class>
</hibernate-mapping>
Manager.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.foreign.Manager" table="MANAGERS">
<id name="mgrId" type="java.lang.Integer">
<column name="MGR_ID" />
<generator class="native" />
</id>
<property name="mgrName" type="java.lang.String">
<column name="MGR_NAME" />
</property>
<!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 -->
<!-- 没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段 -->
<one-to-one name="dept" class="com.atguigu.hibernate.one2one.foreign.Department" property-ref="mgr"></one-to-one>
</class>
</hibernate-mapping>
运行一个空的test程序,可以生成数据库表managers和departments:
下面测试基于外键的1-1关联关系的save和get操作:
基于外键的1-1关联关系的save操作:
@Test
public void testSave(){
Department department = new Department();
department.setDeptName("DEPT-BB");
Manager manager = new Manager();
manager.setMgrName("MGR-BB");
//设定关联关系
department.setMgr(manager);
manager.setDept(department);
//保存操作
//建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句
session.save(manager);
session.save(department);
}
这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
基于外键的1-1关联关系的get操作:
@Test
public void testGet(){
//1. 默认情况下对关联属性使用懒加载
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());
//2. 所以会出现懒加载异常的问题.
// session.close();
// Manager mgr = dept.getMgr();
// System.out.println(mgr.getClass());
// System.out.println(mgr.getMgrName());
//3. 查询 Manager 对象的连接条件应该是 dept.manager_id = mgr.manager_id
//而不应该是 dept.dept_id = mgr.manager_id
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}
同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,而且已经完成了初始化。如下所示:
@Test
public void testGet2(){
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
//在执行下面的代码之前已经完成了对department对象的初始化
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName());
}
运行结果:
关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
基于主键的一对一关联关系
基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:
<generator class="foreign">
<!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
<param name="property">mgr</param>
</generator>
采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
下面我们仍以department和manager的例子进行测试,首先新建两个类:
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
//getters and setters
}
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
//getters and setters
}
映射文件:
Department.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.one2one.primary">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<!-- 使用外键的方式来生成当前的主键 -->
<generator class="foreign">
<!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
<param name="property">mgr</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<!-- 采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性, 其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束 -->
<one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
Manager.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS">
<id name="mgrId" type="java.lang.Integer">
<column name="MGR_ID" />
<generator class="native" />
</id>
<property name="mgrName" type="java.lang.String">
<column name="MGR_NAME" />
</property>
<one-to-one name="dept" class="com.atguigu.hibernate.one2one.primary.Department"></one-to-one>
</class>
</hibernate-mapping>
生成的数据库表如下:
managers
departments
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
下面测试save和get方法:
基于主键的1-1关联关系的save操作:
@Test
public void testSave(){
Department department = new Department();
department.setDeptName("DEPT-AA");
Manager manager = new Manager();
manager.setMgrName("MGR-AA");
//设定关联关系
manager.setDept(department);
department.setMgr(manager);
//保存操作
//先插入哪一个都不会有多余的 UPDATE
session.save(department);
session.save(manager);
}
和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
基于主键的1-1关联关系的get操作:
@Test
public void testGet(){
//1. 默认情况下对关联属性使用懒加载
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());
//2. 所以会出现懒加载异常的问题.
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}
@Test
public void testGet2(){
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
/*System.out.println(mgr.getMgrName()); System.out.println(mgr.getDept().getDeptName()); */
}
基于主键的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:
还没有评论,来说两句吧...