MyBatis关联查询的实现

 更新时间:2026年04月23日 15:10:46   作者:二哈喇子!  
本文围绕MyBatis展开,介绍了关联查询,包括多对一关联association和一对多关联collection的使用方法及代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

关联查询是指在一个查询中同时获取多个表中的数据,将它们结合在一起进行展示。

关联表需要两个及以上的表

数据库代码:

DROP TABLE IF EXISTS student;
DROP TABLE IF EXISTS teacher;
CREATE TABLE teacher
(
   tid    int PRIMARY KEY auto_increment,
   tname  VARCHAR(30)
);
CREATE TABLE student
(
   sid    int PRIMARY KEY auto_increment,
   sname  VARCHAR(30),
   age    int,
   tid    int,
   foreign key (tid)  references teacher(tid)
);

生成实体类代码:

Student :

public class Student {
	private Integer sid;
	private String sname;
	private Integer age;
	// 方便以后的扩展   po类
	private Teacher teacher;
// 自动生成 Getter、Setter、toString()、有参无参方法 
}

Teacher :

public class Teacher {
	private Integer tid;
	private String tname;
// 自动生成 Getter、Setter、toString()、有参无参方法
}

提示:以下是本篇文章正文内容,下面案例可供参考

  1. 主键列标记是id
  2. 普通列标记是result
  3. 关联查询的映射标记是association

多对一关联 association

<association>标签用于描述一个一对一的关联关系,它可以嵌套在<select>语句中,查询指定的关联表数据。

多个学生关联一个老师

在 main/java 文件夹中创建学生和老师的接口以及同名的映射文件

其中映射文件中的namespace是接口文件中的限定名

查询所有的学生数据,同时关联出对应的老师

所以在学生接口文件中:

public interface StudentMapper {
	List<Student> queryAllStudents();
}

在学生的映射文件中:SQL语句用的SQL99写法

<mapper namespace="com.mybatis.mapper.StudentMapper">
								<!-- 结果的映射 -->
	<select id="queryAllStudents" resultMap="st">
		select sid,sname,age,t.tid,tname
		from student s join teacher t
		on s.tid = t.tid
	</select>
</mapper>

SQL语句写完后要在Navicat中单独运行一下:防止有错

因为 t.tid,tname 和 teacher 数据不一致,所有需要在映射文件中加一个 resultMap 标记

需要把不一致的那两列单独映射到 teacher 属性里

所以最终学生的映射文件中的代码:

<mapper namespace="com.mybatis.mapper.StudentMapper">
						  <!-- st:随便起名 -->
	<resultMap type="Student" id="st">
	<!-- 主键列用id标记 -->
		<id column="sid" property="sid"/>
	<!-- 普通列用result标记 -->
		<result column="sname" property="sname"/>
		<result column="age" property="age"/>
	<!-- 关联查询的映射标记是association -->
		<association property="teacher" javaType="Teacher">
			<id column="tid" property="tid"/>
			<result column="tname" property="tname"/>
		</association>
	</resultMap>
								<!-- 结果的映射与上面的id值对应 -->
	<select id="queryAllStudents" resultMap="st">
		select sid,sname,age,t.tid,tname
		from student s join teacher t
		on s.tid = t.tid
	</select>
</mapper>

测试类代码:

@Test 
	public void queryStudent() {
		StudentMapper mapper = session.getMapper(StudentMapper.class);
		List<Student> list = mapper.queryAllStudents();
		//	foreach提示
		for (Student student : list) {
			System.out.println(student);
		}
	}

一对多关联 collection

<collection>标签用于描述一个一对多的关联关系,它可以嵌套在<select>语句中,查询关联表数据集合。

一个老师关联多少个学生

查询老师数据时,同时关联出老师对应的学生

这里要更老师的 po 类,加上:

// 数组和集合能体现多个数据,这里用集合,因为不确定有多少学生
	private List<Student> students;
	// 生成Getter、Setter

所以在老师接口文件中:首先在接口中定义一个方法(查询所有老师)

List<Teacher> queryAllTeachers();

在老师的映射文件中:
首先加上 namespace,映射文件中的namespace是接口文件中的限定名

