Hibernate(三)

本文详细介绍了Hibernate中多表设计的关系划分,包括一对一、一对多、多对多的关系实现,以及如何在数据库中创建这些关系。进一步讲解了一对多和多对多关系的映射配置步骤,涉及实体类和映射文件的设置。同时,文章涵盖了多表的增删改操作和对象导航查询,提供具体的操作示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 多表设计

1.1 表之间的关系划分

  • 一对一
  • 一对多(多对一)
  • 多对多

2. 如何确立和实现数据库中的表关系

2.1 一对多的表关系在数据库中如何实现?

使用外键约束。我们一般习惯把一的方称为主表,把多的一方称为从表。

什么是外键:

从表中有一列,该列的取值除了null之外,只能来源于主表的主键。默认情况下,外键字段的值是可以重复的。

2.2 多对多的表关系在数据库中如何实现?

使用中间表。中间表中只有两个外键,引用两个多对多表的主键。不能有其他字段信息,至于中间表的主键,应该采用联合主键。

任何一个多方的表和中间表去比较,都是一对多的关系。

2.3 一对一的表关系在数据库中如何实现?

有两种:

  • 第一种:建立外键的方式:

    使用外键约束,唯一约束,非空约束。它是把外键字段加了非空和唯一约束。从而实现了一对一。

  • 第二种:使用主键的方式:

    让其中一张表既是主键,又是外键。

2.4 如何确立两张表之间的关系:

找外键。

3. 学习多表映射配置要遵循的步骤

  • 第一步:确定两张表之间的关系
  • 第二步:在数据库中实现两张表之间的关系建立
  • 第三步:在实体类中描述出两个实体之间的关系
  • 第四步:在映射配置文件中建立两个实体和两张表之间的关系

4. 一对多关系映射配置

示例:客户和联系人两张表

4.1 确定两张表之间的关系

一个客户可以包含多个联系人,多个联系人可以属于同一个客户。所以:客户和联系人之间的关系是一对多。

4.2 在数据库中实现两张表之间的关系建立

实现一对多的关系,靠外键。客户表是主表,联系人表是从表。我们需要在联系人表中添加外键。

img

4.3 在实体类中描述出两个实体之间的关系

主表的实体类应该包含从表实体类的集合引用,从表的实体类应该包含主表实体类的对象引用

/**
 * 客户实体类
 *
 * @author wgy
 */
public class Customer implements Serializable {
    private Long custId;
    private String custName;
    private String custSource;
    private String custIndustry;
    private String custLevel;
    private String custAddress;
    private String custPhone;

    /**
     * 一对多关系映射:一的一方
     * 主表实体应该包含从表实体的集合引用
     */
    private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
    ...
}
/**
 * 联系人的实体类
 *
 * @author wgy
 */
public class LinkMan implements Serializable {

    private Long lkmId;
    private String lkmName;
    private String lkmGender;
    private String lkmPhone;
    private String lkmMobile;
    private String lkmEmail;
    private String lkmPosition;
    private String lkmMemo;

    /**
     * 一对多关系映射,多的一方。
     * 从表实体包含主表实体的对象引用
     */
    private Customer customer;
    ...
}

4.4 在映射配置文件中建立两个实体和两张表之间的关系

客户配置文件:

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

<!-- 在实体类所在的包下,创建一个xml文件。该文件建议名称为:实体类名称+.hbm+.xml导入约束:dtd约束 -->
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.wgy.domain">
    <class name="Customer" table="cst_customer" lazy="true">
        <id name="custId" column="cust_id">
            <generator class="identity"/>
        </id>
        <property name="custName" column="cust_name"/>
        <property name="custSource" column="cust_source"/>
        <property name="custIndustry" column="cust_industry"/>
        <property name="custLevel" column="cust_level"/>
        <property name="custAddress" column="cust_address"/>
        <property name="custPhone" column="cust_phone"/>
        <!-- 一对多关系映射:主表实体的映射配置
            涉及的标签:
                set:
                    作用:用于配置set集合属性。
                    属性:
                      name:指定实体类中set集合的属性名称。
                      table:指定从表的名称。在一对多配置时可以不写。
                key:
                    作用:用于映射外键字段。
                    属性:
                      column:指定外键字段名称
                one-to-many:
                    作用:用于建立一对多的映射配置
                    属性:
                      class:用于指定从表实体的名称
        -->
        <set name="linkmans" table="cst_linkman">
            <key column="lkm_cust_id"/>
            <one-to-many class="LinkMan"/>
        </set>
    </class>
