day07-MyBatis的关联映射01
MyBatis的关联映射01
实际的开发中,对数据库的操作常常会涉及到多张表,这在面向对象中就涉及到了对象与对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好的处理对象与对象之间的关联关系。
1.关联关系概述
在关系型数据库中,多表之间存在着三种关系,分别是一对一,一对多,多对多。

-
一对一:在任意一个表中引入另外一个表的主键作为外键。
-
一对多:在多个表中都引入了某一个表的主键作为外键
-
多对多:需要用一张中间表表示多对多的关系,这张中间表引入两张表的主键作为外键。
一般来说一个对象映射一张表,因此一对一的关系就是在A类中定义B类属性,一对多的关系就是在A类中定义List< B> 的属性,多对多就是分别在A、B类中定义对方的List 属性。
2.一对一
一对一关系是一个基本的映射关系,比如Person(人)--IDCard(身份证),我们可以通过如下两种方式实现:
- 通过配置XxxMapper.xml实现1对1 [配置方式]
- 通过注解的方式实现1对1 [注解方式]
2.1配置方式
2.1.1环境搭建
配置映射文件来实现一对一的映射关系,实现级联查询,要求通过person可以获取到对应的idencard信息
关于级联查询:若表A中有一个外键引用了表B的主键,A表就是子表,B表就是父表。当查询表A的数据时,通过表A的外键将表B的记录也查找出来,这就是级联查询。相应的还有级联删除,当删除B表的记录时,会先将A表中关联的记录删掉
(1)person表和 idencard表
-- 创建 idencard 表 -- 记录身份证 CREATE TABLE `idencard`( `id` INT PRIMARY KEY AUTO_INCREMENT, `card_sn` VARCHAR(32) NOT NULL DEFAULT '' )CHARSET utf8; INSERT INTO `idencard` VALUES(1,'123456789098765'); -- 创建person表 CREATE TABLE `person`( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL DEFAULT '', `card_id` INT, -- 对应idencard表的主键-id FOREIGN KEY (`card_id`) REFERENCES idencard (`id`)-- card_id作为外键 )CHARSET utf8; INSERT INTO `person` VALUES(1,'张三',1);
(2)实体类 IdenCard 和 Person
package com.li.entity; /** * @author 李 * @version 1.0 */ public class IdenCard { private Integer id; private String card_sn; //省略setter,getter,toString方法 }
package com.li.entity; /** * @author 李 * @version 1.0 */ public class Person { private Integer id; private String name; private IdenCard card; //省略setter,getter,toString方法 }
(3)PersonMapper 接口
package com.li.mapper; import com.li.entity.Person; /** * @author 李 * @version 1.0 */ public interface PersonMapper { //通过Person的id获取到Person,包括这个Person关联的IdenCard对象(级联操作) public Person getPersonById(Integer id); }
2.1.2方式1:多表联查
PersonMapper.xml映射文件实现级联查询,实现方法是使用多表联查,返回的数据通过resultMap结果映射
<mapper namespace="com.li.mapper.PersonMapper"> <!--1.接口声明:public Person getPersonById(Integer id); 2.通过Person的id获取到Person,包括这个Person关联的IdenCard对象(级联操作) 3.返回类型如果配置成resultType="Person",不能实现级联查询, 在返回的person对象中IdenCard属性对象为 null 4.因此需要使用自定义resultMap,在resultMap中指定级联关系--> <select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap"> SELECT * FROM `person`,`idencard` WHERE `person`.`id`= #{id} AND `person`.`card_id` = `idencard`.`id`; </select> <!--association – 一个复杂类型的关联;许多结果将包装成这种类型嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用 1.property="card" 表示 Person对象的card属性 2.javaType="IdenCard" 表示card属性的类型--> <resultMap id="PersonResultMap" type="Person"> <!--id标签–一个ID结果(就是主键);标记出作为主键的结果可以帮助提高整体性能--> <!--这里的property表示Person类的属性名,column表示对应表的字段--> <id property="id" column="id"/> <result property="name" column="name"/> <association property="card" javaType="IdenCard"> <!--这里的property表示IdenCard类的属性名,column表示表的字段名--> <result property="id" column="id"/> <result property="card_sn" column="card_sn"/> </association> </resultMap> </mapper>
测试:
@Test public void getPersonById() { Person person = personMapper.getPersonById(1); System.out.println("person=" + person); if (sqlSession != null) { sqlSession.close(); } }

2.2.3方式2:分解为多次单表操作(推荐使用)
第一种方式使用了多表联查的形式实现级联查询,但是如果涉及的表过多,sql语句可读性就会变差。第二种方式的核心思想是将多表联查分解成单表操作,这样更简洁,易于维护,而且可以复用你写好的方法,推荐使用
(1)创建IdenCardMapper接口
public interface IdenCardMapper { //根据id获取到身份证序列号 public IdenCard getIdenCardById(Integer id); }
(2)在IdenCardMapper的映射文件中实现该方法
<mapper namespace="com.li.mapper.IdenCardMapper"> <!--配置实现public IdenCard getIdenCardById(Integer id);--> <select id="getIdenCardById" parameterType="Integer" resultType="IdenCard"> SELECT * FROM `idencard` WHERE `id` = #{id} </select> </mapper>
(3)PersonMapper接口
//通过Person的id获取到Person,包括这个Person关联的IdenCard对象(方式2) public Person getPersonById2(Integer id);
(4)实现PersonMapper接口的映射文件
1. 先通过 SELECT * FROM person WHERE id =#{id} 返回 person 信息 2. 以第一个操作返回的 card_id 字段数据,作为条件再次查询,得到对应的 IdenCard 数据
如果第一个操作使用了别名,那么返回的时候的字段也是别名,因此第二个操作也要使用别名才能匹配到
<!--通过Person的id获取到Person,包括这个Person关联的IdenCard对象(方式2) 接口方法:public Person getPersonById2(Integer id);--> <resultMap id="PersonResultMap2" type="Person"> <id property="id" column="id"/> <result property="name" column="name"/> <!--第二种方式的核心思想是将多表联查操作分解成单表操作, 这样更简洁,易于维护,复用性更强,推荐使用--> <!--1.property="card"表示Person对象的card属性 2.column="card_id"是SELECT * FROM person WHERE id = #{id}语句返回的card_id字段名/别名 3.返回的字段card_id信息/数据会作为getIdenCardById()的入参来执行方法--> <association property="card" column="card_id" select="com.li.mapper.IdenCardMapper.getIdenCardById"/> </resultMap> <select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2"> SELECT * FROM person WHERE id = #{id}; </select>
测试结果:
可以看到底层执行了两次sql查询操作。首先对person表进行查询,查询结果(card_id)作为第二张表的查询条件(id),再对idencard表进行查询。

2.2注解方式
通过注解的方式来实现一对一的映射关系,实现级联查询,通过person可以获取到对应的idencard的信息。这里只进行方式二的演示。
在实际开发中还是推荐使用配置方式
(1)注解实现方法
IdenCardMapperAnnotation 接口:
package com.li.mapper; import com.li.entity.IdenCard; import org.apache.ibatis.annotations.Select; /** * @author 李 * @version 1.0 * 使用注解的方式实现一对一的映射 */ public interface IdenCardMapperAnnotation { //根据id获取到身份证序列号 @Select(value = "SELECT * FROM `idencard` WHERE `id` = #{id}") public IdenCard getIdenCardById(Integer id); }
PersonMapperAnnotation 接口:
package com.li.mapper; import com.li.entity.Person; import org.apache.ibatis.annotations.One; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; /** * @author 李 * @version 1.0 */ public interface PersonMapperAnnotation { //通过Person的id获取到Person,包括这个Person关联的IdenCard对象 //注解的形式就是对前面xml配置方式的体现 @Select(value = "SELECT * FROM person WHERE id = #{id}")//如果这里返回的字段使用了别名,则@result的card_id也要使用该别名 @Results({//配置返回数据的映射 @Result(id = true, property = "id", column = "id"), @Result(property = "name", column = "name"), @Result(property = "card", column = "card_id", one = @One(select = "com.li.mapper.IdenCardMapper.getIdenCardById")) }) public Person getPersonById(Integer id); }
(2)测试
@Test public void getIdenCardById() { Person person = personMapperAnnotation.getPersonById(1); System.out.println("person=" + person); if (sqlSession != null) { sqlSession.close(); } }

2.3注意事项
一张表是否设置了外键,对MyBatis进行对象级联映射没有影响,外键只是对表本身数据的约束
2.4练习-1对1双向映射
前面我们讲解的是查询Person可以级联查询到IdenCard,如果要求通过查询IdenCard,也可以级联查询到Person,应该如何解决?
这实际上是Person和IdenCard的1对1的双向映射(1<=>1),解决办法除了传统的多表联查之外,仍然可以使用分解为多次单表操作的方式:
1. 先通过 SELECT * FROM idencard WHERE id =#{id} 返回 IdenCard 信息 2. 以第一个操作返回的 id 字段数据,作为条件再次查询,得到对应的 person数据 3. 将结果进行映射,放在resultMap中
(1)修改IdenCard实体类,添加Person属性
package com.li.entity; public class IdenCard { private Integer id; private String card_sn; private Person person; //省略setter、getter、toString方法 }
Person实体类不变。
(2)IdenCard接口
//练习-根据id获取到身份证序列号,同时返回对应的Person信息 public IdenCard getIdenCardById2(Integer cardId);
(3)Person接口
//通过Person的card_id获取到Person信息 public Person getPersonByCardId(Integer cardId);
(4)IdenCardMapper.xml
<resultMap id="IdenCardMap" type="IdenCard"> <id property="id" column="id"/> <result property="card_sn" column="card_sn"/> <!--1.property="person" 是指 IdenCard类的person属性名 2.column="id" 是指getIdenCardById2返回的id字段名作为第二次查询的card_id--> <association property="person" column="id" select="com.li.mapper.PersonMapper.getPersonByCardId"/> </resultMap> <!--练习-根据id获取到身份证序列号,同时返回对应的Person信息 public IdenCard getIdenCardById2(Integer id);--> <select id="getIdenCardById2" parameterType="Integer" resultMap="IdenCardMap"> SELECT * FROM `idencard` WHERE `id` = #{id} </select>
(5)PersonMapper.xml
<!--练习-通过Person的card_id获取到Person--> <select id="getPersonByCardId" parameterType="Integer" resultType="Person"> SELECT * FROM person WHERE card_id = #{cardId}; </select>
(6)测试
@Test public void getIdenCardById2() { IdenCard idenCard = idenCardMapper.getIdenCardById2(1); System.out.println("idenCard=" + idenCard); }

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!