SSM——6.Mybatis的关联映射

这篇文章我们将详细讲解一下Mybatis中的关联映射,然后我们根据具体实例来说明其中每个字段的用法与我们什么时候应该使用关联映射。

目录

1.单表字段的关联映射

 2.多对一的关联查询

2.1准备工作

2.2关联查询之分步查询的sql语句

2.3关联查询之嵌套查询的sql语句

3.一对多的关联查询

​编辑 3.1分步查询 

3.2嵌套查询

4.MyBatis延迟加载策略

4.1在主配置文件中设置

4.2在studentDao.xml当中设置分步查询

4.3 在TeacherDao.xml当中设置关于教师的查询

4.4 配置TeacherDao

4.5 访问设置

 4.6 一些特殊情况下的查询

 5.个人理解


1.单表字段的关联映射

这里我们先来看一下简单的单表字段映射。

观察下面的这个数据库的字段设计:

我们会发现一个问题:它每个字段前面都带有" t_ ",这对我们写User类的时候很不方便。

为什么呢?因为我们的User实体类中定义的字段要与数据库中的字段相对应。如果我们按数据库中的字段名来定义实体类中的字段,那么会违反java的命名规则,如果不按照数据库中的字段名来命名,那又会与其不对应,那怎么办呢?这是我们就要用到关联映射了。

首先,我们头铁一把,继续按照原先的来定义命名,如图所示:

 然后,我们在我们的UserDao.xml中对其进行映射,我们还是直接重新写一个方法来解释一下吧,代码如下:

<select id="findAllByMap" resultMap="resultMap01">
        select * from user
    </select>

    <resultMap id="resultMap01" type="com.qcby.entity.User">
        <id column="id" property="id" javaType="java.lang.Integer"/>
        <result column="t_username" property="username"/>
        <result column="t_birthday" property="birthday"/>
        <result column="t_sex" property="sex"/>
        <result column="t_address" property="address"/>
    </resultMap>

然后,我们来看下截图:

 说明:

第120行:resultMap 后面接的是我们映射表的名称,我们这里的映射表命名为resultMap01,所以填这个

第124行:下面就是我们的映射表了,他的作用是在数据库字段与实体类中的字段不一样时,将二者进行映射,加以绑定。id 叫做唯一标识符,后面接的是我们这个映射表的名称,这里要与上面的resultMap 后面的内容一致;type 指的是与那个实体类进行映射,我们这里是与User类进行的映射

第125行:这一行是比较特殊的,因为它算是id字段独有的,所以是以id为标签的。column 后面接的是我们数据库中的字段名称;property 后面接的是我们实体类中定义的,与之对应的字段名称;javaType 后面接的是property后面接的字段的类型,要写全类名。

第126行:result 表示这就是普通字段了,与前面的id字段区别开。column和property 依然是那个意思,注意 javaType其实写不写都无所谓,影响不大。

这个就是我们的单表的字段映射了。

然后,我们来写接口和测试方法,截图如下:

 最后,让我们运行测试一下:

 成功运行,问题不大。上面就是我们单表字段的关联映射了,很简单。

 2.多对一的关联查询

2.1准备工作

首先,让我们回忆一下数据库中表与表之间的关系:无、一对一、一对多(多对一)、多对多。

现在,让我们回忆一下一对多查询的sql语句如何写:

第一种:select 字段名 from 表一 left join 表二 on student.t_id = teacher.id

第二种:select 字段 from 表一,表二 where 表一.id = 表二.id

(一对多的关系中,我们只能在多表中设置外键)

好,现在sql语句会写,但是,你应该如何用Mybatis来实现?下面我们就来具体讲解一下。

第一步:建表,我们需要在数据库中建student表与teacher表,如下图所示:

 

 (可以直接执行建表操作,建表代码就不给你了)

第二步:插入数据(下图数据是我自己插入的,你们可以插入自己喜欢的数据)

 第三步:在项目的相关包下创建书写JavaBean文件(截图如下)

我这样写就真的对吗?Of Course Not !

为什么?因为javabean类的实际作用是传输值的,其中包括传递参数与接收参数。在Mybatis中,实体类的作用是给xml文件中的SQL语句传值或作为被封装的对象来接收SQL语句执行结果。而在这里,我们做的是关联查询,不管是结果还是参数,都有可能包括所关联的表的信息,那么这里所定义的字段信息就不够,是错误的。

