[转载]Hibernate 一对一 双向关联

来自悟云淡风清《Hibernate 一对一 双向关联》

 

一对一映射有两种方式:主键关联外键关联
以Person类和IdCard类为例,用户和身份证是一对一的双向关联关系

持久化类Person.java:

1
2
3
4
5
6
7
public class Person {
    private int id;
    private String name;
    private IdCard idCard;
   
    //省略setter & getter
}

持久化类IdCard.java:

1
2
3
4
5
6
7
public class IdCard {
    private int id;
    private String cardNo;
    private Person person;
       
    //省略setter & getter
}

hibernate配置文件
hibernate.cfg.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_one_to_one_upk</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">smfr</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="connection.useUnicode">true</property>
        <property name="connection.characterEncoding">GBK</property>
        <property name="hibernate.hbm2ddl.auto">update</property>
        <mapping resource="com/one2one/hibernate/Person.hbm.xml" />
        <mapping resource="com/one2one/hibernate/IdCard.hbm.xml" />
    </session-factory>
</hibernate-configuration>

1.双向一对一主键关联(即其中一个表的主键参照另外一张表的主键而建立起一对一关联关系)
Person.hbm.xml:

1
2
3
4
5
6
7
8
9
10
11
<hibernate-mapping >
    <class name="com.one2one.hibernate.Person" table="t_person">
        <id name="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name"/>
        <!--使用<one-to-one>表示一对一关联-->  
        <one-to-one name="idCard" class="com.one2one.hibernate.IdCard" />
    </class>
</hibernate-mapping>

IdCard.hbm.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
<hibernate-mapping >
    <class name="com.one2one.hibernate.IdCard" table="t_idCard">
        <id name="id">
            <!-- 表示该表的主键值参照“person属性对应的表的主键”的值,注意:但id并不是外键! -->  
            <generator class="foreign">  
            <param name="property">person</param>  
            </generator>  
        </id>
        <property name="cardNo"/>
        <!-- 一对一关联 -->  
        <one-to-one name="person" class="com.one2one.hibernate.Person" />  
    </class>
</hibernate-mapping>

(1)插入
JUnit Test One2OneTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testSave1(){
        //...
    Person person = new Person();
    person.setName("alven");
           
    IdCard idCard = new IdCard();
    idCard.setCardNo("1002711");
           
    person.setIdCard(idCard);
    idCard.setPerson(person);
           
    session.save(person);
    //...
}

输出的SQL语句为:

1
2
Hibernate: INSERT INTO t_person (name) VALUES (?)
Hibernate: INSERT INTO t_idCard (cardNo, id) VALUES (?, ?)

若将idCard.setPerson(person);注释掉,
则相应的Hibernate只有一条,它不会去保存idcard
输出的SQL语句为:

1
Hibernate: INSERT INTO t_person (name) VALUES (?)

若JUnit Test One2OneTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testSave1(){
        //...
    Person person = new Person();
    person.setName("alven");
           
    IdCard idCard = new IdCard();
    idCard.setCardNo("1002711");
           
    //person.setIdCard(idCard);
    idCard.setPerson(person);
           
    session.save(person);
    //...
}

输出的SQL语句为:

1
2
Hibernate: INSERT INTO t_person (name) VALUES (?)
Hibernate: INSERT INTO t_idCard (cardNo, id) VALUES (?, ?)

因为t_idCard的id需要t_person的id,为先保存t_person.

(2)查询
1.通过Person去获得IdCard
JUnit Test One2OneTest:

1
2
3
4
5
6
7
public void testLoad1(){
    //...
    Person person = (Person)session.load(Person.class, 1);
    System.out.println("person.name = " + person.getName());
    System.out.println("idCard.cardNo = "+person.getIdCard().getCardNo());
    //...
}

输出的SQL语句为:

1
Hibernate: SELECT person0_.id AS id1_1_1_, person0_.name AS name2_1_1_, idcard1_.id AS id1_0_0_, idcard1_.cardNo AS cardNo2_0_0_ FROM t_person person0_ LEFT OUTER JOIN t_idCard idcard1_ ON person0_.id=idcard1_.id WHERE person0_.id=?

2.通过IdCard去获得Person
JUnit Test One2OneTest:

1
2
3
4
5
6
7
public void testLoad2(){
    //...
    IdCard idCard = (IdCard)session.load(IdCard.class, 1);
    System.out.println("idCard.cardNo = "+idCard.getCardNo());
    System.out.println("Person.name = "+idCard.getPerson().getName());
    //...
}

