Mybatis多表操作

时间:2022-05-19 19:54:11

一:引言

  在学习完前面的mybatis基本语法后,大家都有个认知,这个Mybatis太强大了,比之前使用JDBC写方便多了,但是你们当初在使用原生JDBC写SQL查询的时候有没有遇到过多表查询呢?肯定大部分人都遇到过,我刚学JDBC的时候遇到多表查询我懵了,不知道如何应对,所以我就默默的执行2条SQL语句,分别查询不同的表,然后对这2个查询出来的数据通过java代码控制,使封装到对象中,这个简直要崩溃的感觉,但是学完Mybatis多表查询后,你会爱上Mybatis的

准备工作:我已经准备好了一对一、一对多、多对多的数据表以及基本的Mybatis的搭建,后面的一些操作我会围绕这上面的数据表开展讲解,希望可以理解

快速搭建一个小项目

二:Mybatis的高级映射

在前面的Mybatis基本使用中映射做过了初步的解释,但是在这我要把Mybatis的映射的参数做个详细的介绍,其实高级映射是通过resultMap标签完成的,而且也是最重要的部分,是为多表查询做个铺垫,所以搞懂Mybatis高级映射是很有必要的!

其实resultMap元素有很多子元素和一个值得讨论的结构。下面是 resultMap 元素的概念视图

constructor – 类在实例化时,用来注入结果到构造方法中
  idArg – ID 参数;标记结果作为 ID 可以帮助提高整体效能
  arg – 注入到构造方法的一个普通结果
id – 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂的类型关联;许多结果将包成这种类型嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集,嵌入结果映射 – 结果映射自身的集,或者参考一个
discriminator – 使用结果值来决定使用哪个结果映射
  case – 基于某些值的结果映射,嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。

1:id和result

<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
这些是结果映射最基本内容。id 和 result 都映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的单独属性或字段。
这两者之间的唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性。这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射)。
 ①:property
映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同
的 JavaBeans 的属性,那么就会使用。否则 MyBatis 将会寻找给定名称
的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你
可以这样映射一些东西:“username”
②:column
从数据库中得到的列名,或者是列名的重命名标签。这也是通常和会
传递给 resultSet.getString(columnName)方法参数中相同的字符串。
③:javaType
一个 Java 类的全限定名,或一个类型别名(参加上面内建类型别名
的列表)。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。
然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType
来保证所需的行为。
③:jdbcType
在这个后会列出所支持的 JDBC 类型列表中的类型。JDBC 类型是仅
仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC
的需要,而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定
这个类型-但仅仅对可能为空的值。
④:typeHandler
我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默
认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理
器的实现,或者是类型别名

id和result属性介绍

BIT         FLOAT   CHAR        TIMESTAMP       OTHER   UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR

支持的JDBC类型

2:构造方法

  <constructor>
    <idArg column="id" javaType="int"/>
    <arg column=”username” javaType=”String”/>
  </constructor>
  对于大多数数据传输对象(Data Transfer Object,DTO)类型,属性可以起作用,而且像你绝大多数的领域模型,指令也许是你想使用一成不变的类的地方。通常包含引用或查询数据的表很少或基本不变的话对一成不变的类来说是合适的。构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBeans 属性来达到这个目的,为了向这个构造方法中注入结果,MyBatis 需要通过它的参数的类型来标识构造方法。Java 没有自查(反射)参数名的方法。所以当创建一个构造方法元素时,保证参数是按顺序排列的,而且数据类型也是确定的。
<constructor>
  <idArg column="id" javaType="int"/>
  <arg column=”username” javaType=”String”/>
</constructor>