</hibernate-mapping>

联系人配置文件:

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

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.wgy.domain">
    <class name="LinkMan" table="cst_linkman">
        <id name="lkmId" column="lkm_id">
            <generator class="identity"/>
        </id>
        <property name="lkmName" column="lkm_name"/>
        <property name="lkmGender" column="lkm_gender"/>
        <property name="lkmPhone" column="lkm_phone"/>
        <property name="lkmMobile" column="lkm_mobile"/>
        <property name="lkmEmail" column="lkm_email"/>
        <property name="lkmPosition" column="lkm_position"/>
        <property name="lkmMemo" column="lkm_memo"/>
        <!-- 一对多关系映射:从表实体的映射配置
                涉及的标签:
                    many-to-one:
                        作用:建立多对一的映射配置
                        属性:
                          name:从表实体中引用主表实体对象引用的名称
                          class:指定属性所对应的实体类名称
                          column:指定从表中外键字段的名称
        -->
        <many-to-one name="customer" class="Customer" column="lkm_cust_id"/>
    </class>
</hibernate-mapping>

5. 多对多关系映射配置

示例:用户和角色

5.1 确定两张表之间的关系

一个用户可以有多个角色,一个角色可以赋给多个用户,所以用户和角色之间是多对多。

5.2 在数据库中实现两张表之间的关系建立

在数据库中实现多对多要靠中间表。中间表中只能出现用户和角色主键。

img

5.3 在实体类中描述出两个实体之间的关系

各自包含对方一个集合引用

/**
 * 用户的实体类
 *
 * @author wgy
 */
public class SysUser implements Serializable {

    private Long userId;
    private String userName;
    private String userPassword;
    private Integer userState;

    //多对多关系映射:一个用户可以具备多个角色
    private Set<SysRole> roles = new HashSet<SysRole>(0);
    ...
}
/**
 * 角色的实体类
 *
 * @author wgy
 */
public class SysRole implements Serializable {

    private Long roleId;
    private String roleName;
    private String roleMemo;

    //多对多关系映射:一个角色可以赋予多个用户
    private Set<SysUser> users = new HashSet<SysUser>(0);
    ...
}

5.4 在映射配置文件中建立两个实体和两张表之间的关系

用户配置文件:

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

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.wgy.domain">
    <class name="SysUser" table="sys_user">
        <id name="userId" column="user_id">
            <generator class="identity"></generator>
        </id>
        <property name="userName" column="user_name"></property>
        <property name="userPassword" column="user_password"></property>
        <property name="userState" column="user_state"></property>
        <!-- 多对多关系映射
                涉及的标签:
                    set:
                        作用:用于映射set集合属性
                        属性:
                            name:指定集合的名称
                            table:指定的是中间表的名称
                    key:
                        作用:用于映射外键字段
                        属性:
                            column:指定的是当前实体在中间表的外键字段名称
                    many-to-many
                        作用:用于映射多对多的关系
                        属性:
                            class:对方的实体类名称
                            column:对方在中间表的外键字段名称
         -->
        <set name="roles" table="user_role_ref">
            <key column="user_id"></key>
            <many-to-many class="SysRole" column="role_id"/>
        </set>
    </class>
</hibernate-mapping>

角色配置文件:

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

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.wgy.domain">
    <class name="SysRole" table="sys_role">
        <id name="roleId" column="role_id">
            <generator class="identity"></generator>
        </id>
        <property name="roleName" column="role_name"></property>
        <property name="roleMemo" column="role_memo"></property>
        <!-- 多对多关系映射 -->
        <set name="users" table="user_role_ref" inverse="true">
            <key column="role_id"></key>
            <many-to-many class="SysUser" column="user_id"/>
        </set>
    </class>
</hibernate-mapping>

6. 多表增删改操作

6.1 一对多关系的操作

6.1.1 保存操作

6.1.1.1 正常保存
/**
 * 保存操作
 * 正常的保存:创建一个新的联系人,需要关联一个客户
 */
