MyBatis多表联查 引出 RDB表关系映射问题

一. 关系型数据库表关系回顾:

1. 一对一: 丈夫表和妻子表是典型的一对一关系;

    RDB中的实现方式: 分别创建丈夫表和妻子表, 将对方表主键设为外键, 因为主键的唯一性, 保证了一对一关系;

2. 一对多: 公司和员工一般是一对多关系; (注意: 其实一对多表反过来看是一对一关系, 即一个员工只属于一个公司)

    RDB中的实现方式: 分别创建公司.表和员工表, 将公司表主键设置为员工表外键;

3. 多对多: 订单和产品是典型的多对多关系;

 

    RDB中实现方式: 因为RDB中表关系只能靠外键实现, 实际单一外键只能实现一对多关系, 故需要创建中间表, 以外键的 "乘积" 来

实现多对多的关系, 如下图:

其中中间表取两表主键为联合主键, 并分别作为关联两表的外键; 

 

扫描二维码关注公众号,回复: 4245274 查看本文章

二. 原生MyBatis中的查表和结果封装:

1. 查询: 查询时, 查询参数必须封装为单个参数, 可以是基本数据类型也可以是POJO/ 数组/集合等 (不包括Map);

2. 返回值: 查询的单条结果只能封装到单个对象中, 当多条数据时自动作风扎UN个到List中, 故无论需要什么数据, 查询后单条结果都要进行封装;

 

三. MyBatis不同表关系的查询方法:

    在Java中, 我们将每张表的字段作为 POJO 的成员属性, 创建不同的POJO, 此时可以体现:

 

1. 一对一关系查询: 查询某男人及其妻子的全部信息:

POJO: Husband类 Wife类

需求引出问题: 查询双方所有信息, 需要进行二表联查, 查询结果无法直接封装在Hunband或者Wife对象中;

问题解决: 

    方式1: 新建类Hunband_Wife, 封装二表联查后的所有属性, 此类并不具有实际意义, 仅为封装数据而存在, 违背OO思想, 不推荐;

    方式2: 实际妻子表的主键id值在丈夫表中仅是一个映射 ( 理解为操作系统桌面快捷方式, 实际文件并未放到桌面, 当我们找到桌面对应文件夹时实际其中并不直接包含相应文件) , 我们需要将真实的Wife对象封装到Hunband对象中, 故在Hunband类中新增Wife实体作为成员属性 (同样,Wife类中新增Hunband实体作为成员属性), 如下图:  

查询结果的多层封装需要用到MyBatis映射文件中的 resultMap 标签中的 association 子标签, 该标签可以将多表查询的一部分封装成实体成员属性对象 (association子标签可以有多个,即类中可以定义多个实体成员属性 ), 用法如下:

	<resultMap id="mapOfFindAll" type="com.wen.domain.Hunband">
		<id property="hid" column="hid"></id>
		<result property="hname" column="hname"></result>
		<result property="wid" column="wid"></result>
		<association property="wife" javaType="com.wen.domain.Wife">
			<id property="wid" column="wid"></id>
			<result property="wname" column="wname"></result>
			<result property="hid" column="hid"></result>
		</association>
	</resultMap>

 

2. 一对多关系查询: 查询某企业及其所有员工的信息

POJO: Company类, Staff类

需求引出问题: 查询该公司的所有员工,属于一对多关系,无法直接封装在实体类中, 也无法直接定义实体成员属性;

问题解决: 此时应该定义一个集合作为Company成员属性, 集合泛型为Staff, 即实现一个公司对应多个员工的目的:

但是,数据库二表联查结果表是二维表结构, 数据条数等于员工数, 需要对公司信息进行去重, 并将所有员工信息封装后放入List, 此时用到MyBatis映射文件中的 resultMap 标签中的 collection 子标签, 用法如下:

	<resultMap id="mapOfFindAll" type="com.wen.domain.Company">
		<id property="cid" column="cid"></id>
		<result property="cname" column="cname"></result>
		<result property="caddress" column="caddress"></result>
		<collection property="staffList" ofType="list" javaType="com.wen.domain.Staff">
			<id property="sid" column="sid"></id>
			<result property="sname" column="sname"></result>
		</collection>
	</resultMap>

 