<select id="queryAllTeachers">
		select t.tid,tname,sid,sname,age
		from teacher t join student s 
		on t.tid = s.tid
	</select>

SQL语句写完后要在Navicat中单独运行一下:防止有错

接下来配置映射:

<!-- 结果映射 -->
	<resultMap type="Teacher" id="teacher">
		<id column="tid" property="tid"/>
		<result column="tname" property="tname"/>
<!-- 一对多映射使用collection标记 --><!-- 属性名property是老师po类的students -->
		<collection property="students" ofType="Student">
			<id column="sid" property="sid"/>
			<result column="sname" property="sname"/>
			<result column="age" property="age"/>
		</collection>
	</resultMap>

最后要在 select 标签内加一个 resultMap,因为结果是自己单独配置的

所以最终老师的映射文件中的代码:

<mapper namespace="com.mybatis.mapper.TeacherMapper">
<!-- 结果映射 -->
	<resultMap type="Teacher" id="teacher">
		<id column="tid" property="tid"/>
		<result column="tname" property="tname"/>
<!-- 一对多映射使用collection标记 --><!-- 属性名property是老师po类的students -->
		<collection property="students" ofType="Student">
			<id column="sid" property="sid"/>
			<result column="sname" property="sname"/>
			<result column="age" property="age"/>
		</collection>
	</resultMap>
	<select id="queryAllTeachers" resultMap="teacher">
		select t.tid,tname,sid,sname,age
		from teacher t join student s 
		on t.tid = s.tid
	</select>
</mapper>

接下来写测试类

@Test 
	public void queryTeacher() {
		TeacherMapper mapper = session.getMapper(TeacherMapper.class);
		List<Teacher> list = mapper.queryAllTeachers();
		// foreach循环
		for (Teacher teacher : list) {
			// 只输出老师数据
			System.out.println(teacher);
			// 得到学生
			List<Student> sl = teacher.getStudents();
			// 遍历sl学生集合,看看老师关联的学生数据
			for (Student student : sl) {
				System.out.println(student);
			}
		}
	}

resultMap元素 处理结果集映射

当数据表中的列和需要返回的对象的属性不完全一致, MyBatis是不会自动赋值的。此时,就可以使用<resultMap>元素进行处理。

<resultMap>元素的子元素<id>用于表示哪个列是主键,而<result>元素用于表示POJO和数据表中普通列的映射关系。

<resultMap id="custResultMap" type="Customer">
       <id property="id" column="id" />
       <result property="custId" column="cust_id"/>
       <result property="name" column="name"/>
</resultMap>

三表连接查询案例

效果:

用户登录选择体检套餐,男用户选择男士套餐,女用户选择女士套餐

在数据库中的对应关系:

po 类:

po类中要写三个po,因为有三个表

套餐明细表:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
//自动生成Getter、Setter和toString()方法
@Data  
//自动生成无参构造函数
@NoArgsConstructor  
//自动生成所有参数构造函数
@AllArgsConstructor 
//自动生成Builder模式代码
@Builder    
public class Setmealdetailed {
  private Long sdId;
  private Long smId;
  private Long ciId;
//Setmealdetailed表checkitem表与一对一关系
  private Checkitem checkitem;  
}

检查项表:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Checkitem {
  private Integer ciId;
  private String ciName;
  private String ciContent;
  private String meaning;
  private String remarks;
}

套餐表:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Setmeal {
  private Integer smId;
  private String name;
  private Integer type;
  private Double price;
//中间表Setmealdetailed,多个数据用集合
  private List<Setmealdetailed> Setmealdetailed;
}

Mapper接口:

@Mapper
public interface SetmealMapper {
	List<Setmeal> querySetmeals(int type);
}

Mapper接口对应的映射文件:

<?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">
        <!-- SetmealMapper接口中的限定名 -->
