MyBatis多表查询

目录

1. 多表关系回顾

2. 一对一查询

2.1 一对一多表查询方式一:基于标签进行手动结果映射封装 

2.2 一对一多表查询方式二 - 通过标签来封装属性中所关联的对象

3. 一对多查询

4. 多对多查询


1. 多表关系回顾

  • 在项目开发当中一对一关系的表不常见,因为一对一关系的两张表通常会合并为一张表。

2. 一对一查询

一张表对应一个实体类,一个实体类对应一个Mapper接口。 

例如:查询菜品,同时查询出该菜品所属的分类。

分析:查询菜品是主查询,因此将结果封装到菜品当中,直接在菜品类的属性当中声明一个菜品分类的对象。 

Mybatis多表查询的难点是难在于怎样进行查询数据的封装!

Dish

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 菜品表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dish {
    /** 主键ID */
    private Integer id;
    /** 菜品名称 */
    private String name;
    /** 菜品分类ID */
    private Integer categoryId;
    /** 菜品图片 */
    private String image;
    /** 描述信息 */
    private String description;
    /** 状态:0停售,1起售 */
    private Short status;
    /** 创建时间 */
    private LocalDateTime createTime;
    /** 更新时间 */
    private LocalDateTime updateTime;

    /** 记录当前菜品所属的分类信息 */
    private Category category;
}

Category

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 分类表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Category {
    /** 主键ID */
    private Integer id;
    /** 分类名称 */
    private String name;
    /** 类型 1-菜品分类 2-套餐分类*/
    private Short type;
    /** 顺序 */
    private Integer sort;
    /** 状态 0-禁用 1-启用 */
    private Short status;
    /** 创建时间 */
    private LocalDateTime createTime;
    /** 更新时间 */
    private LocalDateTime updateTime;
}

DishMapper 

package com.gch.mapper;

import com.gch.pojo.Dish;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface DishMapper {
    /**
     * 查询菜品,同时查询出该菜品所属的分类
     * @return 将查询出来的多条结果封装到List集合当中
     */
    public List<Dish> findAllDishWithCategory();
}

DishMapper.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="com.gch.mapper.DishMapper">
    <!--查询菜品,同时查询出该菜品所属的分类-->
    <select id="findAllDishWithCategory" resultType="com.gch.pojo.Dish">
        select dish.*,
               c.id,
               c.name as     分类名称,
               c.type        类型,
               c.sort        顺序,
               c.status      状态,
               c.create_time 创建时间,
               c.update_time 更新时间
        from dish
                 left join category c on dish.category_id = c.id
    </select>
</mapper>

注意:记得在配置文件当中来更新数据库的连接 

测试类:

    @Autowired
    private DishMapper dishMapper;

    @Test
    public void testFindAllDishWithCategory() {
        List<Dish> dishList = dishMapper.findAllDishWithCategory();
        dishList.forEach(s ->{
            System.out.println(s);
        });
    }

运行测试方法,看控制台输出:

可以看到catrgory为null,说明查询出来的菜品所属的分类信息没有被正确封装! 

为什么category为null呢?

  • 原因就在于最后是把查询出来的结果封装到Dish对象当中,但是Dish对象当中的属性名就是category,与数据库当中Category表中的字段名完全不一致,因此封装不上。 

2.1 一对一多表查询方式一:基于标签<resultMap>进行手动结果映射封装 

  • 既然默认的规则{实体类类名与表中字段名相同}不能给我们自动封装上,那我们就需要手动封装! 
  • 这里要用到一个标签 - <resultMap>,Map是映射的意思,resultMap - 结果映射!

