Mybatis(进阶部分)

时间:2024-10-01 21:50:16

四 Mybatis完成CURD(二)

4.5 多条件CRUD

        之前的案例中,接口里方法的形参个数都是1个;如果方法形参是两个或者两个以上时,MyBatis又该如何获取获取参数呢?

Mybatis提供了好几种方式,可以获取多个参数的值

第一种: 使用arg0,arg1…或者param1,param2…来按照参数顺序获取对应的值

 /*
        通过部门编号,领导编号,员工姓名,查询员工信息
     */
    Employee findByDeptnoAndMgrAndEname(int deptno, int mgr, String ename);

        接口里的方法与Sql映射文件中的语句进行映射后,并且在调用方法期间,Mybatis会默认将所有传入到方法中的实际参数封装到一个Map对象中,实际参数作为value,按照从左到右的顺序,分别绑定到key名为arg0,arg1…或者param1,param2…上。

因此我们在获取参数时,可以这样写

    <!--Employee findByDeptnoAndMgrAndEname(int deptno, int mgr, String ename);-->
<!--    方法的参数有多个时,可以不用子标签里书写parameterType属性-->
<!--    没有指定参数类型时,会默认从自动封装的Map对象中,寻找具体的值给?赋上
        使用arg0,arg1,arg2...
        或者使用param1,param2,param3...
-->
    <select id="findByDeptnoAndMgrAndEname" resultType="employee" resultMap="resultMap1" >
<!--        select * from emp where deptno = #{arg0} and mgr =#{param2} and ename=#{arg2}-->
<!--        select * from emp where deptno = #{arg0} and mgr =#{arg1} and ename=#{arg2}-->
        select * from emp where deptno = #{param1} and mgr =#{param2} and ename=#{param3}

    </select>

测试:

@Test
    public void test1(){
        //获取代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        /*
            当对应的Sql映射文件中,没有指定参数类型时,Mybatis会将实际参数封装到一个Map对象中
                arg0,arg1...作为key,
                实际参数从左到右依次作为value与key对应。
                还会再存储一组键值对:
                param1,param2....作为key
                实际参数从左到右依次作为value与key对应。
         */
        Employee scott = mapper.findByDeptnoAndMgrAndEname(30, 7698, "TURNER");
        System.out.println(scott);
    }

第二种:Map作为参数

map 集合:只需要保证 SQL 中的参数名和 map 集合的键的名称对应上,即可设置成功

/*
        如果在设计方法时,需要使用多个形参。为了将值传入到Mybatis的映射文件中的sql语句中,我们可以换一个思路:
        就是在设计方法时,形参设计成Map类型,在调用时,将各种实际参数封装到Map对象中,然后只需要传入Map参数即可

        注意,映射文件中,要使用parameterType:来指定形参为Map类型,map/Map
     */
    Employee findByMap(Map<String,Object> map);

映射文件:

<!--    Employee findByMap(Map<String,Object> map);-->
<!--    如果形参是Map类型,占位符中的名称必须是map的key,所以在封装成map对象时,尽量见名知意-->
    <select id="findByMap" resultType="Employee" resultMap="resultMap1" parameterType="map">
        select * from emp where deptno = #{deptno} and mgr = #{mgr} and empno = #{empno} and job = #{job}
    </select>

测试:

 @Test
    public void test2(){
        //获取代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        // 自定义一个map类型,用于封装浏览器传过来的各种值。
        Map<String,Object> map = new HashMap<>();
        map.put("deptno",30);
        map.put("mgr",7698);
        map.put("empno",7499);
        map.put("job","SALESMAN");
        //map在存储键值对时,key必须和占位符中的名字一样。因为占位符是通过key来从map中获取具体的value值,给?赋值。
        Employee byMap = mapper.findByMap(map);
        System.out.println(byMap);
    }

第三种:实体类作为参数

实体类封装参数:只需要保证 SQL 中的参数名和实体类属性名对应上,即可设置成功

实体类:

package com.mybatis.pojo;

public class ParamsType {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;

    public ParamsType() {}

    public ParamsType(Integer empno, String ename, String job, Integer mgr) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
    }

    @Override
    public String toString() {
        return "ParamsType{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                '}';
    }
}

接口:

/*
        如果在设计方法时,需要使用多个形参。除了使用Map类型外,还可以将这多个形参设计到一个实体类对象上。
        比如: 如果这些形参都是表对应的那个实体类,那就可以直接使用表对应的实体类,比如Employee
            还可以单独封装到一个参数实体类,专门用于传参用的,比如定义一个类型ParamsType。
        select * from emp where empno = ? and ename = ? and job = ? and mgr = ?

     */
    Employee findByEntity(ParamsType params);

 映射文件:

<!--     Employee findByEntity(ParamsType params);-->
    <select id="findByEntity" resultType="Employee" resultMap="resultMap1" parameterType="paramsType">
        select * from emp where empno=#{empno} and ename=#{ename} and job=#{job} and mgr=#{mgr}
    </select>

 测试代码

    @Test
    public void test3(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        ParamsType paramsType = new ParamsType(7499,"ALLEN","SALESMAN",7698);
        Employee byEntity = mapper.findByEntity(paramsType);
        System.out.println(byEntity);
    }

第四种:使用@Param注解命名参数

散装参数:需要使用 @Param (" SQL 中的参数占位符名称")

    /*
        如果在设计方法时,需要使用多个形参。除了上述方法之外,还可以使用注解@Param("占位符的名字")。
        这种方式就可以使用对应的形参给对应的占位符赋值了。非常方便。
     */
    Employee findByAnnotation(@Param("empno") int empno,
                              @Param("ename") String ename,
                              @Param("job") String jobb,
                              @Param("mgr") int mgrno);

 映射文件:

<!--    Employee findByAnnotation(@Param("empno") int empno,
                              @Param("ename") String ename,
                              @Param("job") String jobb,
                              @Param("mgr") int mgrno);-->
    <select id="findByAnnotation" resultType="Employee" resultMap="resultMap1">
        select * from emp where empno=#{empno} and ename=#{ename} and job=#{job} and mgr=#{mgr}

    </select>

测试类:

    @Test
    public void test4(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee byEntity = mapper.findByAnnotation(7499,"ALLEN","SALESMAN",7698);
        System.out.println(byEntity);
    }

4.6 动态SQL

        SQL语句会随着用户的输入和外部条件的变化而变化,我们称之为动态SQL。MyBatis对动态SQL有很强大的支持。

4.6.1 where/if标签

        if标签,是根据Test属性中的布尔表达式的值,从而决定是否执行包含在其中的SQL片段。如果判断结果为true,则执行其中的SQL片段;如果结果为false,则不执行其中的SQL片段

应用场景如图所示: 

存在的问题:第一个条件不需要逻辑运算符。  

案例演示:

在接口EmployeeMapper.java里添加如下方法

 /* ---where条件可能会随着用户的输入而改变的我们称之为动态sql----
        mybatis支持动态sql的写法
        第一个动态SQL: 应用场景: 浏览器在查询操作时,从多个条件中进行任意个组合条件进行查询,条件个数未知
    * */
    Employee findByConditionNum(Map<String,Object> map);

在映射文件EmployeeMapper.xml里配置如下

第一种方案:使用恒等式让所有条件格式都一样

<!--    动态sql的练习
    Employee findByConditionNum(Map<String,Object> map);
    用户有的时候,按照 员工编号查询....
        有的时候按照 员工姓名查询....
        有的时候按照 员工编号 和职位查询
        select * from emp where empno = ?
        select * from emp where ename = ?
        select * from emp where empno = ? and job = ?
-->
<!--    第一种写法:使用1=1恒成立的写法,来解决 可能多一个and关键字的问题
-->
    <select id="findByConditionNum" resultType="Employee" resultMap="resultMap1" parameterType="map">
        select * from emp
        where 1=1
        <if test="empno != null and empno!='' ">
            and empno = #{empno}
        </if>
        <if test="ename != null and ename!='' ">
            and ename = #{ename}
        </if>
        <if test="job != null and ename!='' ">
            and job = #{job}
        </if>

    </select>

 第二种方案:使用<where>标签替换where关键字。 注意:where标签会将第一个条件之前的连接符自动去掉

    