正确的写法:(代码如下:)

teacher类的:

package com.qcby.entity;

public class Teacher {

    private Integer id;
    private String Tname;
    

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTname() {
        return Tname;
    }

    public void setTname(String tname) {
        Tname = tname;
    }



    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", Tname='" + Tname + '\'' +
                '}';
    }
}

 student类的:

package com.qcby.entity;

public class Student {

    private Integer id;
    private String Sname;
    private String sex;
    private Integer age;
    private Integer t_id;

    //这个是重点
    private Teacher teacher;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getSname() {
        return Sname;
    }

    public void setSname(String sname) {
        Sname = sname;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getT_id() {
        return t_id;
    }

    public void setT_id(Integer t_id) {
        this.t_id = t_id;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", Sname='" + Sname + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", t_id=" + t_id +
                ", teacher=" + teacher +
                '}';
    }
}

这里有两个问题需要说明一下:

1. student类中有Teacher类的对象,如果我在接收或传递参数时没有teacher的值,它会不会报错?

答:不会,会自动补位null值

2.为什么student类中有teacher对象,而teacher类中没有是student对象?

答:有两点。第一:与我们处理业务的逻辑有关,这两张表的关联逻辑是:一个老师对应多个学生,一个学生只有一个老师,所以在处理业务时,我们通常是从学生的角度出发的,从而关联出老师的信息,反过来,如果你从老师的角度来处理业务,那个最终的结果类型是什么样的?肯定是很复杂的,有普通类型有数组类型,不方便。第二,假设我们在teacher类中有创建student对象,而student类中又有teacher对象,这两者是不是闭环了?那我们实践运行时的结果会是什么样的?我也不知道,有空可以试一下。

2.2关联查询之分步查询的sql语句

第四步:我们来写xml文件(这里我们以查询每个学生所对应的老师姓名为例来写)

这里我们使用第一种关联查询的sql语句,即分布查询,sql语句如下:

select 字段 from 表一,表二 where 表一.id = 表二.id

下面我们来看一下具体的xml文件如何写(代码如下):

<select id="getStuTeacherName" resultMap="StudentTeacher01">
        select * from student
    </select>
    <resultMap id="StudentTeacher01" type="com.qcby.entity.Student">
        <id property="id" column="id"/>
        <result property="Sname" column="Sname"/>
        <result property="sex" column="sex"/>
        <result property="age" column="age"/>
        <result property="t_id" column="t_id"/>
        <association property="teacher" column="t_id" javaType="com.qcby.entity.Teacher" select="getTeacherName"/>
    </resultMap>
    <select id="getTeacherName" resultType="com.qcby.entity.Teacher">
        select * from teacher where id = #{t_id}
    </select>

书写出了的截图如下:

 下面我们来具体解释一下:

第12行:这是注释,我这里为了简单,所以后面查询时就直接用 * 来代替了,当然,你写的时候可以写具体字段名

第13行:这是很简单的从学生表中查数据。我们这个分步的关联查询的核心思想就是先从一个表中查出数据,然后从另一个表中查出数据,然后根据关联字段将两次查询的数据拼接起来,然后根据条件再返回呈现。后面的resultMap前面讲过,这里也不多提了。

第16行:这里就是resultMap的具体内容了,因为前面查询的主体是Student,所以后面的type类型就是student类了。

第17行:id字段的映射,没啥好说的。

第22行:这是重点,注意记笔记!

                association:处理对象类型字段的一个标签(在Mybatis中,复杂字段需要单独处理,而一般来说,这里的复杂字段就是对象类型的字段)

                property:后面接的是我们的字段名,这里接的是我们的复杂的字段名——teacher

                column:这里与前面的就有点不一样了,这里写的是我们的关联字段名,也就是说,你student表中是通过哪个字段与teacher表产生关联的。也可以理解为是向下一个sql语句传的值。这里有点不同,要注意一下

                javaType:后面写的是我们复杂字段的类型(一般都是对象类型的字段)