<?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="com.gch.mapper.DishMapper">
    <!--定义resultMap,手动封装 type属性代表单条记录所封装的类型-->
    <resultMap id="dishMap" type="com.gch.pojo.Dish">
        <!--column-字段名  property-属性名-->
        <!--表中字段名(如果下面sql语句有给字段起别名,就用别名)与实体类当中的属性名保持一致-->
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="category_id" property="categoryId"></result>
        <result column="image" property="image"></result>
        <result column="description" property="description"></result>
        <result column="status" property="status"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>

        <result column="主键ID" property="category.id"></result>
        <result column="分类名称" property="category.name"></result>
        <result column="类型" property="category.type"></result>
        <result column="顺序" property="category.sort"></result>
        <result column="状态" property="category.status"></result>
        <result column="创建时间" property="category.createTime"></result>
        <result column="更新时间" property="category.updateTime"></result>
    </resultMap>
    <!--查询菜品,同时查询出该菜品所属的分类-->
    <select id="findAllDishWithCategory" resultMap="dishMap">
        select dish.*,
               c.id          as 主键ID,
               c.name        as 分类名称,
               c.type        as 类型,
               c.sort        as 顺序,
               c.status      as 状态,
               c.create_time as 创建时间,
               c.update_time as 更新时间
        from dish
                 left join category c on dish.category_id = c.id
    </select>
</mapper>

再次运行测试方法,看控制台查询结果,已经成功封装! 

注意:

  • 由于上述字段符合规则,因此也可以删掉名,但是为了可读性,因此我们没有删除。
  • 还有下面爆红的字段,这是IDEA的误报。

 

 

2.2 一对一多表查询方式二 - 通过<association>标签来封装属性中所关联的对象

<?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="com.gch.mapper.DishMapper">

    <!-- 方式一 -->
    <!--定义resultMap,手动封装 type属性代表单条记录所封装的类型-->
    <resultMap id="dishMap1" type="com.gch.pojo.Dish">
        <!--column-字段名  property-属性名-->
        <!--表中字段名(如果下面sql语句有给字段起别名,就用别名)与实体类当中的属性名保持一致-->
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="category_id" property="categoryId"></result>
        <result column="image" property="image"></result>
        <result column="description" property="description"></result>
        <result column="status" property="status"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>

        <result column="主键ID" property="category.id"></result>
        <result column="分类名称" property="category.name"></result>
        <result column="类型" property="category.type"></result>
        <result column="顺序" property="category.sort"></result>
        <result column="状态" property="category.status"></result>
        <result column="创建时间" property="category.createTime"></result>
        <result column="更新时间" property="category.updateTime"></result>
    </resultMap>

    <!-- 方式二-->
    <resultMap id="dishMap2" type="com.gch.pojo.Dish">
        <!-- column-字段名 property-属性名-->
        <!-- 表中字段名(如果下面SQL查询语句有给字段起别名,就用别名)与实体类当中的属性名保持一致-->
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="category_id" property="categoryId"></result>
        <result column="image" property="image"></result>
        <result column="description" property="description"></result>
        <result column="status" property="status"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>
        <!-- assocation标签封装的是一个对象类型 property指定要封装对象的属性名
        javaType属性来指定property属性是什么类型的,也就是要填写该属性的全类名-->
        <association property="category" javaType="com.gch.pojo.Category">
            <id column="主键ID" property="id"></id>
            <result column="分类名称" property="name"></result>
            <result column="类型" property="type"></result>
            <result column="顺序" property="sort"></result>
            <result column="状态" property="status"></result>
            <result column="创建时间" property="createTime"></result>
            <result column="更新时间" property="updateTime"></result>
        </association>
    </resultMap>

    <!--查询菜品,同时查询出该菜品所属的分类-->
    <select id="findAllDishWithCategory" resultMap="dishMap2">
        select dish.*,
               c.id          as 主键ID,
               c.name        as 分类名称,
               c.type        as 类型,
               c.sort        as 顺序,
               c.status      as 状态,
               c.create_time as 创建时间,
               c.update_time as 更新时间
        from dish
                 left join category c on dish.category_id = c.id
    </select>
</mapper>

运行测试方法,验证结果是否正确:

如果在写属性的全类名时,想省略前面的包名不写,那么就可以开启MyBatis的实体类别名配置: 