<-- 第二种写法:配合where标签, where标签的作用,用来连接条件,如果在第一个条件前有多余连接符号(and 、or)。会自动去掉 -->
        <select id="findByConditionNum" resultType="Employee" resultMap="resultMap1" parameterType="map">  
        select * from emp
        <where>
            <if test="empno!=null and empno!='' ">
                empno = #{empno}
            </if>
            <if test="ename!=null and ename!='' ">
                and ename = #{ename}
            </if>
            <if test="job!=null and job!='' ">
                and job = #{job}
            </if>
        </where>
    </select>

4.6.2 choose/when标签  

应用场景:

        choose(when,otherwise):类似于java的switch-case-default, 只要满足一个when,choose就结束了,如果都不满足,就会执行otherwise。

1)在接口EmployeeMapper里添加如下方法

/*
        第二个动态sql:  应用场景: 浏览器在查询操作时,只能从多个条件中选择一个条件进行查询。条件个数只有一个
     */
    Employee findByConditionOne(Map<String,Object> map);

2)在sql映射文件EmployeeMapper.xml里添加如下内容  

<!--    Employee findByConditionOne(Map<String,Object> map);-->
<!--    对应的标签:  choose-when-otherwise  与java的switch-case(break)-default功能一样,只能执行其中一个分支
                choose是父标签
                when是子标签,可以有多个,如果执行了某一个when,则结束choose
                otherwise是子标签,只能有一个,如果没有执行的when,则执行otherwise

    需求: 用户可能按照员工编号查询,也可能按照员工姓名查询,也可能按照奖金查询。
-->

    <select id="findByConditionOne" resultType="Employee" resultMap="resultMap1" parameterType="map">
        select * from emp where
        <choose>
            <when test="empno!=null and empno!='' ">
                empno = #{empno}
            </when>
            <when test="ename!=null and ename!='' ">
                ename = #{ename}
            </when>
            <when test="comm!=null and comm!='' ">
                comm = #{comm}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </select>

 3)测试

 /*模拟场景浏览器端只传过来一个条件进行查询*/
    @Test
    public void test6(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("comm",1400);
        Employee byConditionOne = mapper.findByConditionOne(map);
        System.out.println(byConditionOne);
    }

4.6.3 set/if标签

        当进行修改时,有些字段可能有选择的进行修改,这时我们就可以使用<set>标签 配合<if>标签来完成操作。set标签会自动将最后一个条件的逗号去掉。

 1)在接口EmployeeMapper里添加如下方法

/*
        第三个动态sql: 应用场景: 浏览器在修改信息时,修改的文本框的个数不定
     */
    void updateByCondition(Map<String,Object> map);

2)在sql映射文件EmployeeMapper.xml里添加如下内容  

<!--      void updateByCondition(Map<String,Object> map);

        标签:
            set/if两个标签的应用
            Set标签可以自动去掉最后一个字段后面的逗号。

        应用场景:  浏览器在修改信息时,可能修改了员工姓名,职位
                  也可能修改了员工的工资和奖金,总之就是修改的内容不一定是什么。
-->
    <update id="updateByCondition" parameterType="map">
        update emps
        <set>
            <if test="ename != null and ename != '' ">
                ename = #{ename},
            </if>
            <if test="job != null and job != '' ">
                job = #{job},
            </if>
            <if test="mgr != null and mgr>0">
                mgr = #{mgr},
            </if>
            <if test="hiredate != null and hiredate !='' ">
                hiredate = #{hiredate},
            </if>
            <if test="sal != null and sal >0">
                sal = #{sal},
            </if>
            <if test="comm != null and comm>=0">
                comm = #{comm},
            </if>
            <if test="deptId != null and deptId >0">
                deptno = #{deptId},
            </if>
        </set>
        where empno = #{empno}

    </update>

 3)测试

/*
        浏览器传过来的要修改的字段不一定是哪些
     */
    @Test
    public void test7(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("empno",7499);
        map.put("ename","TOM");
        map.put("job","ClEAR");
        map.put("sal",1500);
        mapper.updateByCondition(map);
        sqlSession.commit();
    }

4.6.4 foreach标签

<foreach>标签的使用, 比如进行集合查询等操作

1)在接口EmployeeMapper里添加如下方法

两种写法:直接传入字符串(使用${})和封装到集合中(foreach方法)

