1. 多表设计
1.1 表之间的关系划分
- 一对一
- 一对多(多对一)
- 多对多
2. 如何确立和实现数据库中的表关系
2.1 一对多的表关系在数据库中如何实现?
使用外键约束。我们一般习惯把一的方称为主表,把多的一方称为从表。
什么是外键:
从表中有一列,该列的取值除了null之外,只能来源于主表的主键。默认情况下,外键字段的值是可以重复的。
2.2 多对多的表关系在数据库中如何实现?
使用中间表。中间表中只有两个外键,引用两个多对多表的主键。不能有其他字段信息,至于中间表的主键,应该采用联合主键。
任何一个多方的表和中间表去比较,都是一对多的关系。
2.3 一对一的表关系在数据库中如何实现?
有两种:
-
第一种:建立外键的方式:
使用外键约束,唯一约束,非空约束。它是把外键字段加了非空和唯一约束。从而实现了一对一。
-
第二种:使用主键的方式:
让其中一张表既是主键,又是外键。
2.4 如何确立两张表之间的关系:
找外键。
3. 学习多表映射配置要遵循的步骤
- 第一步:确定两张表之间的关系
- 第二步:在数据库中实现两张表之间的关系建立
- 第三步:在实体类中描述出两个实体之间的关系
- 第四步:在映射配置文件中建立两个实体和两张表之间的关系
4. 一对多关系映射配置
示例:客户和联系人两张表
4.1 确定两张表之间的关系
一个客户可以包含多个联系人,多个联系人可以属于同一个客户。所以:客户和联系人之间的关系是一对多。
4.2 在数据库中实现两张表之间的关系建立
实现一对多的关系,靠外键。客户表是主表,联系人表是从表。我们需要在联系人表中添加外键。
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 在数据库中实现两张表之间的关系建立
在数据库中实现多对多要靠中间表。中间表中只能出现用户和角色主键。
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:它管查询关联的主表实体是否是立即加载。