                select:后面接的是我们查询关联表中数据的sql语句的名称(即id),相当于一个方法调用。

第24行:这里是我们查询关联表的sql语句,这样要注意一点,因为我们使用的是关联查询,所以这里后面要加上关联的条件。

整理一下我们的思路:

我们这里做的是关联查询,用的sql语句是分步查询的sql语句。首先,我们在student表中查数据,因为是关联查询,所以student表中有复杂字段,我们用association来处理,然后关联调用查询teacher表的sql语句,在这条sql语句中,我们进行条件设置,也就是设置关联条件,将两表关联起来,最后,通过彼此间的关联和条件设置达到关联查询的目的。

第五步:我们来写接口(如下图所示):

 这没什么好说的。

第六步:我们来写测试方法(截图如下):

这也没啥好说的

第七步:运行,看参数传递和结果 

参数传递(日志信息):

运行结果:

 我们来分析下日志信息:

首先:idea向数据库传送一个查询student表的sql语句,没有参数,然后,数据库向idea传送字段名,然后传送第一条数据,然后,idea向数据库传送一条查询teacher表的SQL语句,并传入参数1(即t_id,即上面查询出来的第一条student数据的t_id字段,即两表关联的字段),然后,数据库向idea传送查询出的字段名以及数据和数量(total),然后继续查询student,t_id字段的值一直为1,直到t_id的值为2时,我们再次查询teacher表,返回数据,最后照此流程,直至student表中的数据查完,最后汇总输出,就是上面的运行结果。

2.3关联查询之嵌套查询的sql语句

首先,我们来写xml文件中的sql语句(代码如下):

 <select id="getStuTeacherName02" resultMap="StudentTeacher02">
        select student.Sname ,student.sex,teacher.Tname from student left join teacher on student.t_id = teacher.id
    </select>
    <resultMap id="StudentTeacher02" type="com.qcby.entity.Student">
        <id property="id" column="id"/>
        <result property="Sname" column="Sname"/>
        <result property="sex" column="sex"/>

        <association property="teacher" javaType="com.qcby.entity.Teacher">
            <id property="id" column="id"/>
            <result property="Tname" column="Tname"/>
        </association>

    </resultMap>

截图如下:

说明:

第29行:我们这种写法比较简单,就是直接在查询中写嵌套查询的sql语句,然后在resultMap中对字段进行封装。

第36行:前面讲过,这里与前面不同,这里更简单,property后面接的是复杂字段,也就是类类型的字段,然后下面直接对该字段的成员变量进行映射就行,没什么大问题。

拓展:

1. 我sql中查询x个字段,而我resultMap中也只写这x个字段,行不行? 行。

2. 我sql中查询x个字段,而我resultMap中也只写这x个字段,那返回值是什么样的?你JavaBean中定义多少字段,就返回多少字段。

3. 我sql中查询x个字段,而我resultMap中也只写这x个字段,那其余字段返回啥值? 返回null

4. 假设我sql中查询3个字段,而我resultMap中只写了其中2个字段,行不行?行

5. 假设我sql中查询3个字段,而我resultMap中只写了其中2个字段,那没写的字段返回啥? 返回null

6. 假设我sql中查询3个字段,而我resultMap中只写了其中4个字段,行不行?行

7. 那多写的字段返回啥?返回null

8. 最终返回有值的字段是哪些?是你查询的,和你在resultMap中写的 二者的交集,才有值,其余的都是null

然后,我们来写接口(截图如下):

 然后,写测试方法(截图如下):

 最后,我们看下结果:

以上就是我们嵌套查询的写法,建议使用这个,因为这个比较简单。、

3.一对多的关联查询

上面我们写的是多对一的关联查询。什么意思呢?就是查询每个学生对应的老师姓名,一个学生只对应一个老师,多个学生的老师也是一个,所以从这个角度来说,是多对一的。

下面我们来写下一对多的关联查询。什么意思呢?就是从老师的角度出发,一个老师对应多个学生,所以叫做一对多。下面我们来查询每个老师所教导的学生的姓名。

首先,我们修改一下我们的JavaBean类,截图如下:

Student类:

Teacher类:

 3.1分步查询 

然后,让我们来写xml文件中的sql语句(代码如下)

<select id="getTeacher01" resultMap="TeachersMap01">
        select teacher.Tname,teacher.id from teacher
    </select>
    <resultMap id="TeachersMap01" type="com.qcby.entity.Teacher">

        <id property="id" column="id"/>
        <result property="Tname" column="Tname"/>