/*
            第四个动态sql:
            应用场景: 浏览器端可能使用了复选框来勾选要查询的某一个字段的多个值的数据,底层sql应该是一个集合查询
            比如:
            select * from emp where job in ('SALESMAN','ANALYST')
            传过来的某一个字段值不一定有几个
            select * from emp where ename in('tom','lily','lucy')

            empnos: "'tom','lily','lucy'"
     */
    List<Employee> findByOneColumnMuitlValue1(String empnos);
    /* foreach标签的应用:
            对一个集合进行遍历。

     */
    List<Employee> findByOneColumnMuitlValue2(@Param("enames") List<String> enames);

2)在EmployeeMapper.xml里添加如下内容

<!--    List<Employee> findByOneColumnMuitlValue1(String empnos);

        一个字段的多个值的查询操作:  一种变相的写法。
-->
    <select id="findByOneColumnMuitlValue1" resultType="Employee" parameterType="string" resultMap="resultMap1">
        select * from emp where empno in (${empnos})
    </select>
<!--    List<Employee> findByOneColumnMuitlValue2(List<String> enames);

        foreach标签: 用于遍历集合参数的。  通常用于(not) in 这种sql语句。
        属性:
            -collection: 用于指定方法的形参
            -open :  指定集合的开始符号,比如"("
            -close :  指定集合的结束符号,比如")"
            -item: 遍历期间的元素的存储位置,即变量   注意: foreach标签中要使用变量
            -separator: 分隔符,比如","
-->
    <select id="findByOneColumnMuitlValue2" resultType="Employee" resultMap="resultMap1">
        select * from emp
        where ename in
        <foreach collection="enames" open="(" close=")" item="ename" separator="," >
            #{ename}
        </foreach>
    </select>

3)测试

    @Test
    public void test8(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        String empnos = "7499,7521,7566";
//        String enames = "'TOM','WARD'";
        List<Employee> emps = mapper.findByOneColumnMuitlValue1(empnos);
        emps.forEach(System.out::println);
    }

    @Test
    public void test9(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<String> names = new ArrayList<>();
        names.add("TOM");
        names.add("WARD");
        names.add("KING");
        List<Employee> list = mapper.findByOneColumnMuitlValue2(names);
        list.forEach(System.out::println);
    }

4.7 不同返回值类型的查询

4.7.1 返回基本数据类型

/*
        查询student表中的记录个数
     */
    int selectCount();
    /*
<select id="selectCount" resultType="_int">
        select count(*) from student
    </select>

4.7.2 返回引用类型(实体类)

/*
        返回值为实体类的
     */

    Student findById(Integer id);
<!--    Student findById(Integer id);
        属性resultType: 书写方法的返回值类型  代理对象通过sql语句进行查询,然后将查询到的结果自动封装成返回值类型的实例-->
    <select id="findById" resultType="Student">
        select * from student where id=#{arg0022331kkkskk}
    </select>

4.7.3 返回List类型

 /*
        返回值为List类型
     */
    List<Student> findByPage(@Param("offset") int offset,@Param("pageSize") int pageSize);
<!--    List<Student> findByPage(@Param("page") int page,@Param("pageSize") int pageSize);
        返回值类型: 如果方法的返回值类型时List, 那么在属性resultType上只需要指定集合当元素类型的名字即可
    -->
    <select id="findByPage" resultType="Student">
        select * from student limit #{offset},#{pageSize}
    </select>

4.7.4 返回Map类型

/*
        返回值为Map类型
        应用场景: 将查询出来的信息封装成Map形式的样子。
            表的字段名作为key
            字段值作为value
     */
    Map<String,Object> findByName(String name);
<select id="findByName" resultType="Map">
        select * from student where name=#{name}
    </select>

4.7.5 返回Map实例的集合

 /*
    应用场景: 将查询出来的多条记录,封装到Map集合中

    条件:通过性别,开头查询出来多条记录, 封装到List集合中,泛型为Map
     */
    List<Map<String,Object>> findByGender(@Param("gender") String gender);
<select id="findByGender" resultType="Map">
        select * from student where gender=#{gender}
    </select>

4.7.6 返回Map的另一种情况

    /**
     * 1001  => {address=江南, gender=m, name=刘备, age=40, sid=1001}
     * 1002  => {address=上海, gender=m, name=关羽, age=35, sid=1002}
     * 1003  => {address=长春, gender=m, name=赵云, age=27, sid=1004}
     */
