hibernate系列(二)一对多的关联关系

上一篇文章介绍了基本知识后,本篇该介绍下现实中的一对多的关联关系。如Customer和Order,一个Customer可以拥有多个Order,每个Order只属于一个Customer。这样就存在几种表示形式,可以分为单向关联和双向关联。 
形式1:Order拥有一个Customer引用,这种属于单向关联 
形式2:Customer拥有Order的集合,这种也属于单向关联 
形式3:Order拥有一个Customer引用,同时Customer拥有Order集合,这种属于双向关联 

先来说说形式1:Order拥有一个Customer引用 
Customer还是上一篇文章的形式: 

?


1

2

3

4

5

6

7

8

public class Customer {

 

    private Long id;

    private String name;

    private String email;

    private Timestamp registeredTime;

//省略get、set方法

}

customer表的信息是: 

?


1

2

3

4

5

6

7

CREATE TABLE `customer` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `name` varchar(45) DEFAULT '',

  `email` varchar(45) DEFAULT '',

  `registeredTime` timestamp NULL DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

它的映射文件为Customer.hbm.xml: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?xml version="1.0" encoding="UTF-8"?>

<!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.ligang.domain.Customer" table="customer">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="name" column="name" type="string"/>

        <property name="email" column="email" type="string"/>

        <property name="registeredTime" column="registeredTime" type="timestamp"/>

    </class>

</hibernate-mapping>

然后是Order类: 

?


1

2

3

4

5

6

7

public class Order {

 

    private Long id;

    private String orderNumber;

    private Customer customer;

//省略get、set方法

}

order表的信息如下: 

?


1

2

3

4

5

6

CREATE TABLE `order` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `orderNumber` varchar(45) DEFAULT '',

  `customer_id` int(11) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

对应的映射文件为Order.hbm.xml: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

<?xml version="1.0" encoding="UTF-8"?>

<!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.ligang.domain.Order" table="order">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="orderNumber" column="orderNumber" type="string"/>

        <many-to-one name="customer" column="customer_id" class="com.ligang.domain.Customer"/>

    </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

23

24

25

@Test

    public void testAddOrder(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Customer customer=new Customer();

        customer.setName("校长");

        customer.setEmail("sdsd@qq.com");

        customer.setRegisteredTime(getCurrentTime());

        session.save(customer);

        System.out.println(customer.getId()+":"+customer.getName());

         

        Order order1=new Order();

        order1.setCustomer(customer);

        order1.setOrderNumber("第一个订单");

         

        Order order2=new Order();

        order2.setCustomer(customer);

        order2.setOrderNumber("第二个订单");

        session.save(order1);

        session.save(order2);

         

        tx.commit();

        session.close();

    }

这个过程先保存了Customer对象,然后保存了Order对象,此时一切正常,如下sql语句: 

?


1

2

3

4

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

33:校长

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

然而当我们不保存Customer对象时,即不执行上述的session.save(customer),则会报如下错误: 

?


1

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ligang.domain.Customer

说Order对象引用了未保存的Customer对象,如果希望customer_id可以为空,单独保存Order,则需要在Order.hbm.xml中指明customer_id字段是可以为空的,即not-null="false"(默认就是false,所以不用再设置)。同时,不能执行setCustomer(customer);这样的语句,要保留Customer为空,一旦不为空,并且属于没有保存的对象,则就会报上述错误。 
如果希望,在保存Order的时候,同时级联的保存Customer对象,则需要在Order.hbm.xml中指明customer_id字段的cascade="save-update",此时就会保存或者更新Order的同时级联的保存 
Customer对象,如下sql: 

?


1

2

3

4

null:校长

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

对于查询Order,如下: 

?


1

2

3

4

5

6

7

8

9

10

11

12

@Test

    public void testGetOrder(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Order order=(Order)session.get(Order.class,9L);

        System.out.println(order.getOrderNumber());

        System.out.println(order.getCustomer().getName());

         

        tx.commit();

        session.close();

    }

查询sql如下: 

?


1

2

3

4

Hibernate: select order0_.id as id1_1_0_, order0_.orderNumber as orderNum2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.id=?

第二个订单

Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.email as email3_0_0_, customer0_.registeredTime as register4_0_0_ from hibernate.customer customer0_ where customer0_.id=?

校长

会先去查询Order信息,然后当你使用到customer时,才会进一步去查询Customer信息,这就是延迟加载,此时的customer对象仅仅是一个代理对象。如果你想查询Order信息时同时把Customer信息查询出来,就需要在Order.hbm.xml的customer_id字段中指定lazy="false",默认是"proxy"。如下效果: 

?


1

2

3

4

