前言
在前面我们已经了解了,mybatis 的基本用法,动态SQL,学会使用mybatis 来操作数据库。但这些主要操作还是针对 单表实现的。在实际的开发中,对数据库的操作,常常涉及多张表。
因此本篇博客的目标:通过mybatis 提供的关联映射,建立表与表之间的关系,实现多表的数据操作。
关联映射的概述
在关系型数据库中,表与表之间存在3 种关联映射关系,分别是 一对一,一对多/多对一,和多对多
1. 一对一(One-to-One)
一对一关系是指一个表中的每个记录与另一个表中的一个记录相关联,且这种关联是唯一的。例如:
一个学生只能有一个学生证,一个学生证只属于一个学生。
一个用户只能有一个个人资料,一个个人资料只属于一个用户。
在数据库设计中,一对一关系可以通过以下方式实现:
主键关联:将一个表的主键作为外键放在另一个表中。
联合主键:将两个表的主键合并为一个联合主键,存储在一张表中。
2. 一对多/多对一(One-to-Many/Many-to-One)
一对多关系是指一个表中的一个记录可以与另一个表中的多个记录相关联,而另一个表中的每个记录只能与第一个表中的一个记录相关联。例如:
一个部门可以有多个员工,但每个员工只能属于一个部门。
一个作者可以写多本书,但每本书只能由一个作者创作。
在数据库设计中,一对多关系通常通过外键来实现:
在“多”的一方的表中添加一个外键字段,指向“一”的一方的主键字段。
3. 多对多(Many-to-Many)
多对多关系是指一个表中的多个记录可以与另一个表中的多个记录相关联。例如:
一个学生可以选修多门课程,一门课程也可以被多个学生选修。
一个作者可以写多本书,一本书也可以由多个作者共同创作。
注意
在数据库设计中,多对多关系通常通过一个**关联表(中间表)**来实现:
关联表包含两个表的主键作为外键字段,用于建立多对多关系。
例如,对于学生和课程的多对多关系,可以创建一个选课表,包含学生ID和课程ID作为外键字段。
一对一查询
应用场景
例如 表示 一个人 只能有一个身份证,同时一个身份证也只对应一个人
重点
在学习 一对一查询 时,核心是学习使用 <association>元素 来处理 一对 对关联关系。 <association>元素 提供了一系列 属性用于维护数据表之间的关系
<association>元素 常用的属性
属性 | 说明 |
property | 用于指定映射到的实体类的属性,与表字段一一对应 |
Column | 用于指定表中的对应的字段 |
javaType | 用于指定映射到实体对象的属性 |
jdbcType | 用于指定数据表中对应的字段类型 |
fetchType | 用于指定在关联查询时是否启用延迟加载。fetchType属性 有lazy,eager两个属性值,默认为lazy(默认关联映射延迟加载) |
select | 用于指定引入嵌套查询的子SQL语句,该属性用于关联映射的前提查询 |
autoMapper | 用于指定是否自动映射 |
typeHander | 用于指定一个类型处理器 |
<association>元素 是<resultMap>元素的子元素,它有两种配置方式 :嵌套查询方式,嵌套结果方式。
嵌套查询方式,嵌套结果方式的区别
我理解 嵌套查询方式是多步走,而不是一步到位。例如 你写一个 复合sql语句【相当于sql 嵌套着其他的sql 语句】,去查表中的数据,现在是把这个sql 语句拆开,通过表之间的关系 ,一个个去查,最好得到结果。
样例
<!-- 嵌套查询方式--> <association property="card" column="card_id" javaType="fs.pojo.IdCard" select="fs.mapper.IdCardMapper.findCodeById"/>
嵌套结果方式,则是一步到位。通过使用一个复合的sql 语句【相当于sql 嵌套着其他的sql 语句】得到最终结果。
样例
<!-- 嵌套结果查询--> <association property="card" javaType="fs.pojo.IdCard" > <id property="id" column="card_id"/> <result property="code" column="CODE"/> </association>
demo(案例)
项目准备
数据库中 tb_person 表,tb_idcard 表
实体类 IdCard 类,Person 类
mybatis -config 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties">
</properties> <settings>
<!-- environment 是一个环境,里面包含一个事务管理器,一个数据源 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!-- 配置数据源信息,主要有 数据库驱动,数据库连接地址,数据库用户名,数据库密码等 -->
<property name="driver" value="${driverClass}"></property>
<property name="url" value="${jdbcUrl}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 使用mapper 标签 指定mapper映射文件-->
<mapper resource="mapper/PersonMapper.xml"/>
<mapper resource="mapper/IdCardMapper.xml"/>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/OrdersMapper.xml"/>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
问题:当输入 id=1时,查询person 人的具体信息包括个人身份证信息
PersonMapper 接口
// 根据id查询
Person findPersonById(Integer id);
Person findPersonById2(Integer id);
PersonMapper.xml 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fs.mapper.PersonMapper">
// 需要针对 数据库做的操作【查询,修改,删除,插入】
</mapper>
嵌套查询方式【多步到位】
<select id="findPersonById" parameterType="int" resultMap="card">
select * from tb_person where id = #{id}
</select>
<resultMap id="card" type="fs.pojo.Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<!-- 嵌套查询方式-->
<association property="card" column="card_id" javaType="fs.pojo.IdCard"
select="fs.mapper.IdCardMapper.findCodeById"/>
</resultMap>
嵌套查询方式 接口的findCodeById 方法和IdCardMapper.xml映射文件
- 接口的findCodeById 方法
- IdCardMapper.xml映射文件
嵌套结果方式【一步到位】
<select id="findPersonById2" parameterType="int" resultMap="card2">
select p.*, c.code from tb_person p , tb_idcard c where p.id = #{id} and p.card_id = c.id
</select>
<resultMap id="card2" type="fs.pojo.Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<!-- 嵌套结果查询-->
<association property="card" javaType="fs.pojo.IdCard" >
<id property="id" column="card_id"/>
<result property="code" column="CODE"/>
</association>
</resultMap>
一对多查询
应用场景
与一对一的关联相比,更多关联关系是一对多(或多对一)例如 一个用户 可以有多个订单,多个订单也可以归一个用户所有。
重点使用<collection>元素来处理一对多关联关系。
<collection>元素 和<association>元素的关系
1 <collection>元素 的属性 大部分与 <association> 元素 相同,但其还包含一个特殊属性--ofType
ofType 与javaType属性相对应,用于指定实体类对象中集合类属性所包含的元素类型【集合中存储的实体类对象类型】
2 与 <association> 元素 相同 ,也是<resultMap>元素的子元素,<collection> 元素 也嵌套查询和嵌套结果两者配置方式
demo(案例)
项目准备
数据库 中 tb_user用户表 ,tb_order 订单表
实体类 User 用户 类,Orders订单类
问题:当输入用户 id=1,时获得 该用户所有的订单情况
UserMapper接口
package fs.mapper;
import fs.pojo.User;
public interface UserMapper {
User findUserWithOrders(Integer id);
}
UserMapper.xml 映射文件
嵌套结果查询方式【一步到位】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fs.mapper.UserMapper">
<select id="findUserWithOrders" parameterType="int" resultMap="orders">
select u.*,o.id as orders_id,o.number from tb_user u, tb_orders o
where u.id = o.user_id and u.id = #{id}
</select>
<resultMap id="orders" type="fs.pojo.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<collection property="orders" ofType="fs.pojo.Orders">
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
</collection>
</resultMap>
</mapper>
多对多查询
应用场景
多对多查询 和一对多查询,在现实生活也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多种订单中,订单和商品就是典型的多对多的关系。
重点使用<collection>元素来处理多对多关联关系。
demo(案例)
项目准备
数据库中 tb_orders 订单表 ,tb_product 商品表
中间表 tb_ordersitem
实体类 Product用户 类,Orders订单类
问题:当输入 订单 id=1 时,查询 该订单中所有的商品信息
OrderMapper 接口
package fs.mapper;
import fs.pojo.Orders;
public interface OrdersMapper {
Orders findOrdersWithProduct(Integer id);
Orders findOrdersWithProduct1(Integer id);
}
OrderMapper 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fs.mapper.OrdersMapper">
// 各自数据库的操作
</mapper>
嵌套查询方式【多步到位】
<select id="findOrdersWithProduct1" parameterType="int" resultMap="orders">
select * from tb_orders where id=#{id};
</select>
<resultMap id="orders" type="fs.pojo.Orders">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="fs.pojo.Product" select="fs.mapper.ProductMapper.findProductById">
</collection>
</resultMap>
嵌套查询方式 接口的findProductById 方法和ProductMapper.xml映射文件
- ProductMapper 接口
- ProductMapper.xml 映射文件
嵌套结果方式【一步到位】
- 没有给 tb_product 表的 id 字段 添加别名为 pid 之前
产生问题
当两者关联的表存在相同的字段时,在执行sql 查询 后,会因为tb_order表的id 字段和tb_product 的 id字段 相同,导致 查询结果在映射到product 实体类对象时,后面的product 对象始终会把前面的product 对象覆盖掉。本来应该查询多个 product对象信息,但最好打印只有 1个【最好一个product对象信息】
运行截图
如果给 tb_product 表的 id 字段 添加别名 或者 给 tb_orders 表 的 id 字段添加别名
- 在这里我是给 tb_product 表的 id 字段 添加别名 为 pid
运行截图
- 最好确实出现了3 个product 对象的具体信息