输出的SQL语句为:

1
Hibernate: SELECT idcard0_.id AS id1_0_1_, idcard0_.cardNo AS cardNo2_0_1_, person1_.id AS id1_1_0_, person1_.name AS name2_1_0_ FROM t_idCard idcard0_ LEFT OUTER JOIN t_person person1_ ON idcard0_.id=person1_.id WHERE idcard0_.id=?

结论:
1.采用了主键关联方式,那通过主键关联的两张表,其关联记录的主键值须保持同步。这也就意味着,我们只需为一张表(此处为t_person表)设定主键 生成器,而另一张表(即t_idCard表)的主键与之共享相同的主键值。但是t_idCard表中的主键id并不是参照t_person表id的外键。

2.虽然在Person类和IdCard类之间有互相引用关系,但是数据库端t_person和t_idCard表没有任何参照关系,只是当我们保 存Person对象时,若Person中的idCard属性值不为NULL,此时Hibernate就会以Person.hbm.xml中使用的主键生成 器生成的主键值作为向t_person表和t_idCard表中插入记录的主键值,此时保持了关联记录的主键值的同步(由输出的SQL语句可知这是由 Hibernate处理的,数据库端并不知情)

3.主键关联中默认使用立即加载策略,在<one-to-one>中设置lazy=”proxy”是无法使用延迟加载策略的(即设置lazy属性是无效的)

4.由查询语句可知主键关联使用的是左外连接,可以通过修改中的fetch属性值为”select”修改成每次发送一条select语句的形式,但这会降低效率(因为需发送多条select语句)。
Person.hbm.xml:

1
<one-to-one name="idCard" class="com.one2one.hibernate.IdCard" fetch="select" />

IdCard.hbm.xml:

1
<one-to-one name="person" class="com.one2one.hibernate.Person" fetch="select"/>

结果:
通过person获取idcard

1
2
Hibernate: SELECT person0_.id AS id1_1_0_, person0_.name AS name2_1_0_ FROM t_person person0_ WHERE person0_.id=?
Hibernate: SELECT idcard0_.id AS id1_0_0_, idcard0_.cardNo AS cardNo2_0_0_ FROM t_idCard idcard0_ WHERE idcard0_.id=?

通过idcard获取person

1
2
Hibernate: SELECT idcard0_.id AS id1_0_0_, idcard0_.cardNo AS cardNo2_0_0_ FROM t_idCard idcard0_ WHERE idcard0_.id=?
Hibernate: SELECT person0_.id AS id1_1_0_, person0_.name AS name2_1_0_ FROM t_person person0_ WHERE person0_.id=?

5.<one-to-one>中的constrained属性(关联约束)的说明:
5.1 constrained属性值默认为false,当constrained为false时,生成的两个表之间无任何参照关系,并且使用的是立即加载策略
5.2 如果将constrained属性值改为true
5.2.1 如果执行的是保存对象的语句
(1)若Person.hbm.xml中的constrained为true,建立两表并建立参照关系,t_person表中的id将是参照 t_idCard表中id的外键,按照现在的配置,自然就会抛出异常,因为t_idCard表中id参考t_person的id,异常如下:

1
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.one2one.hibernate.IdCard

生成的两表结构及参照关系为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE TABLE `t_person` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `FK_q2gx6s9cco0dl5pd4b0xexsad` (`id`),
    CONSTRAINT `FK_q2gx6s9cco0dl5pd4b0xexsad` FOREIGN KEY (`id`) REFERENCES `t_idcard` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