Hibernate: select order0_.id as id1_1_0_, order0_.orderNumber as orderNum2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.id=?

Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.email as email3_0_0_, customer0_.registeredTime as register4_0_0_ from hibernate.customer customer0_ where customer0_.id=?

第二个订单

校长

先把所有信息都查出来,然后供使用。目前lazy的取值有三个,false、proxy、no-proxy。false代表:查询Order信息时,立马把Customer的信息查出来,此时order.getCustomer()就是一个Customer对象。proxy代表:查询Order信息时,并不会去查询Customer信息,只有当你用到order的Customer时才会去查询,此时order.getCustomer()返回的是一个代理对象。no-proxy:目前的效果和proxy一样,这一块我还需要继续研究。 

然后来说说形式2:Customer拥有Order的集合 
其中Order类为: 

?


1

2

3

4

5

6

public class Order {

 

    private Long id;

    private String orderNumber;

//略get、set方法

}

Order对应的映射文件为: 

?


1

2

3

4

5

6

7

8

<hibernate-mapping>

    <class name="com.ligang.domain.Order" table="order">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="orderNumber" column="orderNumber" type="string"/>

    </class>

</hibernate-mapping>

Customer对应的类为: 

?


1

2

3

4

5

6

7

8

9

public class Customer {

 

    private Long id;

    private String name;

    private String email;

    private Timestamp registeredTime;

    private Set<Order> orders;

//略get、set方法

}

Customer对应的映射文件为: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

<hibernate-mapping>

    <class name="com.ligang.domain.Customer" table="customer">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="name" column="name" type="string"/>

        <property name="email" column="email" type="string"/>

        <property name="registeredTime" column="registeredTime" type="timestamp"/>

        <set name="orders" cascade="save-update">

            <key column="customer_id"/>

            <one-to-many class="com.ligang.domain.Order"/>

        </set>

    </class>

</hibernate-mapping>

形式2的增添为: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Test

    public void testAddCustomerInverse(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Customer customer=new Customer();

        customer.setName("校长");

        customer.setEmail("sdsd@qq.com");

        customer.setRegisteredTime(getCurrentTime());

        Set<Order> orders=new HashSet<Order>();

         

        Order order1=new Order();

        order1.setOrderNumber("第一个订单");

         

        Order order2=new Order();

        order2.setOrderNumber("第二个订单");

         

        orders.add(order1);

        orders.add(order2);

        customer.setOrders(orders);

         

        session.save(customer);

         

        tx.commit();

        session.close();

    }

会产生如下sql语句: 

?


1

2

3

4

5

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber) values (?)

Hibernate: insert into hibernate.order (orderNumber) values (?)

Hibernate: update hibernate.order set customer_id=? where id=?

Hibernate: update hibernate.order set customer_id=? where id=?

当保存完Customer对象后,由于cascade="save-update"设置,会级联的保存Customer所包含的Order集合,但是此时的Order没有保存customer_id属性,在事务提交前,才会去检查和更新Order的customer_id属性,这个关系是有Customer维护的,当Customer的映射文件中<set>标签的inverse="true"时,即Customer放弃维护这个关系时,此时就只有三个insert语句了: 

?


1

2

3

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber) values (?)

Hibernate: insert into hibernate.order (orderNumber) values (?)

此时的数据就会不完整了,所以inverse必须为true,由Customer去维护关系,此时又会造成多出两个update语句,无法消除,所以一般不采用形式2,关联关系尽量由many的一方来维护。 
对于形式2的查询,可以从Customer去查到它所包含的Order,但是从Order就查不到它所在的Customer,只能手写sql去查出customer_id的值,然后查出对应的Customer,所以更不会采用形式2了。 

说完了形式2,来说说形式3:Order拥有一个Customer引用,同时Customer拥有Order集合 
若仍想使用上述的数据库的表结构,即order表中含有一个customer_id信息。则映射文件的配置要做相应的修改。 
对于Customer类: 

?


1

2

3

4

5

6

7

8

9

public class Customer {

 

    private Long id;

    private String name;

    private String email;

    private Timestamp registeredTime;

    private List<Order> orders;

//略get、set方法

}

对于Customer的映射文件: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<?xml version="1.0" encoding="UTF-8"?>

<!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.ligang.domain.Customer" table="customer">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="name" column="name" type="string"/>

        <property name="email" column="email" type="string"/>

        <property name="registeredTime" column="registeredTime" type="timestamp"/>

        <list name="orders" cascade="save-update">

            <key column="customer_id"/>

            <list-index base="0" column="list_index"/>

            <one-to-many class="com.ligang.domain.Order"/>

        </list>

    </class>

</hibernate-mapping>