二:Mybatis多表查询之一对一

  什么是一对一呢?我先拿现实的例子来说,抛弃其它特殊情况,在我们上学的年代,学校总会统计每个学生的家庭状况,那我们也是可以认为一个学生有一个家庭信息,一个家庭信息里面有一个学生,两边是一对一关系,虽然我这个例子举得不是太好,但是产品和订单的例子就会很好的体现出来,比如一个产品对应一个订单,一个订单对应一个产品。

  在Mybatis里面它不叫一对一查询,叫关联查询,关联元素处理“有一个”类型的关系,而且关联中不同的是你需要告诉 MyBatis 如何加载关联。MyBatis 在这方面会有两种不同的分别是:嵌套查询(通过执行另外一个 SQL 映射语句来返回预期的复杂类型)和嵌套结果(使用嵌套结果映射来处理重复的联合结果的子集)

①:嵌套查询

select属性:

  使用嵌套结果映射来处理重复的联合结果的子集

我现在的要求是学生和家庭是一对一关联关系,这就好办了,我可以用嵌套查询来完成,那我们现在来创建2个实体类吧

//学生实体类   注意要把private int fid;改为private Family family
//因为是关联关系,我要在查到学生对象的时候通过外键把家庭也查询出来
//放到学生对象里面
public class Student {
private int id; //id
private String name; //姓名
private String sex; //性别
private int age; //年龄
private double credit; //成绩/学分
private double money; //零花钱
private String address; //住址
private String enrol; //入学时间
private Family family; //外键 家庭
private int tid; //外键 老师id
//构造器/set/get/toString 你们补充一下
} //家庭对象
public class Family {
private int id; //家庭主键
private int member; //成员个数
private String guardian; //监护人
private String tel; //监护人号码
private String dad; //爸爸姓名
private String mom; //妈妈姓名
private String address; //家庭住址
//构造器/set/get/toString 你们补充一下
}

Student对象和Family对象

<!--配置家庭映射关系-->
<resultMap id="familyMapper" type="family">
<id column="fid" property="id"></id>
<result column="fmember" property="member"></result>
<result column="fguardian" property="guardian"></result>
<result column="ftel" property="tel"></result>
<result column="fdad" property="dad"></result>
<result column="fmom" property="mom"></result>
<result column="faddress" property="address"></result>
</resultMap>
<!--配置学生类映射关系-->
<resultMap id="studentMapper" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
<!--<result column="fid" property="fid"></result>-->
<result column="tid" property="tid"></result>
<association column="fid" property="family" select="findByIdInFamily" javaType="family"></association>
</resultMap>
<!--
column:当前student表中连接family的外键字段名称
property:当前封装family对象的student对象里的名称
javaType:当前封装的对象类型
大家会看到里面有个association标签 但是在后面添加一个select属性就变成嵌套查询了 而且select="findByIdInFamily"
所以执行到这一步就找findByIdInFamily查询标签的ID
--> <!--首先我们得建立一个查询学生的SQL语句和标签-->
<!--查询全部学生-->
<select id="findAll" resultMap="studentMapper">
select * from student;
</select>
<!--可是现在的问题是数据库字段和对象属性不匹配 所以还要再上面定义映射关系 看上面--> <!--查询单个家庭信息-->
<select id="findByIdInFamily" parameterType="Integer" resultMap="familyMapper">
select * from family where fid=#{id};
</select>

嵌套查询

注意:嵌套查询中被封装的对象(如上面的Family)一定要字段名和属性名一样,如果不一样,一定要手动映射,否则查询出来的family为空。虽然这种方式很简单,但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的“N+1 查询问题”。就是说如果我查询学生的数据有5000条,那么mybatis查询到学生的每条数据后都会去执行一个嵌套查询,获取指定id去查询关联的对象(family),5000条数据就会查询family表5000次+1次查询学生全部数据,但是这种嵌套查询也是有一种好处,能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加载,这样的行为可能是很糟糕的。

②:嵌套结果

  嵌套结果查询是我们经常使用到的,这是结果映射的 ID,可以映射关联的嵌套结果到一个合适的对象图中。这是一种替代方法来调用另外一个查询语句。这允许你联合多个表来合成到一个单独的结果集。这样的结果集可能包含重复,数据的重复组需要被分解,合理映射到一个嵌套的对象图。为了使它变得容易,MyBatis 让你“链接”结果映射,来处理嵌套结果。