        <collection property="students" column="id" javaType="ArrayList" ofType="com.qcby.entity.Student"
                    select="getStudent01"/>
    </resultMap>
    <select id="getStudent01" resultType="com.qcby.entity.Student">
        select student.Sname,student.sex,student.t_id from student where t_id = #{id}
    </select>

截图如下:

 说明:

第15行:collection :用来处理集合类型的特殊字段,我们在Teacher类中定义的students是List类型的,column:写的依然是关联字段,注意这里写的是student表中的关联字段,javaType:写类型,我们定义的students是List类型,所以写ArrayList(全类名);ofType:写集合中的具体泛型类型,我们这里是student。

拓展:

1. 第8行,我只查询Tname字段,可不可以?可以运行,但查不出学生信息。

2. 为什么?因为后面查学生表的sql语句的条件部分缺失参数。

3. 第19行,我不写student.t_id ,可不可以?结果对不对? 可以,结果是正确的。

4. 为什么?条件的匹配与你所查的字段无关。

5. 第12行,不写行不行?不行,原因结合上面的拓展和这里的第二问。

然后,我们写接口:

 然后,我们来写测试方法:

 最后,我们来看一下参数传递与结果截图:

 和前面写的差不多,没什么好说的。

3.2嵌套查询

这里我们用嵌套查询来写一下

首先,我们来写我们xml文件中的SQL语句(代码如下):

 <select id="getTeacher02" resultMap="TeachersMap02">
        select teacher.Tname,student.Sname ,student.sex from teacher LEFT join student on student.t_id = teacher.id
    </select>
    <resultMap id="TeachersMap02" type="com.qcby.entity.Teacher">
        <id property="id" column="id"/>
        <result property="Tname" column="Tname"/>

        <collection property="students" ofType="com.qcby.entity.Student">
            <result property="Sname" column="Sname"/>
            <result property="sex" column="sex"/>
        </collection>
    </resultMap>

截图如下:

 说明:

第23行:SQL语句还是那个,我就是为了好看换了下顺序,不换也是一样的。

第29行:前面讲过来,这里不多说了。

 然后,我们来看一下接口:

 然后,我们来看一下测试方法:

 最后,我们来看一下传参过程和运行结果:

 注意:两种查询的结果的显示方式是不一样的。

至于为什么,唔..........我暂时也清楚,待我研究透了,在回来写上。

4.MyBatis延迟加载策略

以上有两种写法来表示查询信息,分别是链表查询和分步查询的方法。那么既然我么能用一个SQL语句能够执行完,那为什么还要分开来写呢?

原因很简单:我们可以发现如果我们把他们连在一起那么他们就是一个多表查询语句,如果不放在一起执行,那那就是单独一个表的查询语句。但是这需要我们设置mybatis的延迟加载(懒加载)

分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoding:当开启时,任何方式的调用都会加载该对象的所有属性。否则,该属性会按需加载

此时就可以实现按需加载,需要获取的数据是什么,就只会执行相应的sql.此时会通过association和collection中的fetchType属性设置当前的分步查询是否使用懒加载

fetchType=“lazy(延迟加载) | eager(立即加载)”

简而言之:我们在分布查询时会写两条sql语句,通过懒加载,你可以调用其中的某一条SQL语句而不去出发另一条SQL语句,就这。

4.1在主配置文件中设置

我们在sqlMapConfig.xml文件中写下如下语句:

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

4.2在studentDao.xml当中设置分步查询

<select id = "getStudent"  resultMap="StudentTeacher">
    select * from student;
</select>
<!--结果映射集-->
<resultMap id="StudentTeacher"  type="com.qcby.entity.Student">
    <result property="id" column="id"/>
    <result property="Sname" column="Sname"/>
    <result property="sex" column="sex"/>
    <result property="age" column="age"/>
    <result property="t_id" column="t_id"/>
    <!-- select="getTeacher"  :调用下一个查询语句       -->
   <!-- select="com.qcby.dao.TeacherDao.getTeacher"  :调用下一个查询语句       -->
    <association property="teacher" column="t_id" javaType="com.qcby.entity.Teacher" 
                 select="com.qcby.dao.TeacherDao.getTeacher" fetchType="lazy"/>
</resultMap>

4.3 在TeacherDao.xml当中设置关于教师的查询

<select id="getTeacher" resultType="com.qcby.entity.Teacher" parameterType="java.lang.Integer">
    select  * from  teacher where id = #{t_id};    <!-- #{id}; 可以写任何东西,因为会自动匹配 t_id -->
</select>

4.4 配置TeacherDao

Teacher getTeacher(Integer id);

4.5 访问设置

①:只访问student当中的内容

@Test
public void getStudent(){
    List<Student> student = mapper.getStudent();
    for (Student student1:student) {
        System.out.println(student1.getSex());
    }
}