#MyBatis配置(关键字camel、log-impl)
mybatis:
  configuration:
    #开启MyBatis的日志输出{配置MyBatis的日志,指定输出到控制台}
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #开启数据库表字段 到 实体类属性的驼峰映射 a_column ---> aColumn
    map-underscore-to-camel-case: true
  #开启MyBatis的实体类别名配置,相当于如果要写该包下类的全类名,直接写类名即可,前面的包名可以省略
  #别名就是类名的简写,并且首字母不区分大小写
  type-aliases-package: com.gch.pojo

3. 一对多查询

SQL语句: 

-- 查询所有分类,同时查询出该分类下的菜品数据
select category.*,
       dish.id          as 主键ID,
       dish.name        as 菜品名称,
       dish.category_id as 菜品分类ID,
       dish.price       as 菜品价格,
       dish.image       as 菜品图片,
       dish.description as 描述信息,
       dish.status      as 状态,
       dish.create_time as 创建时间,
       dish.update_time as 更新时间
from category
         left outer join dish on category.id = dish.category_id;

Category 

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 分类表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Category {
    /** 主键ID */
    private Integer id;
    /** 分类名称 */
    private String name;
    /** 类型 1-菜品分类 2-套餐分类*/
    private Short type;
    /** 顺序 */
    private Integer sort;
    /** 状态 0-禁用 1-启用 */
    private Short status;
    /** 创建时间 */
    private LocalDateTime createTime;
    /** 更新时间 */
    private LocalDateTime updateTime;

    /** 一个分类对应多个菜品,记录该分类下的菜品集合 */
    private List<Dish> dishList = new ArrayList<>();
}

细节:如果定义集合,一般需要初始化一下,防止拿到的结果为null。 

Dish 

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 菜品表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dish {
    /** 主键ID */
    private Integer id;
    /** 菜品名称 */
    private String name;
    /** 菜品分类ID */
    private Integer categoryId;
    /** 菜品图片 */
    private String image;
    /** 描述信息 */
    private String description;
    /** 状态:0停售,1起售 */
    private Short status;
    /** 创建时间 */
    private LocalDateTime createTime;
    /** 更新时间 */
    private LocalDateTime updateTime;

    /** 记录当前菜品所属的分类信息 */
    private Category category;
}

CategoryMapper

package com.gch.mapper;

import com.gch.pojo.Category;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface CategoryMapper {
    /**
     * 查询所有分类,同时查询出该分类下的菜品数据
     * @return
     */
    public List<Category> findAllCategoryWithDish();
}

CategoryMapper.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="com.gch.mapper.CategoryMapper">

    <!-- 定义resultMap,手动封装,type属性代表单条记录所封装的类型-->
    <resultMap id="categoryMap" type="category">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="type" property="type"></result>
        <result column="sort" property="sort"></result>
        <result column="status" property="status"></result>
        <result column="create_time" property="createTime"></result>
        <result column="update_time" property="updateTime"></result>
        <!-- 封装单个对象用<association>标签,封装集合用<collection>标签-->
        <!-- property指的是集合的属性名 ofType属性指的是集合当中单条记录所封装的类型-->
        <collection property="dishList" ofType="Dish">
            <id column="主键ID" property="id"></id>
            <result column="菜品名称" property="name"></result>
            <result column="菜品分类ID" property="categoryId"></result>
            <result column="菜品图片" property="image"></result>
            <result column="描述信息" property="description"></result>
            <result column="状态" property="status"></result>
            <result column="创建时间" property="createTime"></result>
            <result column="更新时间" property="updateTime"></result>
        </collection>
    </resultMap>

    <!-- 查询所有分类,同时查询出该分类下的菜品数据-->
    <select id="findAllCategoryWithDish" resultMap="categoryMap">
        select category.*,
               dish.id          as 主键ID,
               dish.name        as 菜品名称,
               dish.category_id as 菜品分类ID,
               dish.image       as 菜品图片,
               dish.description as 描述信息,
               dish.status      as 状态,
               dish.create_time as 创建时间,
               dish.update_time as 更新时间
        from category
                 left outer join dish on category.id = dish.category_id;
    </select>