@Test
public void test1() {
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    //1.查询一个客户
    Customer c1 = s.get(Customer.class, 1L);
    //2.创建一个新的联系人
    LinkMan l = new LinkMan();
    l.setLkmName("一对多的联系人");
    //3.建立客户和联系人的关联关系(让联系人知道属于哪个客户即可)
    l.setCustomer(c1);
    //4.保存联系人
    s.save(l);
    tx.commit();
}
6.1.1.2 特殊情况
/**
 * 特殊的情况:
 * 	 创建一个客户和一个联系人
 * 	 建立联系人和客户的双向关联关系
 *   使用符合原则的保存
 *   	原则是:先保存主表实体,再保存从表实体
 *
 *  此时保存会有问题:
 *  	我们保存两个实体,应该只有两条insert语句。
 *  	而执行结果却是多了一条update的语句。
 *
 *  解决办法:
 *  	让客户在执行操作的时候,放弃维护关联关系的权利。
 *  	配置的方式:
 *  		在Customer的映射配置文件中的set标签上使用inverse属性。
 *  		inverse含义:是否放弃维护关联关系的权利
 *  			true:放弃
 *  			false:不放弃(默认值)
 *
 */
@Test
public void test2(){
    //1.创建一个客户
    //瞬时态
    Customer c1 = new Customer();
    c1.setCustName("一对多的客户_4");
    //2.创建一个新的联系人
    //瞬时态
    LinkMan l = new LinkMan();
    l.setLkmName("一对多的联系人_4");
    //3.建立客户和联系人的关联关系(双向)
    l.setCustomer(c1);
    c1.getLinkmans().add(l);

    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();

    //4.保存,要符合原则
    //持久态  有一级缓存和快照
    s.save(c1);
    //持久态    有一级缓存和快照
    s.save(l);
    tx.commit();
}
<set name="linkmans" table="cst_linkman" inverse="true">
    <key column="lkm_cust_id"/>
    <one-to-many class="LinkMan"/>
</set>
6.1.1.3 级联保存

级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。

保存客户

/**
 * 保存操作:
 *  级联保存
 *  使用级联保存,配置的方式,仍然是找到Customer的映射配置文件的Set标签,
 *  也可以配置在many-to-one上。
 *  在上面加入cascade属性
 *  	cascade:配置级联操作
 *  		级联保存更新的取值:save-update
 */
@Test
public void test3(){
    //1.创建一个客户
    //瞬时态
    Customer c1 = new Customer();
    c1.setCustName("一对多的客户_5");
    //2.创建一个新的联系人
    //瞬时态
    LinkMan l = new LinkMan();
    l.setLkmName("一对多的联系人_5");
    //3.建立客户和联系人的关联关系(双向)
    l.setCustomer(c1);
    c1.getLinkmans().add(l);

    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();

    //4.保存,要符合原则
    s.save(c1);
    tx.commit();
}
<set name="linkmans" table="cst_linkman" inverse="true" cascade="save-update">
    <key column="lkm_cust_id"/>
    <one-to-many class="LinkMan"/>
</set>

保存联系人

@Test
public void test4(){
    //1.创建一个客户
    //瞬时态
    Customer c1 = new Customer();
    c1.setCustName("一对多的客户_6");
    //2.创建一个新的联系人
    //瞬时态
    LinkMan l = new LinkMan();
    l.setLkmName("一对多的联系人_6");
    //3.建立客户和联系人的关联关系(双向)
    l.setCustomer(c1);
    c1.getLinkmans().add(l);

    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();

    //4.保存,要符合原则
    s.save(l);
    tx.commit();
}
<many-to-one name="customer" class="Customer" column="lkm_cust_id" cascade="save-update"/>

6.1.2 更新操作

/**
 * 更新操作
 *  需求:
 *      创建一个新的联系人,查询一个已有客户
 *      联系人新联系人和已有客户的双向关联关系
 *  更新客户
 */
@Test
public void test5(){
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    //1.查询一个客户
    Customer c1 = s.get(Customer.class, 1L);
    //2.创建一个新的联系人
    //瞬时态
    LinkMan l = new LinkMan();
    l.setLkmName("一对多的联系人");
    //3.建立客户和联系人的关联关系(双向)
    l.setCustomer(c1);
    c1.getLinkmans().add(l);
    //4.更新客户
    s.update(c1);
    tx.commit();
}

6.1.3 删除操作

