一对一的关联关系有一下几类:
1. 单向一对一
a). 基于主键的
b). 基于外键的
2. 双向一对一
a). 基于外键的
好, 下面咱们主要说一下怎么来设置这种关系, 主要是说怎么用, 其中也说一下一些地方为什么这么配置.
OneByOne
一: 基于主键的 单向一对一
我们使用 Person类和 IdCard类来做测试, 一个Person 只能对应一个 IdCard. Just So.
Person类:
package com.single.one2one_primary; public class Person { private Integer id; private String name; private IdCard idCard; public Integer getId() { return id; } public void setId(Integer 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(IdCard idCard) { this.idCard = idCard; } }
IdCard类:
package com.single.one2one_primary; public class IdCard { private Integer id; private String card; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCard() { return card; } public void setCard(String card) { this.card = card; } }
IdCard类的映射文件:
<?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.test.one2one_primary.IdCard" table="IDCARDS"> <id name="id" type="java.lang.Integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="card" type="java.lang.String"> <column name="CARD" /> </property> </class> </hibernate-mapping>
Person的映射文件:
<?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.test.one2one_primary.Person" table="PERSONS"> <id name="id" type="java.lang.Integer"> <column name="ID" /> <!-- 使用 foregin策略: 指使用"对方"的主键来生成自己的主键, 自己并不能独立生成主键. <param name="property">idCard</param>: 指定使用当前持久化类中的那个属性作为"对方" name="property": 固定写法 使用该策略生成主键, 还需要增加一个 <one-to-one> 标签 并且 <one-to-one> 标签属性还应增加 constrained="true" 属性 constrained="true": 指为当前持久化类对应的数据表的主键增加一个外键约束 引用被关联的对象("对方")所使用的数据库主键 --> <generator class="foreign" > <param name="property">idCard</param> </generator> </id> <property name="name" type="java.lang.String"> <column name="NAME" /> </property> <one-to-one name="idCard" constrained="true"></one-to-one> </class> </hibernate-mapping>
Hibernate主配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 配置连接数据库的信息 --> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">oracle</property> <property name="hibernate.connection.url">jdbc:mysql:///hibernate</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <!-- 配置数据库方言 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL57InnoDBDialect</property> <!-- 设置数据表生成策略 --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 是否格式化SQL --> <property name="hibernate.format_sql">true</property> <!-- 是否显示SQL --> <property name="hibernate.show_sql">true</property> <mapping resource="com/single/one2one_primary/IdCard.hbm.xml"/> <mapping resource="com/single/one2one_primary/Person.hbm.xml"/> <!-- <mapping resource="com/single/one2one_foreign/IdCard.hbm.xml"/> <mapping resource="com/single/one2one_foreign/Person.hbm.xml"/> <mapping resource="com/doubles/one2one_foreign/IdCard.hbm.xml"/> <mapping resource="com/doubles/one2one_foreign/Person.hbm.xml"/> --> </session-factory> </hibernate-configuration>
建立单元测试类:
ackage com.single.one2one_foreign; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TestHibernate { private SessionFactory sessionFactory; private Session session; private Transaction transaction; @Before public void init(){ sessionFactory = new Configuration().configure().buildSessionFactory(); session = sessionFactory.openSession(); transaction = session.beginTransaction(); } @After public void distory(){ transaction.commit(); session.close(); sessionFactory.close(); } }
测试保存数据:
@Test public void testInsert(){ Person person = new Person(); person.setName("Jerry"); IdCard idCard = new IdCard(); idCard.setCard("100003"); // 设置关联关系 person.setIdCard(idCard); // 不管保存顺序怎么样, 总是先保存 Order表, 然后再保存 Person表. session.save(person); session.save(idCard); }
测试查询数据:
@Test public void testQuery(){ // 默认使用懒加载 // 可以在 <one-to-one> 总加上 lazy="false" 关闭懒加载 Person person = session.get(Person.class, 1); System.out.println(person.getName()); // 这是一个代理对象 System.out.println(person.getIdCard().getClass().getName()); }
测试删除数据:
@Test public void testDelete(){ IdCard idCard = session.get(IdCard.class, 1); // 删除不了的, 因为有外键约束! // 如何解决呢? 我添加了 cascade="all" 属性, 但是不管用. 不知道怎么解决了. session.delete(idCard); }
测试更新数据:
@Test public void testUpdate(){ IdCard idCard = session.get(IdCard.class, 1); idCard.setCard("200002"); // 我感觉更新数据一般不会出现什么问题, 主要是保存数据和删除数据的时候容易出问题. session.update(idCard); }
二: 基于外键的单向一对一
还是利用上面的Person类和IdCard类, 咱们主要是修改配置文件
使用外键, 然后由于是单向的, 所以配置比较简单, 主要来修改 Person.hbm.xml 文件, eg:
<?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.single.one2one_foreign"> <class name="Person" table="PERSONS"> <id name="id" type="java.lang.Integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="name" type="java.lang.String"> <column name="NAME" /> </property> <!-- 使用 unique="true" 来确定是唯一的, 要不然会出现一对多现象. --> <many-to-one name="idCard" unique="true" class="IdCard" column="IDCARD_ID"></many-to-one> </class> </hibernate-mapping>
这个外键会出现在 Person 表中, 下面咱们还要说双向一对一, <many-to-one>标签出现在哪个表中, 外键就会在哪个表中.
测试数据的保存:
@Test public void testInsert(){ Person person = new Person(); person.setName("Tom"); IdCard idCard = new IdCard(); idCard.setIdCard("10002"); // 设置关联关系 person.setIdCard(idCard); // 先保存没有外键的一端, 然后再保存有外键的一端 // 否则会多出一条 update 语句 session.save(idCard); session.save(person); }
测试查询数据(同上面一样, 默认使用懒加载):
@Test public void testQuery(){ // 默认使用懒加载 // 可以在 <one-to-one> 总加上 lazy="false" 关闭懒加载 Person person = session.get(Person.class, 1); System.out.println(person.getName()); // 这是一个代理对象 System.out.println(person.getIdCard().getClass().getName()); }
测试删除数据(同上面一样, 级联删除还是不行):
@Test public void testDelete(){ IdCard idCard = session.get(IdCard.class, 1); // 删除不了的, 因为有外键约束! // 如何解决呢? 我添加了 cascade="all" 属性, 但是不管用. 不知道怎么解决了. session.delete(idCard); }
测试更新数据(同上面一样):
@Test public void testUpdate(){ IdCard idCard = session.get(IdCard.class, 1); idCard.setIdCard("200002"); // 我感觉更新数据一般不会出现什么问题, 主要是保存数据和删除数据的时候容易出问题. session.update(idCard); }
三: 基于外检的双向一对一
这里跟基于外键的单向一对一差不多, 无非就是在另一个持久化类中添加了一个属性,
这个地方, 我们需要在 IdCard 类中添加 Person 类对象, 并添加上 getter/setter 方法. 然后对它们的映射文件进行配置:
IdCard的映射文件:
<?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.doubles.one2one_foreign"> <class name="IdCard" table="IDCARD"> <id name="id" type="java.lang.Integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="idCard" type="java.lang.String"> <column name="IDCARD" /> </property> <!-- 一定要加上 property-ref="idCard" 否则, Hibernate查询的时候, 会出现SQL连接逻辑出错 on idcard0_.ID=person1_.ID 正常的应该是: on idcard0_.ID=person1_.IDCARD_ID 如果改为 property-ref="name"(这个name是Person类中的name) 那么, 左外连接语句就变成了 on idcard0_.ID=person1_.NAME 可以发现我们配置的 property-ref 的值在对应类中的属性对应的列 就是左外连接 on idcard0_.ID=person1_.? 处, ? 所代表的的值. 如果不配置, property-ref 的取值就是本类的主键属性对应的数据列(ID) 配置成 name 就是 Person类中 name 属性对应的数据列(NAME) 配置成 idCard 就是 Person 类中 idCard 属性对应的数据列(IDCARD_ID) --> <one-to-one name="person" class="Person" property-ref="idCard"></one-to-one> </class> </hibernate-mapping>
Person类的映射文件:
<?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.doubles.one2one_foreign"> <class name="Person" table="PERSON"> <id name="id" type="java.lang.Integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="name" type="java.lang.String"> <column name="NAME" /> </property> <!-- 使用唯一约束(unique="true")保证是一对一 column="IDCARD_ID": 外键的名字 --> <many-to-one name="idCard" class="IdCard" column="IDCARD_ID" unique="true"> </many-to-one> </class> </hibernate-mapping>
测试保存数据:
@Test public void testInsert(){ Person person = new Person(); person.setName("Mike"); IdCard idCard = new IdCard(); idCard.setIdCard("10001"); person.setIdCard(idCard); idCard.setPerson(person); // 先保存没有外键的一端, 然后再保存有外键的一端 // 否则会多出一条 update 语句 session.save(idCard); session.save(person); }
测试查询数据:
@Test public void testQuery(){ // 在双向一对一的情况下 // 1. 查询没有外键的数据表时, 默认使用左外连接(left outer join)进行查询 IdCard idCard = session.get(IdCard.class, 1); System.out.println(idCard.getIdCard()); System.out.println(idCard.getPerson().getClass().getName()); // 2. 查询有外键的数据表时, 默认使用懒加载 Person person = session.get(Person.class, 1); System.out.println(person.getName()); System.out.println(person.getIdCard().getClass().getName()); }
测试删除数据(同上):
只要有外键关联, 没有设置级联操作, 而且外键还有数据表在引用, 就不能删除. 不论是本篇博文中的那种关系.
测试更新数据(同上):
最容易出错的就是查询数据中的懒加载和删除数据中的级联操作, 至于更新数据, 没什么问题.
你很有可能问, 为什么没有基于主键的双向一对一?
很好理解, 因为基于主键的单向一对一: 一方的主键使用另一方的主键来生成自己的主键, 它自己不能生成主键, 只能依赖于对方, 如果是双向的话, 这样, 一方依赖另一方生成主键, 另一方又依赖这一方生成主键, 这样就乱了.