CREATE TABLE `t_idcard` (
    `id` INT(11) NOT NULL,
    `cardNo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

(2)若IdCard.hbm.xml中的constrained为true,建立两表并建立参照关系,t_idCard表中的id将是参照t_person表id的外键。
生成的两表结构及参照关系为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE `t_person` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

CREATE TABLE `t_idcard` (
    `id` INT(11) NOT NULL,
    `cardNo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `FK_msgl07g3retyydasyfaimipe` (`id`),
    CONSTRAINT `FK_msgl07g3retyydasyfaimipe` FOREIGN KEY (`id`) REFERENCES `t_person` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

(3)两边constrained都为true,虽然会在数据库中建立两个相互参照的表t_person和t_idCard表,但是保存对象时就会抛异常导致插入记录失败

1
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.one2one.hibernate.IdCard

生成的两表结构及参照关系为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE TABLE `t_person` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `FK_q2gx6s9cco0dl5pd4b0xexsad` (`id`),
    CONSTRAINT `FK_q2gx6s9cco0dl5pd4b0xexsad` FOREIGN KEY (`id`) REFERENCES `t_idcard` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
CREATE TABLE `t_idcard` (
    `id` INT(11) NOT NULL,
    `cardNo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    INDEX `FK_msgl07g3retyydasyfaimipe` (`id`),
    CONSTRAINT `FK_msgl07g3retyydasyfaimipe` FOREIGN KEY (`id`) REFERENCES `t_person` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

5.2.2 如果执行的是查询对象的语句
若Person.hbm.xml中的<one-to-one>constrained为true,则当 Person person = (Person)session.load(Person.class, 1);延迟加载Person对象中的idCard
见如下hibernate日志:

1
2
Hibernate: SELECT person0_.id AS id1_1_0_, person0_.name AS name2_1_0_ FROM t_person person0_ WHERE person0_.id=?
Hibernate: SELECT idcard0_.id AS id1_0_1_, idcard0_.cardNo AS cardNo2_0_1_, person1_.id AS id1_1_0_, person1_.name AS name2_1_0_ FROM t_idCard idcard0_ LEFT OUTER JOIN t_person person1_ ON idcard0_.id=person1_.id WHERE idcard0_.id=?

2.双向一对一外键关联(本质上是一对多的蜕化形式)
(1)<many-to-one&gt:元素中设置属性unique=”true”就变成了一对一,另外一端也是<many-to-one>
Person.hbm.xml:

1
2
3
4
5
6
7
8
9
10
<hibernate-mapping >
    <class name="com.one2one.hibernate.Person" table="t_person">
        <id name="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name"/>
        <many-to-one name="idCard" column="idCard_id" cascade="all" unique="true"/>
    </class>
</hibernate-mapping>

IdCard.hbm.xml:

1
2
3
4
5
6
7
8
9
<hibernate-mapping >
    <class name="com.one2one.hibernate.IdCard" table="t_idCard">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="cardNo"/>
        <many-to-one name="person" column="person_id" cascade="all" unique="true"/>
    </class>
</hibernate-mapping>

数据库表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE `t_person` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `idCard_id` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `UK_iwf0ex84epvoh2ndq1axpceg9` (`idCard_id`),
    INDEX `FK_iwf0ex84epvoh2ndq1axpceg9` (`idCard_id`),
    CONSTRAINT `FK_iwf0ex84epvoh2ndq1axpceg9` FOREIGN KEY (`idCard_id`) REFERENCES `t_idcard` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
CREATE TABLE `t_idcard` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `cardNo` VARCHAR(255) NULL DEFAULT NULL,
    `person_id` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `UK_sy8r98naljb3cshr6kmgun4j6` (`person_id`),
    INDEX `FK_sy8r98naljb3cshr6kmgun4j6` (`person_id`),
    CONSTRAINT `FK_sy8r98naljb3cshr6kmgun4j6` FOREIGN KEY (`person_id`) REFERENCES `t_person` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

(1)插入
JUnit Test One2OneTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void testSave1(){
    //...
    IdCard idCard = new IdCard();
    idCard.setCardNo("8888888888888");
           
    Person person = new Person();
    person.setName("alven");
    person.setIdCard(idCard);
           
    idCard.setPerson(person);
    session.save(person);
    //...
}

输出的SQL语句为:

1
2
3
Hibernate: INSERT INTO t_idCard (cardNo, person_id) VALUES (?, ?)
Hibernate: INSERT INTO t_person (name, idCard_id) VALUES (?, ?)
Hibernate: UPDATE t_idCard SET cardNo=?, person_id=? WHERE id=?

说明:因为save的是person对象,所以在此之前需保存idCard对象,在向idCards表中插入记录时并不知道person_id的值 (因为尚未保存对应的person对象,所以先给赋一个person_id默认值NULL),然后保存对象person,此时可以得到正确的 person_id,所以最后再update更新t_idCard表中与之对应的记录的person_id值。若将 idCard.setPerson(person)这句注释掉,则就不会输出上述的update语句。

(2)<many-to-one>元素中设置属性unique=”true”就变成了一对一,另外一端是<one-to-one>
Person.hbm.xml:

1
2
3
4
5
6
7
8
9
10
<hibernate-mapping >
    <class name="com.one2one.hibernate.Person" table="t_person">
        <id name="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name"/>
        <many-to-one name="idCard" cascade="all" unique="true" fetch="join" />
    </class>
</hibernate-mapping>

IdCard.hbm.xml:

1
2
3
4
5
6
7
8
9
<hibernate-mapping >
    <class name="com.one2one.hibernate.IdCard" table="t_idCard">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="cardNo"/>
        <one-to-one name="person" property-ref="idCard" />
    </class>
</hibernate-mapping>

表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE TABLE `t_person` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `idCard` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `UK_c7ore53eylsbhkt2qt3sf28xl` (`idCard`),
    INDEX `FK_c7ore53eylsbhkt2qt3sf28xl` (`idCard`),
    CONSTRAINT `FK_c7ore53eylsbhkt2qt3sf28xl` FOREIGN KEY (`idCard`) REFERENCES `t_idcard` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
CREATE TABLE `t_idcard` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `cardNo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;

(1)插入
JUnit Test One2OneTest:

1
2
3
4
5
6
7
8
9
10
11
public void testSave1(){
    //...  
    IdCard idCard = new IdCard();
    idCard.setCardNo("8888888888888");
           
    Person person = new Person();
    person.setName("alven");
    person.setIdCard(idCard);
    session.save(person);
    //...
}

输出的SQL语句为:

1
2
Hibernate: INSERT INTO t_idCard (cardNo) VALUES (?)
Hibernate: INSERT INTO t_person (name, idCard) VALUES (?, ?)

(1)查询
JUnit Test One2OneTest:

1
2
3
4
5
6
7
public void testLoad1(){
    //...  
    Person person = (Person)session.load(Person.class, 1);
    System.out.println("person.name = " + person.getName());
    System.out.println("idCard.cardNo = "+person.getIdCard().getCardNo());
    //...
}

输出的SQL语句为:

1
2
Hibernate: INSERT INTO t_idCard (cardNo) VALUES (?)
Hibernate: INSERT INTO t_person (name, idCard) VALUES (?, ?)

fetch=”join” 输出的SQL语句为:

1
2
Hibernate: SELECT person0_.id AS id1_1_1_, person0_.name AS name2_1_1_, person0_.idCard AS idCard3_1_1_, idcard1_.id AS id1_0_0_, idcard1_.cardNo AS cardNo2_0_0_ FROM t_person person0_ LEFT OUTER JOIN t_idCard idcard1_ ON person0_.idCard=idcard1_.id WHERE person0_.id=?
Hibernate: SELECT person0_.id AS id1_1_1_, person0_.name AS name2_1_1_, person0_.idCard AS idCard3_1_1_, idcard1_.id AS id1_0_0_, idcard1_.cardNo AS cardNo2_0_0_ FROM t_person person0_ LEFT OUTER JOIN t_idCard idcard1_ ON person0_.idCard=idcard1_.id WHERE person0_.idCard=?

fetch=”select”输出的SQL语句为:

1
2
3
Hibernate: SELECT person0_.id AS id1_1_0_, person0_.name AS name2_1_0_, person0_.idCard AS idCard3_1_0_ FROM t_person person0_ WHERE person0_.id=?
Hibernate: SELECT idcard0_.id AS id1_0_1_, idcard0_.cardNo AS cardNo2_0_1_, person1_.id AS id1_1_0_, person1_.name AS name2_1_0_, person1_.idCard AS idCard3_1_0_ FROM t_idCard idcard0_ LEFT OUTER JOIN t_person person1_ ON idcard0_.id=person1_.idCard WHERE idcard0_.id=?
Hibernate: SELECT person0_.id AS id1_1_0_, person0_.name AS name2_1_0_, person0_.idCard AS idCard3_1_0_ FROM t_person person0_ WHERE person0_.idCard=?

可知对于外键关联的一对一关联关系,<many-to-one>默认的加载策略是延迟加载,将其改为立即加载的方式:
——将lazy属性值改为false,如不希望延迟加载,则需将Person.hbm.xml中<many-to-one>的属性值改为false。
——将fetch属性设为”join”,这比改lazy为false效率更高。

示例代码:
下载 hibernate_one2one_upk
下载 hibernate_one2one_ufk1
下载 hibernate_one2one_ufk2

码字很辛苦,转载请注明来自悟云淡风清《Hibernate 一对一 双向关联》
posted @ 2015-12-01 20:28  Emil92  阅读(176)  评论(0编辑  收藏  举报