目录:
作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!
该文章参考学习教材为:
《Java EE企业级应用开发教程 (Spring + Spring MVC +MyBatis)》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!(侵权可联系我,进行删除,如果雷同,纯属巧合)
Mybatis的 “关联映射” :
通过前面的知识点我们熟悉了MyBatis的基本知识,并能够使用MyBatis以及面向对象的方式进行 “数据库操作”,但这些操作只是针对 单表 实现的。在实际的开发中,对数据库的操作常常会涉及多张表,这在面向对象中就涉及了 对象与对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好地处理对象与对象之间的关联关系.
“关联关系” 概述 :
在 关系型数据库中,多表之间存在着三种关联关系,分别为 一对一、一对多和多对多 :
(数据库中表) 三种关联关系 的具体说明如下 : (对应“数据库表”中的 “表”之间的“主外键关系”)
关联关系 描述 一对一 在 任意一方引入对方主键作为 外键。
比如说 : 个人表–-身份证表,是“一对一”的关系,按照规律 : 此时可在将“身份证表”的主键放在“个人表”中当外键,以此实现,根据个人信息查询身份证号。(这是 数据库方面的关联关系)
在 Java对象间的关系 可以是 : 在个人表中添加一个 “类类型” ( 身份证对象 )的属性,该属性是一个对象类型,存储身份证信息,以此完成和对应“Java对象间的” 一对一关系。
(即 添加的类类型属性的 对象 可以是和 :添加外键的数据库的表相对应的)一对多 在 “多”的一方,添加“一”的一方的主键作为外键。
比如说 :
用户表 (一) —订单表(多) ,此时在数据库层面是 :将“用户表” (一)的“主键”作为“订单表” (多)的“外键”。
在Java对象层面是 :在 “一” 对应的对象中添加一个 “对象集合”类型的 属性。多对多 产生 中间关系表,引入两张表的主键作为 (中间表)的 外键,两个主键成为“ 联合主键”或使用新的字段作为主键。 通过数 据库中的表可以描述数据之间的关系,同样,在Java中,通过对象也可以进行关系描述 ( Java对象描述数据表之间的关系 ) :
(Java对象中) 三种关联关系 的具体说明如下 : (对应“Java对象”中的 “表”之间的“主外键关系”)
三种关联关系 描述 一对一的关系 就是在 本类中定义对方类型的对象。如A类中定义B类类型的属性口,B类中定义A类类型的属性a。
(互相在本类中定义对方类型的对象,)
ps :
其实只要在添加了”外键的 数据库表对应的 “对象”中添加一个”类类型“属性即可,“不一定”要两个都添加。一对多 的关系 就是 “一个”A类类型对应“多个”B类类型的情况,需要在A类中以集合的方式引入B类类型的对象,同时也要在 在B类中定义A类类型的属性对象。
ps :
其实只要在 ”一“ 对象中添加一个”对象集合“ 类型属性即可,“不一定”要两个都添加.多对多 的关系 在 A类中定义B类类型的集合,在 B类中定义A类类型的集合。
1.一对一
“一对一” 关联关系 ( 嵌套查询 / 嵌套结果 (常用) )
在现实生活中,一对一关联 关系是十分常见的。例如,一个人只能有一个身份证,同时一个身份证也只会对应一个人。
MyBatis是怎么处理图的这种一对一关联关系的呢?
在 文章: “Mybatis | Mybatis的核心配置” 中的 “配置文件中的主要元素” 中的 <resultMap>元素 包含了一个 <association>子元素,MyBatis 就是通过该元素 : <association>来处理一对一关联关系 的。
在 <association>元素中,通常可以配置以下属性 :
属性 描述 property 指定 映射到 的 实体类中的属性,与表字段一一对应。 column 指定 表中对应的字段。 javaType 指定 映射到实体对象“属性”的类型。
(简而言之 : 现在参与映射的 “实体类” 的类型,一对一关系中,JavaType的值为 : 外键所在的表对应的 对象的类名。)
如:
一个“个人表”( Person对象 ),一个“身份证表” ( IDCard对象 ),“身份证”表的“主键”作为“个人表”的外键。这样就能通过个人id来查询个人信息的同时还能查询 “对应的”身份证号码。 这种是 “一对一”关联关系,要通过 <resultMap>元素中的子元素 : <association>元素来表示这种一对一关系。此时 javaType属性的值为 :IDCard。select
ps :
该属性在 “嵌套查询” 中使用指定 引入 “嵌套查询”的子SQL语句,该属性用于 关联映射中的 嵌套查询。
ps :
select属性 的作用本质 : 通过sql语句获得一个对应的“对象信息”,将其映射到某个对象的“类类型”属性上,以此来完成“一对一”关联关系。fetchType 指定在 关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy (即默认关联映射延迟加载)。 例子如 :
<!-- 使用<association>元素实现"关联关系" : "一对一关系" (是在<resultMap>元素中使用的,不能单独使用) --> <!-- 方式一: 嵌套查询 --> <association property="card" column="card_id" javaType="com.myh.mapper.IdCard" select="com.myh.mapper.IdCardMapper.findCodeById"/> <!-- 方式二: 嵌套结果(更常用) --> <association property="card" column="card_id" javaType="com.myh.mapper.IdCard"> <id property="id" column="card_id"/> <result property="code" column="code"/> </association>
MyBatis在 映射文件中加载关联关系对象主要通过两种方式 : 嵌套查询 和 嵌套结果 ( 更常用 )。
嵌套查询 是指通过执行 另外一条SQL映射语句 来返回预期的复杂类型;
嵌套结果是使用 嵌套结果映射 来处理重复的联合结果的子集 (使用一条复杂的SQL语句来进行多表查询)。ps :
通过 “嵌套结果” ( 更常用 )的方式来进行处理“一对一”关联关系是使用一条复杂的SQL语句来进行“多表查询”,这样就能达到目的,此时在 <association>元素中要设置“外键”对应的数据库表 和 其对应的“对象” 之间的映射关系,以此来完成数据映射。
(其中实际的最重要操作是 : ①在 <association>元素中设置好“外键”对应的数据库表 和 对象属性之间的“映射关系” ②在“映射文件”中通过一条复杂的SQL语句进行 “多表查询”。)
“一对一” 关联关系 “实现方式” (例子)
下面的内容 :将以 个人和身份证 之间的 一对一关联关系 进行详细例子讲解 :
查询个人及其关联的身份证信息是先通过查询个人表中的主键来获个人信息,然后通过表中的外键,来获取证件表中的身份证号信息。
(有两个表 :①个人表 : 其中存放个人信息 ②身份证表 : 其中存放身份证信息。 通过个人表中的个人对应的身份证表中的主键 ,该主键存放在个人表中则作为 ”外键“ ,最后通过该外键实现“个人”对应的“身份证信息”)“一对一”关联关系的 <association>元素的实现方式有两种 :①嵌套查询 ②嵌套结果
“嵌套查询”
嵌套查询 :
通过 <association>元素 + <association>元素的 select属性 + 两条sql语句 来实现 “一对一”关联关系。例子如下 :导入 Mybatis框架依赖 (引入Mybatis的核心包 和 Mybatis的依赖包) 和 MySQL驱动JAR: [Mybatis网址
获得Mybatis核心包 和 Mybatis的依赖包创建tb_idcard 、tb_person表 :
IDCard.java 和 Person.java :
public class IDCard { //持久化类 private Integer id; //该属性为 tb_idcard表中的“主键”,在"tb_person"表中做“外键” private String code; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public String toString() { return "IDCard{" + "id=" + id + ", code='" + code + '\'' + '}'; } }
public class Person { //持久化类 private Integer id; private String name; private Integer age; private String sex; /* 个人关联的“身份证号”,此处将IDCard这个对象,作为属性引入到该表中。 数据库中则通过“tb_person”表中的“外键”来查询到个人对应的“tb_idcard”表中的“身份证号” */ private IDCard card; //"对象类型",要获得一个“IDCard”类型的数据然后"映射"到该属性中 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public IDCard getCard() { return card; } public void setCard(IDCard card) { this.card = card; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + ", card=" + card + '}'; } }
db.properties 和 mybatis-config.xml :
db.properties :
jdbc.driver = com.mysql.cj.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/mybatis?useSSL=false jdbc.username = root jdbc.password = root
mybatis-config.xml :
<?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元素>来引入.propertis文件,可在映射文件中使用该文件中的属性等 --> <properties resource="com/myh/Mybatis的关联映射/properties/db.properties"/> <!-- 使用扫描包的形式来定义“别名” : 替代类的"全限定名",引入更加方便快捷 --> <typeAliases> <package name="com.myh.Mybatis的关联映射.po"/> </typeAliases> <!-- Mybatis的配置文件: mybatis-config.xml : 为全局配置文件,配置了“运行环境的信息”, 主要内容是 : 数据库的连接 (其属于Mybatis操作步骤的第一步 : 读取Mybatis-config.xml配置文件的"先行要准备好的内容") --> <!-- 1.配置环境,默认环境id为mysql --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <!-- 用于指定MyBatis获取数据库连接的方式。“POOLED”代表的是连接池。 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 配置Mapper文件的位置,让"配置文件"读取“映射文件” --> <mappers> <mapper resource="com/myh/Mybatis的关联映射/mapper/IdCardMapper.xml"/> <mapper resource="com/myh/Mybatis的关联映射/mapper/PersonMapper.xml"/> <mapper resource="com/myh/Mybatis的关联映射/mapper/PersonMapper (嵌套结果).xml"/> </mappers> </configuration>
IdCardMapper.xml 和 PersonMapper.java (映射文件):
IdCardMapper.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"> <!-- namespace的命名空间 --> <mapper namespace="IdCardMapper"> <!-- 根据id查询证件的信息 --> <select id="findCodeById" parameterType="Integer" resultType="com.myh.Mybatis的关联映射.po.IDCard"> select * from tb_idcard where id=#{id} </select> </mapper>
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"> <!-- namespace的命名空间 --> <mapper namespace="PersonMapper"> <!-- "嵌套查询" 的方式来实现“一对一”映射关系 --> <resultMap id="IdCardWithPersonResult" type="com.myh.Mybatis的关联映射.po.Person"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age"/> <result property="sex" column="sex"/> <!-- 使用<resultMap>元素的子元素:<association>元素来表示Person对象中存在的 “一对一”关联关系 --> <!-- 此处获得一个IdCard类型对应的“对象”属性数据,然后映射到该"类类型"属性中 --> <association property="card" column="card_id" javaType="com.myh.Mybatis的关联映射.po.IDCard" select="IdCardMapper.findCodeById"/> </resultMap> <!-- 嵌套查询: 通过执行另外一条SQL映射语句来返回预期的"特殊类型" --> <!-- 实际的需求的前提说明 : Person表中除了本来对应的属性外,还添加了“IdCard”对象这个“类类型”属性,这就是”一对一“关联关系,同时也要求在Sql语句中除了基本的查询外, 还要通过Person的id来查询到对应“身份证”信息。(因为这已经设置好了主外键,只要使用<resultMap>的子元素: <association>元素就能解决这个问题) --> <!-- 实际的需求 : 通过Person的id除了基本的Person类的信息外,还要活得IdCard类的信息,然后映射到类的属性中,可用<association> : "一对一"关联关系来实现该需求 --> <select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult"> select * from tb_person where id=#{id} </select> </mapper>
MybatisAssociatedTest.java (测试类) :
//Mybatis关联查询映射测试类 public class MybatisAssociatedTest { /** * “嵌套查询”方式实现“一对一”关联关系 */ @Test //单元测试 public void findPersonByIdTest1() throws IOException { //1.读取mybatis框架的配置文件 String resource = "com/myh/Mybatis的关联映射/mybatis-config.xml"; //通过“输入流”读取mybatis配置文件 /* 在该mybatis-config.xml配置文件中,已配置了“数据源”信息 和配置了"映射文件 : XxxMapper.xml”的位置, 可实施加载“映射文件” */ InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件“构建会话工厂 : SqlSessionFactory ” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory(会话工厂)创建SqlSession("会话"对象) SqlSession sqlSession = sqlSessionFactory.openSession(); //使用Mybatis嵌套查询的方式查询id为1的人的信息 Person person = sqlSession.selectOne("PersonMapper.findPersonById", 1); System.out.println("(一对一关联关系)查询出来的信息为: "+person); //4.关闭SqlSession sqlSession.close(); } }
注意点:
在使用MyBatis “嵌套查询”方式进行MyBatis关联查询映射时,使用 MyBatis的延迟加载 在一定程度上 可以降低运行消耗并提高查询效率。MyBatis 默认没有开启延迟加载,需要在核心配置文件mybatis-config.xml 中的 <settings>元素内进行配置,具体配置方式如下 :
mybatis-config.xml :<settings> <!-- 打开"延迟加载"的开关 --> <setting name="lazyLoadingEnable" value="true"/> <!-- 将“积极加载”改为“延迟加载”,即按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
在映射文件中,MyBatis 关联映射的 <asociation>元素和 <collection>元素中都已默认配置了延迟加载属性,即默认属性fetchType=“lazy” (属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无须在映射文件中再做配置。
“嵌套结果” (常用)
嵌套结果 :
通过 <association>元素 + <association>元素的 <id>元素、<result>元素等 + 一条sql语句 来实现 “一对一”关联关系。嵌套结果与 “嵌套查询”相比,只需要使用一条SQL语句即可,很好地解决了 “嵌套查询”存在的多条sql查询而消耗数据库性能,降低查询效率“的问题。
例子如下 :
① 首先所有的文件、代码和 “嵌套查询” 保持一致。因为“嵌套结果” 和 “嵌套查询”的大部分代码是相似的,只有“映射文件 ”( PersonMapper (嵌套结果).xml ) 和 测试类 中的代码有所不同。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"> <!-- namespace的命名空间 --> <mapper namespace="PersonMapper(嵌套结果)"> <!-- "嵌套结果" 的方式来实现“一对一”映射关系 --> <!-- 通过以下代码就能通过一条复杂的SQL语句来进行“多表查询”,然后通过之前的配置完成“数据映射”,不需要使用“嵌套查询”的要用两条SQL语句来实现 --> <resultMap id="IdCardWithPersonResult" type="com.myh.Mybatis的关联映射.po.Person"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age"/> <result property="sex" column="sex"/> <!-- 使用<resultMap>元素的子元素:<association>元素来表示Person对象中存在的 “一对一”关联关系 --> <!-- 配置“外键”对应的“数据库表” 和 其对应的“对象”之间的映射关系 --> <association property="card" column="card_id" javaType="com.myh.Mybatis的关联映射.po.IDCard"> <id property="id" column="id"/> <result property="code" column="CODE"/> </association> </resultMap> <!-- 嵌套结果: 使用“嵌套结果”映射来处理“重复”的“联合结果”的子集 --> <!-- 实际的需求的前提说明 : Person表中除了本来对应的属性外,还添加了“IdCard”对象这个“类类型”属性,这就是”一对一“关联关系,同时也要求在Sql语句中除了基本的查询外, 还要通过Person的id来查询到对应“身份证”信息。(因为这已经设置好了主外键,只要使用<resultMap>的子元素: <association>元素就能解决这个问题) --> <!-- 实际的需求 : 通过Person的id除了基本的Person类的信息外,还要活得IdCard类的信息,然后映射到类的属性中,可用<association> : "一对一"关联关系来实现该需求 --> <!-- 通过一条复杂的sql语句来进行”多表查询“ --> <select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult"> select p.*,idcard.CODE from tb_person p,tb_idcard idcard where p.card_id = idcard.id and p.id = #{id} </select> </mapper>
MybatisAssociatedTest.java (测试类) :
//Mybatis关联查询映射测试类 public class MybatisAssociatedTest { /** * “嵌套查询”方式实现“一对一”关联关系 */ @Test //单元测试 public void findPersonByIdTest1() throws IOException { //1.读取mybatis框架的配置文件 String resource = "com/myh/Mybatis的关联映射/mybatis-config.xml"; //通过“输入流”读取mybatis配置文件 /* 在该mybatis-config.xml配置文件中,已配置了“数据源”信息 和配置了"映射文件 : XxxMapper.xml”的位置, 可实施加载“映射文件” */ InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件“构建会话工厂 : SqlSessionFactory ” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory(会话工厂)创建SqlSession("会话"对象) SqlSession sqlSession = sqlSessionFactory.openSession(); //使用Mybatis嵌套查询的方式查询id为1的人的信息 Person person = sqlSession.selectOne("PersonMapper.findPersonById", 1); System.out.println("(一对一关联关系)查询出来的信息为: "+person); //4.关闭SqlSession sqlSession.close(); } /** * “嵌套结果”方式实现“一对一”关联关系 */ @Test //单元测试 public void findPersonByIdTest2() throws IOException { //1.读取mybatis框架的配置文件 String resource = "com/myh/Mybatis的关联映射/mybatis-config.xml"; //通过“输入流”读取mybatis配置文件 /* 在该mybatis-config.xml配置文件中,已配置了“数据源”信息 和配置了"映射文件 : XxxMapper.xml”的位置, 可实施加载“映射文件” */ InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件“构建会话工厂 : SqlSessionFactory ” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory(会话工厂)创建SqlSession("会话"对象) SqlSession sqlSession = sqlSessionFactory.openSession(); //使用Mybatis嵌套查询的方式查询id为1的人的信息 Person person = sqlSession.selectOne("PersonMapper(嵌套结果).findPersonById", 1); System.out.println("(一对一关联关系)查询出来的信息为(通过嵌套结果的方式): "+person); //4.关闭SqlSession sqlSession.close(); } }
使用“嵌套结果”的方式 只需执行一条SQL语句就解决 “一对一”关联关系的情况。开发中,“嵌套结果”的方式更常用。
2.一对多
“一对多” 关联关系 ( 嵌套查询 / 嵌套结果 (常用) )
与一对一的关联关系相比,我们 接触更多的关联关系是一对多(或多对一)。例如 : 一个用户可以有多个订单,同时多个订单归一个用户所有。
Mybatis中通过 <collection>元素来处理“一对多” 关联关系,
<collection>元素的属性大部分 <association>元素相同,但其还包含一个 特殊属性 :ofType。 ofType属性与 javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型 (即指明集合中元素的类型)。“一对多”关联关系的实现方式同样有两种 : ①嵌套查询 ②嵌套结果 ( 常用 )
(一对多的 ) 两种方式 在 <collection>元素 中的 代码区别 为 :<!-- 使用<collection>元素实现"关联关系" : "一对多关系" (是在<resultMap>元素中使用的,不能单独使用) --> <!-- 方式一: 嵌套查询 --> <association property="card" column="card_id" javaType="com.myh.mapper.IdCard" select="com.myh.mapper.IdCardMapper.findCodeById"/> <!-- 方式二: 嵌套结果(更常用) --> <association property="card" column="card_id" javaType="com.myh.mapper.IdCard"> <id property="id" column="card_id"/> <result property="code" column="code"/> </association>
“一对多” 关联关系 “实现方式” (例子)
下面的内容 :将以 用户和订单之间的“一对多” 关联关系进行详细例子讲解 :
(有两个表 :①用户表 (一) : 其中存放用户信息 ②订单表 (多) : 其中存放订单信息。 数据库表层是 : 将在 “多” : 订单中添加添加“外键” (将“用户表”中主键作为 “订单表” 中“外键”)“一对多”关联关系的 <collection>元素的实现方式有两种 :①嵌套查询 ②嵌套结果
ps :例子讲解只讲常用的 : “嵌套结果”的方式实现 “一对多 关联关系”。
“嵌套结果” (常用)
嵌套结果 :
通过 <collection>元素 + <collection>元素的 ofType属性、id元素、result元素等 + 一条sql语句 来实现 “一对多”关联关系。例子如下 :第一步、创建tb_user 、tb_orders表 :
创建数据库 中表对应的PO类** (持久化对象)。
db.properties 和 mybatis-config.xml :
db.properties :
jdbc.driver = com.mysql.cj.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/mybatis?useSSL=false jdbc.username = root jdbc.password = root
mybatis-config.xml :
<?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元素>来引入.propertis文件,可在映射文件中使用该文件中的属性等 --> <properties resource="db.properties"/> <!-- Mybatis的配置文件: mybatis-config.xml : 为全局配置文件,配置了“运行环境的信息”, 主要内容是 : 数据库的连接 (其属于Mybatis操作步骤的第一步 : 读取Mybatis-config.xml配置文件的"先行要准备好的内容") --> <!-- 1.配置环境,默认环境id为mysql --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <!-- 用于指定MyBatis获取数据库连接的方式。“POOLED”代表的是连接池。 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 配置Mapper文件的位置,让"配置文件"读取“映射文件” --> <mappers> <mapper resource="com/myh/Mybatis的关联映射/一对多/mapper/userMapper.xml"/> </mappers> </configuration>
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"> <!-- namespace的命名空间 --> <mapper namespace="UserMapper"> <resultMap id="resultMap" type="com.myh.Mybatis的关联映射.一对多.po.User"> <id property="id" column="id"/> <id property="username" column="username"/> <id property="address" column="address"/> <!-- 用“collection”元素“配置一对多”关联关系 (注意同样是“一对多”在①数据库表关系层面 和 ②Java对象属性关系层面的表现是不一样的) --> <!-- ofType : 指定"集合"中的"元素"的类型 --> <!-- id 和 result元素 : 将集合中的元素中的属性 和 其对应的数据库中“字段”数据进行映射 --> <collection property="ordersList" ofType="com.myh.Mybatis的关联映射.一对多.po.Orders"> <id property="id" column="orders_id"/> <result property="number" column="number"/> </collection> </resultMap> <!-- 一对多 : 查看某一"用户"及其关联的“订单信息” 注意: 当关联查询出的列名相同,需使用别名进行区分开 --> <!-- 使用的实现“一对多”关联关系的方式中的“嵌套结果” : 用复杂语句进行“多表查询” --> <select id="findUserWithOrders" parameterType="Integer" resultMap="resultMap" > 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> </mapper>
MybatisCollectionTest.java (测试类)
public class MybatisCollectionTest { //"一对多"关联关系,测试类 /** * “嵌套结果”方式实现“一对多”关联关系 */ @Test //单元测试 public void findUserTest_Collection() throws IOException { //1.读取mybatis框架的配置文件 String resource = "com/myh/Mybatis的关联映射/一对多/mybatis-config.xml"; //通过“输入流”读取mybatis配置文件 /* 在该mybatis-config.xml配置文件中,已配置了“数据源”信息 和配置了"映射文件 : XxxMapper.xml”的位置, 可实施加载“映射文件” */ InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件“构建会话工厂 : SqlSessionFactory ” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory(会话工厂)创建SqlSession("会话"对象) SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("UserMapper.findUserWithOrders", 1); System.out.println("通过(嵌套结果)方式处理(一对多)关联关系地从数据库中获得User数据为: "+user); //4.关闭SqlSession sqlSession.close(); } }
3.多对多
“多对多” 关联关系
在实际项目开发中,“多对多” 的 关联关系 也是非常常见的。以 订单 和 商品 为例,一个订单可以包含多种商品,
而一种商品又可以属于多个订单,订单和商品就属于多对多的关联关系。在数据库中,多对多 的 关联关系 通常使用一个 中间表 来维护,中间表中的订单id作为外键参照 订单表 的id,商品id作为外键参照 商品表 的id
(即将“订单表 和“”商品表 的主键在 “中间表中当“外键””)。下面的“多对多”例子中的出发思考点是 : 订单—产品。(思考的 :查询id为xxx的订单的”产品信息“)
ps : “映射文件” 中整体的执行流程 : 虽然都是多对多,但实际开发中一般客户的思考方向是 :从“订单”出发然后到“产品”的 (如 : 我这个订单选了5个产品)。所以此时映射文件的执行流程 : 是从OrderMapper.xml 到 productMapper.xml 。
“多对多” 关联关系的 “例子”
下面的“多对多”例子中的出发思考点是 : 订单—产品。(思考的 :查询id为xxx的订单的”产品信息“)
“多对多”关联关系的 <collection>元素的实现方式有两种 :①嵌套查询 ②嵌套结果
“嵌套查询”
嵌套查询 :
通过 <collection>元素 + <collection>元素的 select属性 + 两条sql语句 来实现 “多对多”关联关系。例子如下 :创建tb_product、tb_orders、tb_ordersitem这三个表 :
创建tb_product表 和 tb_orders表对应的 “持久化类”。
OrdersMapper.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"> <!-- namespace的命名空间 --> <mapper namespace="OrdersMapper"> <select id="findOrdersWithProduct" parameterType="Integer" resultMap="resultMap"> select * from tb_orders where id = #{id} </select> <!-- collection中的select属性中的sql语句的开发目标是 : 根据订单id来查询对应的“产品”,查询到后"映射“存储的List集合中 --> <resultMap id="resultMap" type="com.myh.Mybatis的关联映射.多对多.po.Orders"> <id property="id" column="id"/> <id property="number" column="number"/> <collection property="productList" column="id" ofType="com.myh.Mybatis的关联映射.多对多.po.Product" select="ProductMapper.findProductById"> </collection> </resultMap> </mapper>
ProductMapper.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"> <!-- namespace的命名空间 --> <mapper namespace="ProductMapper"> <!-- 根据订单id来在“中间表”中查询对应的“产品”--> <!--将此处获得的“产品”的组成的List集合映射到 OrdersMapper.xml中的 collection元素中的 productList上 --> <select id="findProductById" parameterType="Integer" resultType="com.myh.Mybatis的关联映射.多对多.po.Product"> select * from tb_product where id in (select product_id from tb_ordersitem where orders_id = #{id}) </select> </mapper>
测试类代码 :
/** * “嵌套查询”方式实现“多对多”关联关系 */ @Test //单元测试 public void findUserTest_Collection() throws IOException { //1.读取mybatis框架的配置文件 String resource = "com/myh/Mybatis的关联映射/多对多/mybatis-config.xml"; //通过“输入流”读取mybatis配置文件 /* 在该mybatis-config.xml配置文件中,已配置了“数据源”信息 和配置了"映射文件 : XxxMapper.xml”的位置, 可实施加载“映射文件” */ InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件“构建会话工厂 : SqlSessionFactory ” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory(会话工厂)创建SqlSession("会话"对象) SqlSession sqlSession = sqlSessionFactory.openSession(); //查询id为1的订单中的商品信息 Orders orders = sqlSession.selectOne("OrdersMapper.findOrdersWithProduct", 1); System.out.println(orders); //4.关闭SqlSession sqlSession.close(); }
“嵌套结果” (常用)
嵌套结果 :
通过 <collection>元素 + <collection>元素的 ofType属性、id子元素、result子元素等 + 一条sql语句 来实现 “多对多”关联关系。例子如下 :大部分代码和 “嵌套查询”的代码是相同的,最大的差别是在 映射文件中,其中的代码有根本性的改变,导致从“嵌套查询” 变成了 “嵌套结果” 。
OrdersMapper2.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"> <!-- namespace的命名空间 --> <!-- 多对多中的“嵌套结果”--> <mapper namespace="OrdersMapper2"> <!-- 复杂的sql语句查询 + “结果映射” 处理 “多对多”关系 --> <select id="findOrdersWithProduct2" parameterType="Integer" resultMap="resultMap222"> select o.*,p.id as pid,p.name,p.price from tb_orders o ,tb_product p,tb_ordersitem oi where oi.orders_id = o.id and oi.product_id = p.id and o.id = #{id} </select> <!-- 多对多中的“嵌套结果”--> <!-- collection中的select属性中的sql语句的开发目标是 : 根据订单id来查询对应的“产品”,查询到后"映射“存储的List集合中 --> <resultMap id="resultMap222" type="com.myh.Mybatis的关联映射.多对多.po.Orders"> <id property="id" column="id"/> <id property="number" column="number"/> <collection property="productList" ofType="com.myh.Mybatis的关联映射.多对多.po.Product"> <id property="id" column="pid"/> <result property="name" column="name"/> <result property="price" column="price"/> </collection> </resultMap> </mapper>
上述代码中,只定义了一条查询SQL,通过该SQL就能查询出订单及其关联的商品信息。