<mapper namespace="com.tijian.mapper.SetmealMapper">
<!-- 定义结果映射,type属性指定结果集对应的Java类型,id属性指定结果集的标识符 -->
	<resultMap type="Setmeal" id="setmeal">
	<!-- 定义主键映射,column属性指定数据库中的列名,property属性指定Java对象中的属性名 -->
		<id column="smId" property="smId"/>
		<!-- 定义普通映射,column属性指定数据库中的列名,property属性指定Java对象中的属性名 -->
		<result column="name" property="name"/>
		<result column="type" property="type"/>
		<result column="price" property="price"/>
		<collection property="Setmealdetailed" ofType="Setmealdetailed" javaType="List"> <!-- property——→po数据 -->
			<id column="sdId" property="sdId"/>
	 		<result column="smId" property="smId"/>
	 		<result column="ciId" property="ciId"/>
			<association property="checkitem" javaType="Checkitem">  <!-- po数据 -->
		 		<id column="ciId" property="ciId"/>
		 		<result column="ciName" property="ciName"/>
		 		<result column="ciContent" property="ciContent"/>
		 		<result column="meaning" property="meaning"/>
		 		<result column="remarks" property="remarks"/>
	 		</association>
	 	</collection>
	 	<!-- 结束结果映射 -->
	</resultMap>
	<select id="querySetmeals" resultMap="setmeal">
		select *
		from 
			setmealdetailed sd 
			join checkitem c
			on c.ciId=sd.ciId
			join setmeal sm
	        on sd.smId=sm.smId
        where sm.type=#{type}
	</select>
</mapper>

映射文件中的SQL语句:

这是一个查询语句,用于查询体检套餐。

  1. select *:查询所有列。
  2. from setmealdetailed sd join checkitem c on c.ciId=sd.ciId join setmeal sm on sd.smId=sm.smId:从setmealdetailed、checkitem和setmeal三个表中进行关联查询。通过sd和c表以ciId列为关联条件,关联出体检项目的详细信息;通过sd和sm表以smId列为关联条件,关联出体检套餐的信息。
  3. where sm.type=#{type}:根据体检套餐的类型进行条件查询,其中#{type}是一个占位符,表示需要从Java对象中动态获取type属性的值。
<select id="querySetmeals" resultMap="setmeal">
		select *
		from 
			setmealdetailed sd 
			join checkitem c
			on c.ciId=sd.ciId
			join setmeal sm
	        on sd.smId=sm.smId
        where sm.type=#{type}
	</select>

service接口:

SetmealService接口定义了查询体检套餐的方法querySetmeals,该方法接受一个type参数用于查询指定类型的体检套餐。

public interface SetmealService {
    List<Setmeal> querySetmeals(int type);
}

service实现类:

SetmealServiceImpl实现了SetmealService接口,通过自动装配SetmealMapper接口实现数据库查询,并将结果返回给调用方。

@Service
public class SetmealServiceImpl implements SetmealService {
    @Autowired
    private SetmealMapper mapper;
	@Override
	public List<Setmeal> querySetmeals(int type) {
		// TODO 自动生成的方法存根
		return mapper.querySetmeals(type);
	}
}

controller类:

SetmealController类是一个基于Spring框架的RESTful风格的Web服务控制器,使用@RestController@RequestMapping注解声明了一个"/setmeal"的请求处理器。该类通过自动装配SetmealService接口来实现查询体检套餐的功能,当收到GET请求后,调用querySetmeals方法,并将结果返回给调用方。其中,type参数由请求URL中的查询参数指定。

@RestController
@RequestMapping("/setmeal")
public class SetmealController {
    @Autowired
    private SetmealService service;
    @GetMapping
    public List<Setmeal> query(int type){
        return service.querySetmeals(type);
    }
}

测试:(JSON格式)

前端:

<template>
    <!-- 定义模板 -->
    <div class="wrapper">
        <!-- 定义页面外层容器 -->
        <header>
            <!-- 定义头部 -->
            <span><</span>
            <!-- 定义左侧返回按钮 -->
            请您选择体检套餐
            <!-- 显示标题 -->
        </header>
        <div class="taocan" v-for="(setItem, i) in setItems" :key="i">
            <!-- 设置套餐容器,并遍历数据,为每个套餐设置唯一的key值 -->
            <div class="top">
                <!-- 定义套餐顶部栏 -->
                <div class="title">
                    <!-- 定义套餐标题 -->
                    <h3>{{ this.text }}套餐</h3>
                    <!-- 显示套餐名 -->
                    <p>{{ setItem.name }}</p>
                    <!-- 显示套餐描述 -->
                </div>
                <div class="xiangqing">
                    <!-- 定义套餐详情栏 -->
                    <span>详情</span>
                    <!-- 显示"详情"按钮 -->
                    <i class="fa fa-sort-down" @click="showit(i)"></i>
                    <!-- 显示下拉箭头 -->
                </div>
            </div>
            <div class="xiangmu" v-show="showFlag[i]">
                <!-- 定义项目表格,根据showFlag变量判断是否展开表格 -->
                <table>
                    <tr>
                        <!-- 定义表头 -->
                        <th>检查类型</th>
                        <th>检查内容</th>
                        <th>检查意义</th>
                    </tr>
                    <tr class="tr" v-for="(setmealdetailed, j) in setItems[i].setmealdetailed" :key="j">
                        <!-- 遍历数据,显示检查类型、检查内容、检查意义 -->
                        <th> {{ setmealdetailed.checkitem.ciName }} </th>
                        <th> {{ setmealdetailed.checkitem.ciContent }} </th>
                        <th> {{ setmealdetailed.checkitem.meaning }} </th>
                    </tr>
                </table>
            </div>
        </div>
    </div>
</template>
<!-- 定义javascript代码 -->
<script>
import axios from 'axios';
export default {
    data() {
        // 数据初始化
        return {
            user: {},  // 用户数据
            setItems: [],  // 套餐数据
            text: '女士',  // 默认套餐标题中“女士”
            showFlag: []  // 用于判断项目表格是否展开的标识数组
        }
    },
    methods: {
        // 定义方法
        showit(i) {
            this.showFlag[i] = !this.showFlag[i]
            // 点击详情按钮切换表格的展开状态
        }
    },
    components: {},
    computed: {},
    watch: {},
    mounted() {
        // 组件挂载时执行的代码   (获取用户信息)
        this.user = JSON.parse(sessionStorage.getItem('login'))
        // 获取用户信息
        axios.get(`/api/setmeal?type=${this.user.sex}`)
            .then(resp => {
                // 获取套餐信息(返回的数据 )
                this.setItems = resp.data
                // 将后台获取的套餐数据赋值给setItems
                this.showFlag = Array(this.setItems.length).fill(false)
                // 使用长度为setItems.length,初始值为false的数组,用于判断项目表格是否展开
                if (this.user.sex == 1) {
                    this.text = '男士'
                    // 如果登录用户性别为男,将套餐标题中的“女士”修改为“男士”
                }
            })
    }
}
</script>

css样式:

<style scoped>
.wrapper {  /* 定义样式,设置首页外层容器的属性 */
    width: 100vw;
    min-height: 100%;
    height: auto;
    background-color: #f7f7f7;  /* 设置背景颜色为#f7f7f7 */
}
header {  /* 定义样式,设置头部的属性 */
    width: 100vw;
    height: 12vw;
    flex: 0 0 12vw;
    background-color: #fff;  /* 设置背景颜色为#fff */
    display: flex;  /* 将header设置成flex布局 */
    align-items: center;  /* 设置垂直居中 */
    font-size: 6vw;  /* 设置字体大小为6vw */
    color: 4vw;  /* 设置字体颜色为#4vw */
}
header span {  /* 定义样式,设置头部标题的属性 */
    margin-left: 4vw;
    margin-right: 18vw;
}
.taocan {  /* 定义样式,设置套餐容器的属性 */
    width: 94vw;
    display: flex;  /* 将套餐容器设置成flex布局 */
    flex-direction: column;  /* 设置flex布局方向为垂直 */
}
.top {  /* 定义样式,设置套餐顶部栏的属性 */
    width: 94vw;
    height: 15vh;
    background-color: #fff;  /* 设置背景颜色为#fff */
    margin-left: 3vw;
    margin-top: 4vw;
    display: flex;  /* 将套餐顶部栏设置成flex布局 */
    align-items: center;  /* 设置垂直居中 */
}
.title {  /* 定义样式,设置套餐标题的属性 */
    width: 40vw;
    height: 15vh;
    font-size: 4vw;  /* 设置字体大小为4vw */
    display: flex;  /* 将套餐标题设置成flex布局 */
    flex-direction: column;  /* 设置flex布局方向为垂直 */
    justify-content: center;  /* 设置垂直居中 */
    margin-left: 10vw;
}
.title p {  /* 定义样式,设置套餐标题中的段落的属性 */
    color: #575656;  /* 设置字体颜色为#575656 */
}
.xiangqing {  /* 定义样式,设置套餐详情栏的属性 */
    width: 15vw;
    height: 10vw;
    font-size: 4vw;  /* 设置字体大小为4vw */
    display: flex;  /* 将套餐详情栏设置成flex布局 */
    align-items: center;  /* 设置垂直居中 */
    justify-content: space-around;  /* 设置子元素之间平均分配空间 */
    margin-left: 20vw;
    border-left: 0.3px solid #cdcdcd;  /* 设置左侧边框为0.3px宽度、#cdcdcd颜色 */
    color: #575656;
}
.xiangmu {  /* 定义样式,设置项目表格的属性 */
    width: 94vw;
    margin-left: 3vw;
}
table {  /* 定义样式,设置表格的属性 */
    border-collapse: collapse;  /* 设置表格边框合并 */
}
.xiangmu th {  /* 定义样式,设置表头的属性 */
    font-size: 3.5vw;
    width: 94vw;
    border: 0.2vw solid #bcbcbc;  /* 设置表格边框宽度为0.2vw、颜色为#bcbcbc */
}
.tr {  /* 定义样式,设置表格行的属性 */
    background-color: #fff;  /* 设置背景颜色为#fff */
}
</style>

到此这篇关于MyBatis关联查询的实现的文章就介绍到这了,更多相关MyBatis关联查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java Eclipse进行断点调试的方法

    Java Eclipse进行断点调试的方法

    本篇文章主要介绍了Java Eclipse进行断点调试的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • Springboot整合RabbitMQ实现发送验证码的示例代码

    Springboot整合RabbitMQ实现发送验证码的示例代码

    这篇文章主要介绍了Springboot整合RabbitMQ实现发送验证码的功能,基于AMQP协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开 发中应用非常广泛,需要的朋友可以参考下
    2022-02-02
  • springmvc中进行数据保存以及日期参数的保存过程解析

    springmvc中进行数据保存以及日期参数的保存过程解析

    这篇文章主要介绍了springmvc中进行数据保存以及日期参数的保存过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Springboot RestTemplate设置超时时间的方法(Spring boot 版本)

    Springboot RestTemplate设置超时时间的方法(Spring boot 

    这篇文章主要介绍了Springboot RestTemplate设置超时时间的方法,包括Spring boot 版本<=1.3和Spring boot 版本>=1.4,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • Mybatis实现数据的增删改查实例(CRUD)

    Mybatis实现数据的增删改查实例(CRUD)

    本篇文章主要介绍了Mybatis实现数据的增删改查实例(CRUD),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • SpringData如何通过@Query注解支持JPA语句和原生SQL语句

    SpringData如何通过@Query注解支持JPA语句和原生SQL语句

    这篇文章主要介绍了SpringData如何通过@Query注解支持JPA语句和原生SQL语句,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • 基于AOP+Redis的简易滑动窗口限流

    基于AOP+Redis的简易滑动窗口限流

    本文主要介绍了基于AOP+Redis的简易滑动窗口限流,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-07-07
  • Spring PlatformTransactionManager使用、原理深度解析

    Spring PlatformTransactionManager使用、原理深度解析

    PlatformTransactionManager是Spring事务管理的核心接口,提供统一的编程模型封装底层事务能力,本文从工作原理、使用方法、注意事项和设计考量四个维度深入解析,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • 使用Filter拦截器如何实现请求跨域转发

    使用Filter拦截器如何实现请求跨域转发

    这篇文章主要介绍了使用Filter拦截器如何实现请求跨域转发,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java链接redis_动力节点Java学院整理

    Java链接redis_动力节点Java学院整理

    这篇文章主要介绍了Java链接redis,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论