比之前多加了一个list,这里有一个无法理解的地方。对于List,是虽说是有序的,但是有时候我并不看重这个顺序,而它这里强制性加上了一个<list-index>标签或者<index>标签,不然就报错,这个标签的作用就是对于Customer的List<Order>集合从base开始编号,然后把这个编号强制到数据库order表中的column="list_index"指定的字段上,也就是说,数据库的Order表必须多余的添加一个list_index字段用来存储编号。我无法忍受这样的强制性,各种各样的需求在很多情况下都存在的,不要以为你认为有几种情况就只有几种情况。为了不需要这样的字段,我们只能使用Set,这就必须要求Customer中不是List<Order>而是Set<Order>。所以这一块,我无法理解,还希望知道的网友帮我解答这个疑问。 
对于Order没有变化和上面的一样。 
测试添加: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Test

    public void testTwoRelation(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Customer customer=new Customer();

        customer.setName("校长");

        customer.setEmail("sdsd@qq.com");

        customer.setRegisteredTime(getCurrentTime());

        List<Order> orders=new ArrayList<Order>();

         

        Order order1=new Order();

        order1.setOrderNumber("第一个订单");

         

        Order order2=new Order();

        order2.setOrderNumber("第二个订单");

         

        orders.add(order1);

        orders.add(order2);

        customer.setOrders(orders);

         

        session.save(customer);

         

        tx.commit();

        session.close();

    }

会看到如下的sql语句: 

?


1

2

3

4

5

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: update hibernate.order set customer_id=?, list_index=? where id=?

Hibernate: update hibernate.order set customer_id=?, list_index=? where id=?