</mapper>

测试方法:

    @Autowired
    private CategoryMapper categoryMapper;

    @Test
    public void testFindAllCategoryWithDish(){
        List<Category> categoryList = categoryMapper.findAllCategoryWithDish();
        categoryList.forEach(s ->{
            System.out.println(s);
        });
    }

运行测试方法,查看控制台输出结果:

 

4. 多对多查询

学生表、课程表、学生课程关系表。

注意:学生课程这张中间关系表它不是业务表! 

--  ======================================多对多=============================
create table tb_student(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '姓名',
    no varchar(10) comment '学号'
) comment '学生表';
insert into tb_student(name, no) values ('黛绮丝', '2000100101'),('谢逊', '2000100102'),('殷天正', '2000100103'),('韦一笑', '2000100104');


create table tb_course(
   id int auto_increment primary key comment '主键ID',
   name varchar(10) comment '课程名称'
) comment '课程表';
insert into tb_course (name) values ('Java'), ('PHP'), ('MySQL') , ('Hadoop');


create table tb_student_course(
   id int auto_increment comment '主键' primary key,
   student_id int not null comment '学生ID',
   course_id  int not null comment '课程ID',
   constraint fk_courseid foreign key (course_id) references tb_course (id),
   constraint fk_studentid foreign key (student_id) references tb_student (id)
)comment '学生课程中间表';

insert into tb_student_course(student_id, course_id) values (1,1),(1,2),(1,3),(2,2),(2,3),(3,4);

 

SQL 

-- 查询所有学生,同时查询出该学生选修的所有课程
select tb_student.*, tc.id as 主键ID, tc.name as 课程名称
from tb_student
         left outer join tb_student_course as tsc on tb_student.id = tsc.student_id
         left outer join tb_course as tc on tsc.course_id = tc.id;

细节:用到了两次左连接! 

Student 

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

/**
   学生表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    /** 主键ID */
    private Integer id;
    /** 姓名 */
    private String name;
    /** 学号 */
    private String no;

    /** 定义集合,记录该学生选修的所有课程 */
    public List<Course> courseList = new ArrayList<>();
}

Course 

package com.gch.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
   课程表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Course {
    /** 主键ID */
    private Integer id;
    /** 课程名称 */
    private String name;
}

StudentMapper

package com.gch.mapper;

import com.gch.pojo.Student;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface StudentMapper {
    /**
     * 查询所有学生,同时查询出该学生选修的所有课程
     * @return
     */
    public List<Student> findAllStudentWithCourse();
}

StudentMapper.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="com.gch.mapper.StudentMapper">

    <!-- 定义resultMap,手动封装,type属性代表的单条记录所封装的类型-->
    <resultMap id="studentMap" type="student">
        <id property="id" column="id"></id>
        <result column="name" property="name"></result>
        <result column="no" property="no"></result>
        <!-- 封装集合用<collection>标签,property指的是集合的名称,ofType属性指的是集合当中单条记录所封装的类型-->
        <collection property="courseList" ofType="Course">
            <id column="主键ID" property="id"></id>
            <result column="课程名称" property="name"></result>
         </collection>
    </resultMap>

    <!-- 查询所有学生,同时查询出该学生选修的所有课程-->
    <select id="findAllStudentWithCourse" resultMap="studentMap">
        select tb_student.*, tc.id as 主键ID, tc.name as 课程名称
        from tb_student
                 left outer join tb_student_course as tsc on tb_student.id = tsc.student_id
                 left outer join tb_course as tc on tsc.course_id = tc.id;
    </select>

</mapper>

编写测试方法,运行测试方法:

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testFindAllStudentWithCourse(){
        List<Student> studentList = studentMapper.findAllStudentWithCourse();
        studentList.forEach(s ->{
            System.out.println(s);
        });
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Surpass余sheng军

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

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

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

打赏作者

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

抵扣说明:

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

余额充值