<!--配置学生类映射关系-->
<resultMap id="studentMapper" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
<result column="tid" property="tid"></result>
<association property="family" javaType="family" column="fid">
<id column="fid" property="id"></id>
<result column="fmember" property="member"></result>
<result column="fguardian" property="guardian"></result>
<result column="ftel" property="tel"></result>
<result column="fdad" property="dad"></result>
<result column="fmom" property="mom"></result>
<result column="faddress" property="address"></result>
</association>
</resultMap>
<!--property:bean对象名称 javaType:指定关联的类型 column:数据库字段名称 最少指定前2个--> <!--查询全部学生-->
<select id="findAll" resultMap="studentMapper">
select s.*,f.fmember,f.fguardian,f.ftel,f.fdad,f.fmom,f.faddress
from student s inner join family f using(fid)
</select>
<!--using:表示主键和外键一样可以不用on xx=xx -->

三:Mybatis多表查询之一对多查询

什么是一对多呢?其实可以很容易理解,在学校每个学生都有辅导员,而且每个辅导员就有多个学生,通常辅导员是管理一个班学生的,所以呀,老师对学生就是一对多关系,我上面的表也可以很好的表现出来,teacher(辅导员)student(学生)

  在Mybatis对与一对多称为集合查询 ,其实和关联查询差不多,集合查询是把映射嵌套的结果封装到List中,也可以分为集合嵌套查询和集合集合嵌套结果,争夺下面的案例我首先把teacher类和student类给改造一下

//辅导员对象
public class Teacher {
private int id; //id
private String name; //姓名
private String sex; //性别
private int age; //年龄
private double salary; //工资
private String address; //住址
private List<Student> students; //学生对象
//构造器/set/get/toString 你们补充一下
} //学生对象
public class Student {
private int id; //id
private String name; //姓名
private String sex; //性别
private int age; //年龄
private double credit; //成绩/学分
private double money; //零花钱
private String address; //住址
private String enrol; //入学时间
//构造器/set/get/toString 你们补充一下
} //辅导员接口
public interface TeacherDao {
//查询全部老师信息
List<Teacher> findAll();
} //辅导员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="cn.xw.dao.TeacherDao"> <resultMap id="teacherMapper" type="teacher">
<id column="tid" property="id"></id>
<result column="tname" property="name"></result>
<result column="tsex" property="sex"></result>
<result column="tsalary" property="salary"></result>
<result column="taddress" property="address"></result>
</resultMap> <!--查询全部辅导员信息-->
<select id="findAll" resultMap="teacherMapper">
select * from teacher;
</select> </mapper>

代码改造

①:集合的嵌套查询

<!--辅导员关系映射-->
<resultMap id="teacherMapper" type="teacher">
<id column="tid" property="id"></id>
<result column="tname" property="name"></result>
<result column="tsex" property="sex"></result>
<result column="tage" property="age"></result>
<result column="tsalary" property="salary"></result>
<result column="taddress" property="address"></result>
<collection property="students" ofType="student" select="findById" column="tid"></collection>
</resultMap> <!--查询全部辅导员信息-->
<select id="findAll" resultMap="teacherMapper">
select * from teacher;
</select> <!--学生关系映射-->
<resultMap id="studentMapper" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
</resultMap>
<!--查询单个学生-->
<select id="findById" parameterType="Integer" resultMap="studentMapper">
select * from student;
</select>

②:集合的嵌套结果

<!--辅导员关系映射-->
<resultMap id="teacherMapper" type="teacher">
<id column="tid" property="id"></id>
<result column="tname" property="name"></result>
<result column="tsex" property="sex"></result>
<result column="tage" property="age"></result>
<result column="tsalary" property="salary"></result>
<result column="taddress" property="address"></result>
<collection property="students" ofType="student" column="tid">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
</collection>
</resultMap> <!--查询全部辅导员信息-->
<select id="findAll" resultMap="teacherMapper">
select s.*,t.tid,tname,t.tsex,t.tage,t.tsalary,t.taddress from
teacher t left join student s using(tid) ;
</select>