//    Map<String,Map<String,Object>>
    @MapKey("id")
    Map<Integer,Object> findByGender2(@Param("gender") String gender);
    <select id="findByGender2" resultType="Map">
        select * from student where gender=#{gender}
    </select>

4.7.7 返回Set集合

/*
    * 应用场景:  数据库里的记录可能有重复数据,  然后再进行查询时,想要进行去重操作,我们就可以设计方法的
    * 返回值为Set,  泛型为该表的实体类   注意:  实体类必须重写equals方法和hashCode方法。
    * */
Set<Student> findAllByDistinct();
<select id="findAllByDistinct" resultType="student">
   select * from student
</select>

4.8 特殊SQL的查询

4.8.1 模糊查询

在sql映射文件中的三种写法:

like  '%${username}%'
like concat('%',#{username},'%')
like "%"#{username}"%"

案例演示:

/*
        模糊查询:select * from student where name like '%备%'
     */
    List<Student> findByNameLike(@Param("shortName") String shortName);
<!--    select * from student where name like '%${shortName}%'-->
<!--    select * from student where name like concat('%',#{shortName},"%")-->

<!--    模糊匹配。-->
    <select id="findByNameLike" resultType="Student">
         select * from student where name like "%"#{shortName}"%"
    </select>

4.8.2 批量删除

两种方法:

        批量删除: 一般都是通过唯一标识符来删除的,浏览器上应该使用的是复选框。

        传入服务端的应该是一个唯一标识符的集合。

第一种处理方式 String ids = "1,2,3,4,5" 占位符应该使用${} delete from student where id in (${})

   void deleteBatch1(String ids);
    <delete id="deleteBatch1">
        delete from student where id in (${i})
    </delete>

第二种处理方式: 使用foreach标签

void deleteBatch2(@Param("ids") List<Integer> ids);
<!--    foreach中的collection属性默认为list list就是你传入的集合的。 如果没有指定(用@param指定),就会是默认的list-->
    <delete id="deleteBatch2" parameterType="list">
        delete from student where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id" >
            #{id}
        </foreach>
    </delete>

 4.8.3 动态指定表名

动态指定表名应该就是根据不同的表名实现不同的查询结果。

/*动态指定表名*/
    List<Student> findAllByTableName(String tableName);
<!--    动态指定表名:  站位符使用${}
        List<Student> findAllByTableName(String tableName);-->
    <select id="findAllByTableName" resultType="com.mybatis.pojo.Student">
        select  * from ${tableName}
    </select>

4.8.4 主键返回

自增长类型主键

主键返回:

         之前的写法: 想要向数据库中新增一条记录 insert into student (id,name,gender,age,address)values(null,'安其拉','f',40,'北京')

         自增主键会传值null,想要获取主键值还需要再去表中查询。

mybatis为我们提供了可以在添加时获取主键的方式。(useGenerateKeys,keyproperty)

 void insertStudent(Student student);
<!--
    应用场景: 向数据库保存记录时,同时返回这条记录的主键值。应用于后续代码。
        useGeneratedKeys: 是否要使用数据库中生产的该条记录的主键值,true表示使用,false表示不使用
        keyproperty: 用于指定生成的主键值,存储的位置,一般指的是对应的实体类的属性值。
-->
    <insert id="insertStudent" useGeneratedKeys="true" keyProperty="id">
        insert into student values(null,#{name},#{gender},#{age},#{address})
    </insert>

 测试:

@Test
public void test12() throws IOException {
   InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
   SqlSession sqlSession = factory.openSession();
   StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

   Student student = new Student("张鲁一",40,"男","2222233333","北京");
   mapper.insertStudent(student);
   System.out.println(student.getId());

   sqlSession.commit();
   sqlSession.close();

}

 非自增长类型主键

创建数据库的新表

use mybatis_db;
drop table if exists computer;
create table if not exists computer(
	id varchar(100) primary key,
	brand varchar(20),
   model varchar(20),
	price double
);

insert into computer values (1, 'ThinkPad','X1',12000);
insert into computer values (2, 'Mi','p1',6500);
insert into computer values (3, 'lenove','s1',4000);

commit;

select * from computer;

        而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!

使用 selectKey 帮助插入UUID作为字符串类型主键示例:

<insert id="insertUser" parameterType="User">
    <selectKey keyProperty="id" resultType="java.lang.String"
        order="BEFORE">
        SELECT UUID() as id
    </selectKey>
    INSERT INTO user (id, username, password) 
    VALUES (
        #{id},
        #{username},
        #{password}
    )
</insert>

        在上例中,我们定义了一个 insertUser 的插入语句来将 User 对象插入到 user 表中。我们使用 selectKey 来查询 UUID 并设置到 id 字段中。

        通过 keyProperty 属性来指定查询到的 UUID 赋值给对象中的 id 属性,而 resultType 属性指定了 UUID 的类型为 java.lang.String。

        需要注意的是,我们将 selectKey 放在了插入语句的前面,这是因为 MySQL 在 insert 语句中只支持一个 select 子句,而 selectKey 中查询 UUID 的语句就是一个 select 子句,因此我们需要将其放在前面。

        最后,在将 User 对象插入到 user 表中时,我们直接使用对象中的 id 属性来插入主键值。

        使用这种方式,我们可以方便地插入 UUID 作为字符串类型主键。当然,还有其他插入方式可以使用,如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。

4.9 级联查询

4.9.1 多对一查询

        多对一,指的是表与表之间的记录关系,比如学生信息表(`S_ID`,`S_NAME`,……,`T_ID`)与教师(班主任)信息表(`T_ID`,`T_NAME`,……)。多个学生是一个老师教的。通过学生信息表里的任意一条记录,都可以找到教师信息表里的对应的老师信息。   

        第一种写法:字段映射

        第二种写法:association(关联,联合)

        第三种写法:分步写法

/*通过员工id 查找员工信息和其所在部门信息
    * 两种写法在mapper文件中。
    * */
    Employee findById(Integer id);
 <!--    字段映射写法。column: 指定数据库表中的字段名,property: 指定实体类中的属性名,
        由于要给属性dept赋值,而dept是一个Dept类型,因此实际上是给dept的各个属性赋值。
        -->
    <resultMap id="result1" type="employee">
        <id property="id" column="empno"/>
        <result property="name" column="ename"/>
        <result property="job" column="job"/>
        <result property="mgr" column="mgr"/>
        <result property="hireDate" column="hiredate"/>
        <result property="sal" column="sal"/>
        <result property="bonus" column="comm"/>
        <result property="deptId" column="deptno"/>
        <result property="dept.deptno" column="deptno"/>
        <result property="dept.dname" column="dname"/>
        <result property="dept.loc" column="loc"/>
    </resultMap>
    <select id="findById" resultMap="result1">
        select * from emp e join dept d on e.deptno = d.deptno where e.empno = #{id}
    </select>

 第二种写法 association

 <resultMap id="result2" type="employee">
        <!--        id子标签,专门用于主键映射
                    column: 指定数据库表中的字段名
                    property: 指定实体类中的属性名
         -->
        <id property="id" column="empno"/>
        <result property="name" column="ename"/>
        <result property="job" column="job"/>
        <result property="mgr" column="mgr"/>
        <result property="hireDate" column="hiredate"/>
        <result property="sal" column="sal"/>
        <result property="bonus" column="comm"/>
        <result property="deptId" column="deptno"/>

<!--       多对一的查询的第二种写法: 使用association(级联,关联,联合)标签。
            property: 用于指定给实体类的哪个属性做映射。
            javaType: 用于指定该属性的java类型。
            association的子标签: 用于将字段与实体类的属性的属性进行映射关系。
-->
        <association property="dept" javaType="Dept">
            <result property="deptno" column="deptno"/>
            <result property="dname" column="dname"/>
            <result property="loc" column="loc"/>
        </association>
    </resultMap>


<!--    -->
    <select id="findById" resultMap="result2">
        select * from emp e join dept d on e.deptno = d.deptno where e.empno = #{id}
    </select>

 第三种 分步查询

 // 多对一的查询的第三种写法: 分步查询
    // 通过id 查询员工信息,同时查询出员工所对应的部门信息
    Employee findByIdFirstEmpSecondDept(Integer id);
/*
        多对一的第三种写法的第二步
     */
    Dept findByIdSecondStep(Integer deptno);

区别代码:

<association property="dept" select="com.mybatis.mapper.DeptMapper.findByIdSecondStep" column="deptno"/>
<!--
    多对一的分步写法的第二步
-->
    <select id="findByIdSecondStep" resultType="Dept">
        select * from dept where deptno = #{id}
    </select>
<resultMap id="result3" type="employee">
        <id property="id" column="empno"/>
        <result property="name" column="ename"/>
        <result property="job" column="job"/>
        <result property="mgr" column="mgr"/>
        <result property="hireDate" column="hiredate"/>
        <result property="sal" column="sal"/>
        <result property="bonus" column="comm"/>
        <result property="deptId" column="deptno"/>

        <!--       多对一的查询的第三种写法: 也是使用association(级联,关联,联合)标签。
                    不同再写association里面的子标签了
                    只需配置下面几个属性即可
                    property: 用于指定给实体类的关联属性(Dept dept)
                    select: 对应的是第二步查询,语法结构:namespace.id(另一个映射的namespace地址值加上映射里的方法id值)
                    column: 第二步的sql需要一个条件,column用于指定第一步查询中要作为第二个sql语句的字段名。
        -->
        <association property="dept" select="com.mybatis.mapper.DeptMapper.findByIdSecondStep" column="deptno"/>
    </resultMap>

    <select id="findByIdFirstEmpSecondDept" resultMap="result3">
        select * from emp where empno = #{id}
    </select>

4.9.2 一对多查询

        一对多,其实就是多对一的反向操作。教师信息表是主表,学生信息表是副表,通过教师信息表的任意一条记录,都可以在学生信息表里找到该教师的多个学生信息。

第一种写法:collection

 // 案例演示: 查询某一个部门的信息,及其所有员工的信息。
    Dept findByIdCollection(Integer deptno);
<!--    一对多的写法 collection-->
    <resultMap id="deptMap1" type="Dept">
        <result property="deptno" column="deptno"/>
        <result property="dname" column="dname"/>
        <result property="loc" column="loc"/>
        <collection property="emps" ofType="Employee">
            <id property="id" column="empno"/>
            <result property="name" column="ename"/>
            <result property="job" column="job"/>
            <result property="mgr" column="mgr"/>
            <result property="hireDate" column="hiredate"/>
            <result property="sal" column="sal"/>
            <result property="bonus" column="comm"/>
            <result property="deptId" column="deptno"/>
        </collection>
    </resultMap>



    <select id="findByIdCollection" resultMap="deptMap1">
        select * from dept d join emp e on d.deptno = e.deptno where d.deptno=#{deptno}
    </select>

第二种写法:分步写法

 /*
        一对多的第二种写法: 分步查询
     */
    Dept findByIdFirstStep(Integer deptno);
 // 一对多查询的第二种写法分步查询
    // 第二步:通过第一步查询的结果中的deptno字段进行查询员工信息
    List<Employee> findByIdSecondStep(Integer deptno);

 区别代码:

<collection property="emps" select="com.mybatis.mapper.EmployeeMapper.findByIdSecondStep" column="deptno"/>
    <resultMap id="deptMap2" type="Dept">
        <result property="deptno" column="deptno"/>
        <result property="dname" column="dname"/>
        <result property="loc" column="loc"/>
        <collection property="emps" select="com.mybatis.mapper.EmployeeMapper.findByIdSecondStep" column="deptno">
        </collection>
    </resultMap>


<!--    一对多查询的分步第一步-->
    <select id="findByIdFirstStep" resultMap="deptMap2">
        select * from dept where deptno=#{deptno}
    </select>
<resultMap id="result4" type="employee">
        <id property="id" column="empno"/>
        <result property="name" column="ename"/>
        <result property="job" column="job"/>
        <result property="mgr" column="mgr"/>
        <result property="hireDate" column="hiredate"/>
        <result property="sal" column="sal"/>
        <result property="bonus" column="comm"/>
        <result property="deptId" column="deptno"/>
    </resultMap>

    <select id="findByIdSecondStep" resultMap="result4">
        select * from emp where deptno = #{id}
    </select>

4.9.3 多对多查询

        一般多对多,都会涉及到第三张表。 学生信息表(每个学生的信息都是唯一的一条记录), 课程信息表(每个科目也都是唯一的一条记录),学生课程表(一个学生可以选择多个科目进行学习,一个科目可以被多个学生选择学习)。学生信息表和课程信息表通过学生课程表进行的对应关系,就是多对多的关系。

建表sql:

CREATE TABLE `course`  (
  `c_id` int(0) NOT NULL AUTO_INCREMENT COMMENT '课程ID',
  `c_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程名称',
  `t_id` int(0) NOT NULL COMMENT '授课教师ID',
  `c_academy` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '所属学院',
  `c_note` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程备注',
  PRIMARY KEY (`c_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES (1, '高数', 1, '信息学院', '高等数学,微积分');
INSERT INTO `course` VALUES (2, '英语', 2, '工程学院', '英语选修');
INSERT INTO `course` VALUES (3, 'JAVA', 3, '信息学院', '面向对象的编程语言');
INSERT INTO `course` VALUES (4, '食品安全', 1, '食品学院', '民以食为天');
INSERT INTO `course` VALUES (5, '土木建筑', 2, '工程学院', '桥梁,观景房');
INSERT INTO `course` VALUES (6, '体育', 2, '工程学院', '健身强体...');



CREATE TABLE `score`  (
  `s_id` int(0) NOT NULL COMMENT '学生ID',
  `c_id` int(0) NOT NULL COMMENT '课程ID',
  `score` int(0) NULL DEFAULT NULL COMMENT '课程分数',
  PRIMARY KEY (`s_id`, `c_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of score
-- ----------------------------
INSERT INTO `score` VALUES (1001, 1, 80);
INSERT INTO `score` VALUES (1001, 2, 90);
INSERT INTO `score` VALUES (1001, 3, 99);
INSERT INTO `score` VALUES (1002, 1, 70);
INSERT INTO `score` VALUES (1002, 2, 60);
INSERT INTO `score` VALUES (1002, 3, 80);
INSERT INTO `score` VALUES (1003, 1, 80);
INSERT INTO `score` VALUES (1003, 2, 80);
INSERT INTO `score` VALUES (1003, 4, 80);
INSERT INTO `score` VALUES (1004, 3, 50);
INSERT INTO `score` VALUES (1004, 4, 30);
INSERT INTO `score` VALUES (1004, 5, 20);
INSERT INTO `score` VALUES (1005, 5, 76);
INSERT INTO `score` VALUES (1005, 6, 87);
INSERT INTO `score` VALUES (1006, 5, 31);
INSERT INTO `score` VALUES (1006, 6, 34);
INSERT INTO `score` VALUES (1007, 4, 89);
INSERT INTO `score` VALUES (1007, 6, 98);

 StudentMapper接口

public interface StudentMapper {
    /*
        查询每个学生的基本信息,及其所学科目信息
     */
    List<Student> findAll();

    /*
        根据学生ID,查询学生的基本信息,及其所学科目信息(不包含成绩)
     */
    Student findById(Integer id);

    /*
        查询所有学生的学号,姓名,性别,年龄,课程号,选修科目名称,选修成绩
     */
    List<StudentInfo> findAllStudentInfo();
}

 mapper映射文件

<mapper namespace="com.mybatis.mapper.StudentMapper">

    <resultMap id="studentMap1" type="Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="gender" column="gender"/>
        <result property="address" column="address"/>
        <collection property="courses" ofType="course">
            <id property="cId" column="c_id"/>
            <result property="cName" column="c_name"/>
            <result property="tId" column="t_id"/>
            <result property="academy" column="c_academy"/>
            <result property="note" column="c_note"/>
        </collection>
    </resultMap>


    <select id="findAll" resultMap="studentMap1">
        select * from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
    </select>

    <select id="findById" resultMap="studentMap1">
        select * from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
        where s.id=#{id}
    </select>


    <resultMap id="studentMap2" type="studentInfo">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="gender" column="gender"/>
        <result property="cid" column="c_id"/>
        <result property="cName" column="c_name"/>
        <result property="score" column="score"/>
    </resultMap>
    <select id="findAllStudentInfo" resultMap="studentMap2">
        select s.id,s.name,s.age,s.gender,c.c_id,c.c_name,score
        from student s
        left join score sc on s.id = sc.s_id
        left join course c on sc.c_id = c.c_id
    </select>
</mapper>