3. 多对多关系查询: 分为两类:

一类是中间表无联合主键之外的字段信息(如老师和学生), 一类是中间表还有联合主键之外的字段信息 (如: 用户和游戏角色,此时中间表会存在一个角色等级的信息    又如: 订单和产品,此时中间表会存在订单中某一产品的数量信息);

 

A. 中间表无其他字段 (老师和班级):

    情况分析: 该种场景下, 一般不需要以中间表作为数据源查询关联信息, 故使用情景可直接分为两个一对多查询, 只不过将sql语句从二表联查增加到了三表联查;

    此时实体属性不通过中间表对象进行关联, 如下:

    表关系:

    实体定义:

    查询某个老师的所教授班级时: MyBatis的resultMap定义:

	<resultMap id="mapOfFindClass" type="com.wen.domain.Teacher">
		<id property="tid" column="tid"></id>
		<result property="tname" column="tname"></result>
		<collection property="classList" ofType="list" javaType="com.wen.domain.Class">
			<id property="cid" column="cid"></id>
			<result property="cname" column="cname"></result>
		</collection>
	</resultMap>

但是,该种方法限制了中间表字段的可扩展性, 比如: 现在学校举办活动, 将每个老师带的每个班效果做评比打分吗此时便要增加中间表字段: 分数;

 

B. 中间表有其它字段 (玩家和游戏角色):

    情况分析: 该种情况下, 可能需要以中间表作为基础进行查询, 比如游戏角色等级排行榜的实现;

    此时玩家和游戏角色的类通过中间表对象进行关联, 如下:

    表关系:

    定义实体:

    查询某一等级的所有用户所持游戏角色信息: MyBatis的resultMap定义:

	<resultMap id="mapOfAllP_R" type="com.wen.domain.playRole">
		<result property="level" column="level"></result>
		<association property="role" javaType="com.wen.domain.Role">
			<id property="rid" column="rid"></id>
			<result property="rname" column="rname"></result>
		</association>
		<association property="player" javaType="com.wen.domain.Player">
			<id property="pid" column="pid"></id>
			<result property="pname" column="pname"></result>
		</association>
	</resultMap>

同理: 因为两张表和中间表对应POJO都定义了相应实体集合和实体成员属性, 故: 通过resultMap联合多表查询语句可以查询任何相关信息;


RDB表关系映射问题:

1. 基于有外键的表到相应关联表的查询实际是一对一关系, 所以为了体现RDB中的外键, 需要在基本表的类中定义外键所在表对应的实体成员属性;

2.基于被外键关联表的查询, 实际是一对多关系, 即一个主键可以被多次引用为外键, 故需在基本表的类中定义集合, 泛型为外键所在的表对应实体类;

3.基于多表中间表的查询, 是实际意义上的多对多查询, 此时在中间表的类中定义相应两个关联表的实体成员属性即可;

 

所有支持面向对象的语言中, 包括以面向对象思维的数据传递语言(如JSON), 对象关系的维系靠的是树杈结构, 每一个实体成员属性(或者集合)都是一个节点, 如下图:

但是在RDB中, 对象关系(表关系)是以 "二维表加外键连接" 的方式实现的:

故: 通过上述分析和论述, 想实现以上两种数据模型的完整关系映射, 达到只要有关系就可以封装进一个对象的目的, 遵循以下原则即可:

1. 当对象id被作为某表外键时, 需要定义一个某表对应实体为泛型的集合作为成员属性;

2. 当引用了某表的id作为外键时, 需要定义一个某表对应类作为实体成员属性;

3. 第2条优先于第1条, 即某字段同时为主键和外键时, 仅定义实体成员属性; 

前提: RDB表的设计遵循了三范式, 每张表对应一个实体类, 再根据表关系创建相应的实体或集合成员属性;


转载请标明出处: 划船一哥

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_42711325/article/details/83422053
今日推荐