四:总结(必看细节之我的错误总结)

1:自我认知

对于我刚接触Mybatis的多表操作遇到了好多错误bug,俗话说遇到的错误越多越要开心,因为这是对你以后的成长,我也觉得这句话有点道理,通过这2天的操作,我对Mybatis的多表操作不能说全部都懂吧,但是在日常对数据进行简单的增删改查应该问题不大。说实话我使用Mybatis多表操作遇到过好几个小时都没解决的bug,查过好多资料,也发现网上和我出现过一模一样的问题,如 CSDN的一个人发的问题  其实那些后面评论的解决方法都是不可行的,不能真正解决此问题,接下来我就和大家谈谈我遇到的一些问题吧,针对mybatis的多表操作

2:遇到的问题及注意事项

①:在做一对一关联查询的时候,查询出来的关联对象一直为空?

  其实这个错误和我在上面发的CSDN的错误差不多,我先把我的代码展示出来,错误的代码

    <!--配置学生类映射关系-->
<resultMap id="studentMapper" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
<result column="tid" property="tid"></result>
<association property="family" javaType="family" column="fid" select="findByIdInFamily"></association>
</resultMap> <!--查询全部学生-->
<select id="findAll" resultMap="studentMapper">
select * from student;
</select> <!--查询单个家庭信息 通过关联查询查询出来-->
<select id="findByIdInFamily" parameterType="Integer" resultType="family">
select * from family where fid=#{id};
</select> <!--
为什么family都是为空呢?
Student{id=1, name='王生安', sex='女', age=22, credit=11.0, money=401.1, address='安徽六安', enrol='2019-01-08', family=null, tid=4}
Student{id=2, name='李鑫灏', sex='女', age=24, credit=4.0, money=902.8, address='安徽合肥', enrol='2019-03-17', family=null, tid=2}
Student{id=3, name='薛佛世', sex='女', age=21, credit=52.0, money=532.1, address='安徽蚌埠', enrol='2018-10-16', family=null, tid=2}-->

查询为空的代码

其实遇到这个问题是最烦人的,即不报错,也在网上很难找到,如果有个师傅带着话,相信很快解决,废话不多说了

问题分析:

首先我的数据库字段和对象字段不一样,这个是最关键的,在做一对一关联查询,我忽略了对family字段的映射,这就直接导致出现空,但是上文CSDN发布的问题是他直接在关联查询标签里面写映射关系,他的思路是对的,但是他忘了他那个是嵌套查询,如果是嵌套结果查询,对sql语句重写,语法更改一下是没有问题的

<!--配置学生类映射关系-->
<resultMap id="studentMapper" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
<result column="ssex" property="sex"></result>
<result column="sage" property="age"></result>
<result column="scredit" property="credit"></result>
<result column="smoney" property="money"></result>
<result column="saddress" property="address"></result>
<result column="senrol" property="enrol"></result>
<result column="tid" property="tid"></result>
<association property="family" javaType="family" column="fid" select="findByIdInFamily"></association>
</resultMap> <!--查询全部学生-->
<select id="findAll" resultMap="studentMapper">
select * from student;
</select>
<!--映射家庭关系-->
<resultMap id="familyMapper" type="family">
<id column="fid" property="id"></id>
<result column="fmember" property="member"></result>
<result column="fguardian" property="guardian"></result>
<result column="ftel" property="tel"></result>
<result column="fdad" property="dad"></result>
<result column="fmom" property="mom"></result>
<result column="faddress" property="address"></result>
</resultMap> <!--查询单个家庭信息 通过关联查询查询出来-->
<select id="findByIdInFamily" parameterType="Integer" resultMap="familyMapper">
select * from family where fid=#{id};
</select>

问题解决

注意事项:

①:其实我们在写这些关联查询的时候,实体类是不用把外键字段纳入实体类里面,如果粗心的话,操作不当会导致错误,我们如果一个对象包含另一个对象就直接使用对象作为类型

②:一对多和多对多本质是是一样的,一对多是对一方设置数据为List集合而多对多则是双向的