/**
 * 删除操作
 *      删除从表数据就是单表
 *      删除主表数据:
 * 	    看有没有从表数据引用
 * 	    有引用:
 * 	        在删除是,hibernate会把从表中的外键字段置为null,然后再删除主表数据。
 * 		如果外键字段有非空约束,则hibernate不能更新外键字段为null,会报错。
 * 		如果仍然想删除,此时需要使用级联删除。同时必须配置inverse属性是true。
 * 	    没有引用: 就是单表,直接删
 */
@Test
public void test6(){
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    //1.查询一个客户
    Customer c9 = s.get(Customer.class, 5L);
    //2.删除id为5的客户
    s.delete(c9);
    tx.commit();
}
<set name="linkmans" table="cst_linkman" inverse="true" cascade="save-update,delete">
    <key column="lkm_cust_id"/>
    <one-to-many class="LinkMan"/>
</set>

6.2 多对多关系的操作

6.2.1 保存操作

/**
 * 保存操作
 *  需求:
 *      创建2个用户和3个角色
 *      让1号用户具备1号和2号角色
 *      让2号用户具备2号和3号角色
 *      保存用户和角色
 */
@Test
public void test1(){
    SysUser u1 = new SysUser();
    u1.setUserName("用户1");
    SysUser u2 = new SysUser();
    u2.setUserName("用户2");

    SysRole r1 = new SysRole();
    r1.setRoleName("角色1");
    SysRole r2 = new SysRole();
    r2.setRoleName("角色2");
    SysRole r3 = new SysRole();
    r3.setRoleName("角色3");

    //建立双向关联关系
    //先建立用户的
    u1.getRoles().add(r1);
    u1.getRoles().add(r2);
    u2.getRoles().add(r2);
    u2.getRoles().add(r3);
    //再建立角色
    r1.getUsers().add(u1);
    r2.getUsers().add(u1);
    r2.getUsers().add(u2);
    r3.getUsers().add(u2);

    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    s.save(u1);
    s.save(u2);
    s.save(r1);
    s.save(r2);
    s.save(r3);
    tx.commit();
}

6.2.2 删除操作

/**
 * 删除操作
 * 实际开发中:多对多的双向级联删除是禁止使用的
 */
@Test
public void test2(){

    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    //1查询id为3的用户
    SysUser u3 = s.get(SysUser.class, 3L);
    //删除
    s.delete(u3);
    tx.commit();

}

7. Hibernate中的多表查询

7.1 对象导航查询

7.1.1 概述

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。

例如:我们通过OID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。

对象导航查询的使用要求是:两个对象之间必须存在关联关系。

7.1.2 对象导航检索示例

查询联系人

/**
 * 查询id为1的客户下所属联系人
 * 一对多时,根据一的一方查询多的一方时,需要使用延迟加载。(默认配置即可)
 */
@Test
public void test1() {
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    //查询id为1的客户
    Customer c = s.get(Customer.class, 1L);
    System.out.println(c);
    Set<LinkMan> linkmans = c.getLinkmans();
    System.out.println(linkmans);
    tx.commit();
}

查询客户

/**
 * 查询id为5的联系人属于哪个客户
 * 多对一时,根据多的一方查询一的一方时,不需要使用延迟加载,而是使用立即加载,需要配置一下
 * 需要找到联系人的映射配置文件:在many-to-one标签上使用lazy属性。
 *      取值有:
 *          false:使用立即加载。
 *          proxy:是看load方法是延迟加载还是立即加载
 *          no-proxy:不管
 */
@Test
public void test2(){
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    LinkMan l = s.get(LinkMan.class, 5L);
    System.out.println(l);
    Customer c = l.getCustomer();
    System.out.println(c);
    tx.commit();
}
<many-to-one name="customer" class="Customer" column="lkm_cust_id" cascade="save-update" lazy="false"/>

load方法加载

/**
 * 关于load方法改为立即加载的方式
 *      找到查询实体的映射配置文件,它的class标签上也有一个lazy属性。含义是:是否延迟加载
 *          true:延迟加载(默认值)
 *          false:立即加载
 */
@Test
public void test3(){
    Session s = HibernateUtil.getCurrentSession();
    Transaction tx = s.beginTransaction();
    Customer c = s.load(Customer.class, 1L);
    System.out.println(c);
    tx.commit();
}
  • class标签的lazy:它只能管load方法是否是延迟加载。
  • set标签的lazy:它管查询关联的集合对象是否是延迟加载。
  • many-to-one的lazy:它管查询关联的主表实体是否是立即加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值