为什么会有两个update语句呢? 
还是因为那个list-index标签要存储编号到order的list_index字段上。 
这里我就更该List为Set,同时还要重写Order类的equals和hashcode方法,要满足orderNumber和customer_id都一致时视为重复,为什么这样做可以看我的之前的一篇专门讲述这个事情的文章(http://lgbolgger.iteye.com/blog/2115446)。 
更该为set后又产生了一个新的问题,上述sql语句中insert into hibernate.order (orderNumber, customer_id) values (?, ?)其中customer_id是有值的,但是更该为Set后就变成null,导致后来又补上两个update语句把customer_id值补上去了。 
如下sql: 

?


1

2

3

4

5

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: update hibernate.order set customer_id=? where id=?

Hibernate: update hibernate.order set customer_id=? where id=?

越来越感觉挺简单的东西让hibernate搞的逻辑更加复杂了。这时候就需要这样做了,建立两者的双重关联,并且在配置文件中让one方放弃维护两者关系,在set标签中使用inverse="true"。即在Customer.hbm.xml中如下更该: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

<hibernate-mapping>

    <class name="com.ligang.domain.Customer" table="customer">

        <id name="id" column="id" type="long">

            <generator class="identity"/>

        </id>

        <property name="name" column="name" type="string"/>

        <property name="email" column="email" type="string"/>

        <property name="registeredTime" column="registeredTime" type="timestamp"/>

        <set name="orders" cascade="save-update" inverse="true">

            <key column="customer_id"/>

            <one-to-many class="com.ligang.domain.Order"/>

        </set>

    </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

23

24

25

26

27

28

@Test

    public void testTwoRelation(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Customer customer=new Customer();

        customer.setName("校长");

        customer.setEmail("sdsd@qq.com");

        customer.setRegisteredTime(getCurrentTime());

        Set<Order> orders=new HashSet<Order>();

         

        Order order1=new Order();

        order1.setCustomer(customer);

        order1.setOrderNumber("第一个订单");

         

        Order order2=new Order();

        order2.setCustomer(customer);

        order2.setOrderNumber("第二个订单");

         

        orders.add(order1);

        orders.add(order2);

        customer.setOrders(orders);

         

        session.save(customer);

         

        tx.commit();

        session.close();

    }

此时,一切就正常了,只有三个insert语句,如下: 

?


1

2

3

Hibernate: insert into hibernate.customer (name, email, registeredTime) values (?, ?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

Hibernate: insert into hibernate.order (orderNumber, customer_id) values (?, ?)

对于形式3的查询,从Customer查Order: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

@Test

    public void testGetTwoRelation(){

        Session session=hibernateDao.getSession();

        Transaction tx=session.beginTransaction();

         

        Customer customer=(Customer)session.get(Customer.class,48L);

        System.out.println(customer.getName());

        for(Order order:customer.getOrders()){

            System.out.println(order.getOrderNumber());

        }

        tx.commit();

        session.close();

    }

此时也可以通过设定Customer.hbm.xml中set标签的lazy="false",来提前查询Order,默认是用到Order时才会去查询。 
从Order到Customer的查询也是类似的。 

时间: 2024-11-08 22:34:16

hibernate系列(二)一对多的关联关系的相关文章

hibernate系列(四)一对一关联关系

以Person类和IDCard类为例,这里仅仅说一种一对一关联关系,即Person类拥有IDCard,但是IDCard不含Person类,数据库库的表如下:  ? 1 2 3 4 5 6 CREATE TABLE `hibernate`.`person` (   `id` INT NOT NULL AUTO_INCREMENT,   `name` VARCHAR(45) NULL,   `age` INT NULL,   `idcard_id` INT NULL,   PRIMARY KEY (

hibernate两个表之间的关联关系,主键和外键的类型不同

问题描述 hibernate两个表之间的关联关系,主键和外键的类型不同 现在有两个表,表a和b两个表,a里面的主键为aid,b的主键为bid,b中有a的外键aid,现在aid的类型为int类型的,b表中的aid为string类型的,如何建立关联关系才能够解决当前报错的问题呢?报错信息如下:Wrong column type in b for column aid. Found: varchar expected: integer这个错误的意思是说,在b表中找到了varchar类型的aid,但是我

WorldWind系列二:擒贼先擒王篇1

有了WorldWind系列一的基础,我们已经可以进行正常调试运行啦!可以先操作看看软件的功能吧,这样我们才可以知道WorldWind有哪些功能等待我们学习的. 开始我们的"WorldWind系列二:擒贼先擒王"分析WorldWind主窗体,从Main函数入口一步步解析学习.至少对于我来说,里面有很多知识要学的.(补充一下:无法进入WorldWind.cs窗体的设计界面,这个问题我早就发现了,但没解决,我们根据功能直接看代码吧) 1.使用System.Version在内部,读取软件版本信

iOS开发UINavigation系列二——UINavigationItem

iOS开发UINavigation系列二--UINavigationItem 一.引言         UINavigationItem是导航栏上用于管理导航项的类,在上一篇博客中,我们知道导航栏是通过push与pop的堆栈操作来对item进行管理的,同样,每一个Item自身也有许多属性可供我们进行自定制.这篇博客,主要讨论UINavigationItem的使用方法. UINavigationBar:http://my.oschina.net/u/2340880/blog/527706. 二.来

VSTO之旅系列(二):创建Excel解决方案

原文:VSTO之旅系列(二):创建Excel解决方案   本专题概要 引言 创建VSTO项目 Excel对象模型 创建Excel外接程序 创建Excel文档级自定义项 小结   一.引言 也许很多朋友都没有听说过VSTO这个东西的,本人之前也同样也不知道的,但是由于工作的原因接触了这方面,由于VSTO方面国内的资料比较少,本人刚开始学习的时候都是参考MSDN的,但是上面很多资料都是英文的,可能学习起来会比较慢点,所以本人把最近一段时间学习的内容记录下来,一来是作为一个巩固的学习笔记,二来希望这些

Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能

Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSDK分享组件的,奈何需要去注册各平台的账号,还要审核,有些审核还挺久,就没办法,改为写这个Bmob了,相信大家对Bmob都是挺期待的吧,因为他作为Android后端的实现很好的支持,国内很多软件都在使用它,他的功能也是特别神奇,这里就不一一细说了,我们用实际的例子来见证他的神奇 官网:http://w

【spring】一般情况下,我们一般建议在一对多双向关联关系中,将一方的inverse属性设置为true

一般情况下,我们一般建议在一对多双向关联关系中,将一方的inverse属性设置为true

iOS中CoreData数据管理系列二——CoreData框架中三个重要的类

iOS中CoreData数据管理系列二--CoreData框架中三个重要的类 一.引言     在上一篇博客中,介绍了iOS中使用CoreData框架设计数据模型的相关步骤.CoreData框架中通过相关的类将数据--数据模型--开发者无缝的衔接起来.NSManagedObjectModel对应数据模型,即上篇博客中我们创建的.xcdatamodeld文件:NSPersistentStoreCoordinator相当于数据库与数据模型之间的桥接器,通过NSPersistentStoreCoord

系列二VS项目软件配置工具介绍

原文:系列二VS项目软件配置工具介绍 Svn和VisualSvn介绍      在使用TortoiseSvn(SVN客户端)+ AnkhSvn(VS2008插件) +VisualSvn Server(版本控制服务器)进行源代码版本控制前,有必要先了解下Subversion(Svn).     Svn(Subversion)是近年来崛起的版本管理工具,是CVS的接班人.目前,绝大多数开源软件都使用Svn作为代码版本管理软件.      Svn客户端: Subversion的客户端有两类,一类是we