Hibernate关联映射-----一对多关联和多对一关联

来源:互联网 发布:淘宝收货货款冻结15天 编辑:程序博客网 时间:2024/05/16 15:01

一对多关联和多对一关联在实际应用中式非常普遍的。例如一个会员(Member)可以有多个订单(Order),而每个订单只能属于某个特定的会员,这便是一个典型的一对多关联。

本示例要用到的两个POJO类如下:

public class Member {
    private String id;
    private String name;
    private Integer age;
    private Set<Order> orders = new HashSet<Order>();

    ......

}

public class Order {
    private Integer id;
    private String name;
    private String num;
    private String memberId;

    ......

}

 

会员(Member)拥有多个订单(Order),而每个订单只能属于某个特定的会员。

首先来演示和学习一对多的单向关联。

两个人POJO的映射文件如下:

Member.hbm.xml:

<hibernate-mapping package="org.louis.domain">
    <class name="Member" table="TEST_MEMBER">
        <id name="id" column="ID">
            <generator class="uuid.hex"></generator>
        </id>
        <property name="age" column="AGE"></property>
        <property name="name" column="NAME"></property>

        <!--set元素,就是定义一个集合,它的name属性值是对应的POJO中的相关属性名称-->
        <set name="orders" cascade="all">
            <key column="MEMBER_ID"></key><!--指定“
多”的一段的外键,与“一”端得主键相关联-->
            <one-to-many class="Order"/><!--指定了“多”端对应的类-->
        </set>
    </class>
</hibernate-mapping>

Order.hbm.xml:

<hibernate-mapping package="org.louis.domain">
    <class name="Order" table="TEST_ORDER">
        <id name="id" column="ID">
            <generator class="native"></generator>
        </id>
       
        <property name="name" column="NAME"></property>
        <property name="num" column="NUM"></property>
        <!--外键-->
        <property name="memberId" column="MEMBER_ID"></property>


</hibernate-mapping>

在Member.hbm.xml中主要的是它的<set>元素,它定义了Order作为它的一个集合属性。而Order.hbm.xml则和普通的映射文件没有什么不同。

下面进行测试:

a、插入数据

    public void insert() {
        Session session = HibernateSessionFactory.getSessionFactory()
                .getCurrentSession();
        session.beginTransaction();
        Member m = new Member();
        m.setAge(24);
        m.setName("Louis");

        Order order = new Order();
        order.setName("order 1");
        order.setNum("order num 1");
       
        m.getOrders().add(order);
       
        session.save(m);
        session.getTransaction().commit();
    }

查看Hibernate在后台执行的SQL语句如下:

Hibernate:
    insert
    into
        TEST_MEMBER
        (AGE, NAME, ID)
    values
        (?, ?, ?)
Hibernate:
    insert
    into
        TEST_ORDER
        (NAME, NUM, MEMBER_ID)
    values
        (?, ?, ?)
Hibernate:
    update
        TEST_ORDER
    set
        MEMBER_ID=?
    where
        ID=?

总共执行了3条Sql语句,前两条是进行数据的插入,第三条将Order的外键更新为Member的主键值。因为关联关系时单向的,关联关联关系由Member来维护,而“多”的一方并不知道它自己和Member有任何关系。在这个例子中,Hibernate首先向数据库插入Member的数据,然后插入Order的数据(这时候Order的外键为NULL,如果设置其外键不允许为NULL的话就会报错),最后Hibernate会发送一条更新Order外键的Sql语句,其值由Member的主键值而来。由此看出采用这种单向关联存在着很大的问题,包括性能、是否为空等问题。

b、加载数据

public void getMemberById(String id) {
        Session session = HibernateSessionFactory.getSessionFactory()
                .getCurrentSession();
        session.beginTransaction();
        Member m = (Member)session.load(Member.class, id);
        System.out.println(m+"/n"+m.getOrders().size());
        session.getTransaction().commit();
    }

查看后台的SQL语句:

Hibernate:
    select
        member0_.ID as ID5_1_,
        member0_.AGE as AGE5_1_,
        member0_.NAME as NAME5_1_,
        idcard1_.ID as ID4_0_,
        idcard1_.NUM as NUM4_0_,
        idcard1_.MEMBER_ID as MEMBER3_4_0_
    from
        TEST_MEMBER member0_
    left outer join
        TEST_IDCARD idcard1_
            on member0_.ID=idcard1_.MEMBER_ID
    where
        member0_.ID=?
Hibernate:
    select
        orders0_.MEMBER_ID as MEMBER4_1_,
        orders0_.ID as ID1_,
        orders0_.ID as ID6_0_,
        orders0_.NAME as NAME6_0_,
        orders0_.NUM as NUM6_0_,
        orders0_.MEMBER_ID as MEMBER4_6_0_
    from
        TEST_ORDER orders0_
    where
        orders0_.MEMBER_ID=?

这里有点要注意:m.getOrders()在这里必须要在加载Member对象的Session范围内,否则得话就会出错,因为Session已经关闭。

c、删除数据

public void delete(String id) {
        Session session = HibernateSessionFactory.getSessionFactory()
                .getCurrentSession();
        session.beginTransaction();
        Member m = (Member) session.load(Member.class, id);
        session.delete(m);
        session.getTransaction().commit();
    }

查看Hibernate后台SQL语句如下:

Hibernate:
    select
        member0_.ID as ID5_1_,
        member0_.AGE as AGE5_1_,
        member0_.NAME as NAME5_1_,
        idcard1_.ID as ID4_0_,
        idcard1_.NUM as NUM4_0_,
        idcard1_.MEMBER_ID as MEMBER3_4_0_
    from
        TEST_MEMBER member0_
    left outer join
        TEST_IDCARD idcard1_
            on member0_.ID=idcard1_.MEMBER_ID
    where
        member0_.ID=?
Hibernate:
    select
        orders0_.MEMBER_ID as MEMBER4_1_,
        orders0_.ID as ID1_,
        orders0_.ID as ID6_0_,
        orders0_.NAME as NAME6_0_,
        orders0_.NUM as NUM6_0_,
        orders0_.MEMBER_ID as MEMBER4_6_0_
    from
        TEST_ORDER orders0_
    where
        orders0_.MEMBER_ID=?
Hibernate:
    update
        TEST_ORDER
    set
        MEMBER_ID=null
    where
        MEMBER_ID=?

Hibernate:
    delete
    from
        TEST_ORDER
    where
        ID=?
Hibernate:
    delete
    from
        TEST_MEMBER
    where
        ID=?

因为我根据一个Member的主键来加载到它的一个对象实例,然后将其删除,所以Hibernate首先加载Member对象及其Order集合;因为要删除Order,所以又更新了Order的外键为NULL,最后删除Order和Member。可见删除一个会员会执行大量的SQL语句,对性能的影响可想而知,并且如果Order的外键不允许为NULL,那么在更新Order时就会报错,这些都是要考虑的。而理想的情况应该是这样的:首先取出Member和其相关的集合,让后逐个删除与Member关联的对象,最后删除Member,比起上面的少了大量的更新语句,显然性能上要有所提高。

为了能够达到上述理想情况,使用这种单向关联是不行的,那就要通过双向关联来实现。

注:如果Member有上千个订单的话就要一条一条的删除订单,性能上也会受影响,幸好,Hibernate提高了一个系统属性可以设置成用来进行批量更新。hibernate.jdbc.batch_size,Hibernate官方建议取值在5和30之间。只有当要执行的SQL语句到指定的数目后,Hibernate才将其提交执行,这样减少了与数据交互的次数,从而提高性能。

 

关与双向关联请看下章的介绍。

原创粉丝点击