 ②:访问全部的内容和有关Teacher表当中的内容时

@Test
public void getStudent(){
    List<Student> student = mapper.getStudent();
    for (Student student1:student) {
       // System.out.println(student1);
        System.out.println(student1.getTeacher().getTname());
    }
}

 4.6 一些特殊情况下的查询

我们在主配置文件当中设置的懒加载,对于任何一个分步查询都是有效的,但是在一些特殊的情况下,我们希望有些语句不分开查询,这个时候我们就需要设置该语句只能立即加载

<select id = "getStudent"  resultMap="StudentTeacher">
    select * from student;
</select>
<!--结果映射集-->
<resultMap id="StudentTeacher"  type="com.qcby.entity.Student">
    <result property="id" column="id"/>
    <result property="Sname" column="Sname"/>
    <result property="sex" column="sex"/>
    <result property="age" column="age"/>
    <result property="t_id" column="t_id"/>
    <!-- select="com.qcby.dao.TeacherDao.getTeacher"  :调用下一个查询语句       -->
    <!-- column="t_id" 两个表的关联字段-->
    <!--fetchType="eager" 立即加载-->
    <association property="teacher" column="t_id" javaType="com.qcby.entity.Teacher"
                 select="com.qcby.dao.TeacherDao.getTeacher" fetchType="eager"/>
</resultMap>

测试语句

@Test
public void getStudent(){
    List<Student> student = mapper.getStudent();
    for (Student student1:student) {
        System.out.println(student1.getSex());
    }
}

 5.个人理解

关联映射,说白了,就是用Mybatis解决关联查询的一种方式。想一想,我们关联查询的SQL语句是怎样的?有两种方式,一种是嵌套查询,就是left join ...on...那种,另一种是 ...from 表一,表二where 条件。

我们先说第一种方式,用Mybatis来写很简单,SQL语句照写,然后用resultMap来做结果集的映射,其中复杂字段特殊处理,要用 association来处理,然后property写字段名,javaType写类型,下面在写复杂字段中所包含的字段,顶多再来个ofType写具体的泛型类型,就这,没啥。

再看第二种方式,也称分步查询。咱先在第一张表查,查完后再在第二张表查,然后根据关联字段,将两张表中查询的数据联系起来,进行输出。想一想怎么写,首先,从第一张表中查,然后用resultMap来做结果集的映射,其中复杂字段特殊处理,要用 association来处理,然后property写字段名,javaType写类型,column写关联字段(也就是第一张表给第二张表传递的条件值),select写查第二张表的SQL的id名,然后我们写查第二张表的语句,注意这里要用到关联条件,关联查询关联查询,你肯定要用到关联条件。然后,就这,也没啥。

再看看懒加载。懒加载也叫延迟加载,是针对分步查询用的。为什么?因为你分步查询分步了啊,我想单独调用你的第一步或第二步,那就用到懒加载了啊,就这。怎么做呢?先写配置,然后,我们对于分步查询来说,可以将两步分开写,但是要注意,你要某一步中要保留入参的位置,这样,我执行第一步后,可以调用第二步的方法,然后将我第一步查询的某个值作为入参进行第二步的查询。也就是说:合,咱是关联查询,分,咱可以各干各的。

说一下它的应用场景(没实验过):我们在A.xml文件中写SQL1,完成对A表的某一查询,然后在B.xml中写SQL2,完成对B表的某一查询,然后,A表与B表是关联的,我想从A.xml中,通过A表查到B表的内容,这时,我会在A.xml中的某个具体SQL语句下调用B.xml中的SQL2(实际是调用接口),这样,就实现了从A表中查到B表内容的操作。但前提是B.xml中要有入参的位置,供AB表的关联字段进行传递用。

至于关联映射的一些小的注意事项啊,懒加载的具体设置啊,可以看上面的内容,这里不细说。

就这,很简单,有问题吗?没有问题!

以上内容仅为个人理解,如有不正确的地方,希望各位大佬指出!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L纸鸢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值